diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage index 63798e2e29e44..3f4732f15f334 100644 --- a/.ci/Jenkinsfile_coverage +++ b/.ci/Jenkinsfile_coverage @@ -23,6 +23,8 @@ def handleIngestion(timestamp) { kibanaPipeline.downloadCoverageArtifacts() kibanaCoverage.prokLinks("### Process HTML Links") kibanaCoverage.collectVcsInfo("### Collect VCS Info") + kibanaCoverage.generateReports("### Merge coverage reports") + kibanaCoverage.uploadCombinedReports() kibanaCoverage.ingest(timestamp, '### Injest && Upload') kibanaCoverage.uploadCoverageStaticSite(timestamp) } diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky index 425a5e71798b1..2f496329dfd8e 100644 --- a/.ci/Jenkinsfile_flaky +++ b/.ci/Jenkinsfile_flaky @@ -70,6 +70,8 @@ def getWorkerFromParams(isXpack, job, ciGroup) { "run `node scripts/mocha`" ) }) + } else if (job == 'accessibility') { + return kibanaPipeline.functionalTestProcess('kibana-accessibility', './test/scripts/jenkins_accessibility.sh') } else if (job == 'firefoxSmoke') { return kibanaPipeline.functionalTestProcess('firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh') } else if(job == 'visualRegression') { @@ -79,7 +81,9 @@ def getWorkerFromParams(isXpack, job, ciGroup) { } } - if (job == 'firefoxSmoke') { + if (job == 'accessibility') { + return kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh') + } else if (job == 'firefoxSmoke') { return kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh') } else if(job == 'visualRegression') { return kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh') diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy index 8e9b041d32d3e..0cd64dcfd41fd 100644 --- a/.ci/end2end.groovy +++ b/.ci/end2end.groovy @@ -12,8 +12,7 @@ pipeline { environment { BASE_DIR = 'src/github.com/elastic/kibana' HOME = "${env.WORKSPACE}" - APM_ITS = 'apm-integration-testing' - CYPRESS_DIR = 'x-pack/plugins/apm/e2e' + E2E_DIR = 'x-pack/plugins/apm/e2e' PIPELINE_LOG_LEVEL = 'DEBUG' } options { @@ -43,32 +42,6 @@ pipeline { env.APM_UPDATED = isGitRegionMatch(patterns: regexps) } } - dir("${APM_ITS}"){ - git changelog: false, - credentialsId: 'f6c7695a-671e-4f4f-a331-acdce44ff9ba', - poll: false, - url: "git@github.com:elastic/${APM_ITS}.git" - } - } - } - stage('Start services') { - options { skipDefaultCheckout() } - when { - anyOf { - expression { return params.FORCE } - expression { return env.APM_UPDATED != "false" } - } - } - steps { - notifyStatus('Starting services', 'PENDING') - dir("${APM_ITS}"){ - sh './scripts/compose.py start master --no-kibana' - } - } - post { - unsuccessful { - notifyStatus('Environmental issue', 'FAILURE') - } } } stage('Prepare Kibana') { @@ -85,7 +58,7 @@ pipeline { steps { notifyStatus('Preparing kibana', 'PENDING') dir("${BASE_DIR}"){ - sh script: "${CYPRESS_DIR}/ci/prepare-kibana.sh" + sh "${E2E_DIR}/ci/prepare-kibana.sh" } } post { @@ -105,24 +78,20 @@ pipeline { steps{ notifyStatus('Running smoke tests', 'PENDING') dir("${BASE_DIR}"){ - sh ''' - jobs -l - docker build --tag cypress --build-arg NODE_VERSION=$(cat .node-version) ${CYPRESS_DIR}/ci - docker run --rm -t --user "$(id -u):$(id -g)" \ - -v `pwd`:/app --network="host" \ - --name cypress cypress''' + sh "${E2E_DIR}/ci/run-e2e.sh" } } post { always { - dir("${BASE_DIR}"){ - archiveArtifacts(allowEmptyArchive: false, artifacts: "${CYPRESS_DIR}/**/screenshots/**,${CYPRESS_DIR}/**/videos/**,${CYPRESS_DIR}/**/test-results/*e2e-tests.xml") - junit(allowEmptyResults: true, testResults: "${CYPRESS_DIR}/**/test-results/*e2e-tests.xml") - } - dir("${APM_ITS}"){ - sh 'docker-compose logs > apm-its.log || true' - sh 'docker-compose down -v || true' - archiveArtifacts(allowEmptyArchive: false, artifacts: 'apm-its.log') + dir("${BASE_DIR}/${E2E_DIR}"){ + archiveArtifacts(allowEmptyArchive: false, artifacts: 'cypress/screenshots/**,cypress/videos/**,cypress/test-results/*e2e-tests.xml') + junit(allowEmptyResults: true, testResults: 'cypress/test-results/*e2e-tests.xml') + dir('tmp/apm-integration-testing'){ + sh 'docker-compose logs > apm-its-docker.log || true' + sh 'docker-compose down -v || true' + archiveArtifacts(allowEmptyArchive: true, artifacts: 'apm-its-docker.log') + } + archiveArtifacts(allowEmptyArchive: true, artifacts: 'tmp/*.log') } } unsuccessful { @@ -137,7 +106,7 @@ pipeline { post { always { dir("${BASE_DIR}"){ - archiveArtifacts(allowEmptyArchive: true, artifacts: "${CYPRESS_DIR}/ingest-data.log,kibana.log") + archiveArtifacts(allowEmptyArchive: true, artifacts: "${E2E_DIR}/kibana.log") } } } diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es index c87ca01354315..a6fe980242afe 100644 --- a/.ci/es-snapshots/Jenkinsfile_verify_es +++ b/.ci/es-snapshots/Jenkinsfile_verify_es @@ -22,8 +22,8 @@ def SNAPSHOT_MANIFEST = "https://storage.googleapis.com/kibana-ci-es-snapshots-d kibanaPipeline(timeoutMinutes: 150) { catchErrors { slackNotifications.onFailure( - title: ":broken_heart: *<${env.BUILD_URL}|[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure>*", - message: ":broken_heart: [${SNAPSHOT_VERSION}] ES Snapshot Verification Failure", + title: "*<${env.BUILD_URL}|[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure>*", + message: "[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure", ) { retryable.enable(2) withEnv(["ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}"]) { diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index ab68a60dcfc27..11f9ccaeddb1e 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -35,20 +35,20 @@ mkdir -p ".geckodriver" cp "node_modules/geckodriver/geckodriver.tar.gz" .geckodriver/geckodriver.tar.gz echo "$geckodriverPkgVersion" > .geckodriver/pkgVersion +echo "Creating bootstrap_cache archive" + # archive cacheable directories mkdir -p "$HOME/.kibana/bootstrap_cache" tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ - node_modules \ - packages/*/node_modules \ - x-pack/node_modules \ - x-pack/legacy/plugins/*/node_modules \ - x-pack/legacy/plugins/reporting/.chromium \ - test/plugin_functional/plugins/*/node_modules \ - examples/*/node_modules \ + x-pack/plugins/reporting/.chromium \ .es \ .chromedriver \ .geckodriver; +echo "Adding node_modules" +# Find all of the node_modules directories that aren't test fixtures, and aren't inside other node_modules directories, and append them to the tar +find . -type d -name node_modules -not -path '*__fixtures__*' -prune -print0 | xargs -0I % tar -rf "$HOME/.kibana/bootstrap_cache/$branch.tar" "%" + echo "created $HOME/.kibana/bootstrap_cache/$branch.tar" if [ "$branch" == "master" ]; then diff --git a/.eslintignore b/.eslintignore index 362b3e42d48e5..fbdd70703f3c4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ /.es /build /built_assets +/config/apm.dev.js /data /html_docs /optimize @@ -25,7 +26,8 @@ target /src/plugins/vis_type_timelion/public/_generated_/** /src/plugins/vis_type_timelion/public/webpackShims/jquery.flot.* /x-pack/legacy/plugins/**/__tests__/fixtures/** -/x-pack/plugins/apm/e2e/cypress/**/snapshots.js +/x-pack/plugins/apm/e2e/**/snapshots.js +/x-pack/plugins/apm/e2e/tmp/* /x-pack/plugins/canvas/canvas_plugin /x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts /x-pack/plugins/canvas/shareable_runtime/build diff --git a/.eslintrc.js b/.eslintrc.js index 86ac92de9042d..f0b7d6864bef0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,7 +50,7 @@ const ELASTIC_LICENSE_HEADER = ` `; const allMochaRulesOff = {}; -Object.keys(require('eslint-plugin-mocha').rules).forEach(k => { +Object.keys(require('eslint-plugin-mocha').rules).forEach((k) => { allMochaRulesOff['mocha/' + k] = 'off'; }); @@ -202,6 +202,11 @@ module.exports = { from: ['(src|x-pack)/plugins/*/server/**/*'], errorMessage: `Public code can not import from server, use a common directory.`, }, + { + target: ['(src|x-pack)/plugins/*/common/**/*'], + from: ['(src|x-pack)/plugins/*/(server|public)/**/*'], + errorMessage: `Common code can not import from server or public, use a common directory.`, + }, { target: [ '(src|x-pack)/legacy/**/*', @@ -220,7 +225,7 @@ module.exports = { '!src/core/server/index.ts', // relative import '!src/core/server/mocks{,.ts}', '!src/core/server/types{,.ts}', - '!src/core/server/test_utils', + '!src/core/server/test_utils{,.ts}', // for absolute imports until fixed in // https://github.com/elastic/kibana/issues/36096 '!src/core/server/*.test.mocks{,.ts}', @@ -582,11 +587,11 @@ module.exports = { }, /** - * SIEM overrides + * Security Solution overrides */ { // front end typescript and javascript files only - files: ['x-pack/plugins/siem/public/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/security_solution/public/**/*.{js,ts,tsx}'], rules: { 'import/no-nodejs-modules': 'error', 'no-restricted-imports': [ @@ -600,7 +605,7 @@ module.exports = { }, { // typescript only for front and back end - files: ['x-pack/{,legacy/}plugins/siem/**/*.{ts,tsx}'], + files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{ts,tsx}'], rules: { // This will be turned on after bug fixes are complete // '@typescript-eslint/explicit-member-accessibility': 'warn', @@ -635,7 +640,7 @@ module.exports = { // { // // will introduced after the other warns are fixed // // typescript and javascript for front end react performance - // files: ['x-pack/plugins/siem/public/**/!(*.test).{js,ts,tsx}'], + // files: ['x-pack/plugins/security_solution/public/**/!(*.test).{js,ts,tsx}'], // plugins: ['react-perf'], // rules: { // // 'react-perf/jsx-no-new-object-as-prop': 'error', @@ -646,7 +651,7 @@ module.exports = { // }, { // typescript and javascript for front and back end - files: ['x-pack/{,legacy/}plugins/siem/**/*.{js,ts,tsx}'], + files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{js,ts,tsx}'], plugins: ['eslint-plugin-node', 'react'], env: { mocha: true, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a160094a54130..4716346277029 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,10 +6,7 @@ /x-pack/plugins/dashboard_enhanced/ @elastic/kibana-app /x-pack/plugins/lens/ @elastic/kibana-app /x-pack/plugins/graph/ @elastic/kibana-app -/src/legacy/server/url_shortening/ @elastic/kibana-app -/src/legacy/server/sample_data/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app /src/plugins/input_control_vis/ @elastic/kibana-app @@ -142,6 +139,8 @@ /config/kibana.yml @elastic/kibana-platform /x-pack/plugins/features/ @elastic/kibana-platform /x-pack/plugins/licensing/ @elastic/kibana-platform +/x-pack/plugins/global_search/ @elastic/kibana-platform +/x-pack/plugins/cloud/ @elastic/kibana-platform /packages/kbn-config-schema/ @elastic/kibana-platform /src/legacy/server/config/ @elastic/kibana-platform /src/legacy/server/http/ @elastic/kibana-platform @@ -157,7 +156,6 @@ /x-pack/legacy/plugins/security/ @elastic/kibana-security /x-pack/legacy/plugins/spaces/ @elastic/kibana-security /x-pack/plugins/spaces/ @elastic/kibana-security -/x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security /x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security /x-pack/plugins/security/ @elastic/kibana-security /x-pack/test/api_integration/apis/security/ @elastic/kibana-security @@ -179,7 +177,7 @@ /x-pack/plugins/telemetry_collection_xpack/ @elastic/pulse # Kibana Alerting Services -/x-pack/plugins/alerting/ @elastic/kibana-alerting-services +/x-pack/plugins/alerts/ @elastic/kibana-alerting-services /x-pack/plugins/actions/ @elastic/kibana-alerting-services /x-pack/plugins/event_log/ @elastic/kibana-alerting-services /x-pack/plugins/task_manager/ @elastic/kibana-alerting-services @@ -228,12 +226,12 @@ /x-pack/test/plugin_functional/plugins/resolver_test/ @elastic/endpoint-app-team @elastic/siem /x-pack/test/plugin_functional/test_suites/resolver/ @elastic/endpoint-app-team @elastic/siem -# SIEM -/x-pack/plugins/siem/ @elastic/siem @elastic/endpoint-app-team +# Security Solution +/x-pack/plugins/security_solution/ @elastic/siem @elastic/endpoint-app-team /x-pack/test/detection_engine_api_integration @elastic/siem @elastic/endpoint-app-team -/x-pack/test/api_integration/apis/siem @elastic/siem @elastic/endpoint-app-team +/x-pack/test/api_integration/apis/security_solution @elastic/siem @elastic/endpoint-app-team /x-pack/plugins/case @elastic/siem @elastic/endpoint-app-team /x-pack/plugins/lists @elastic/siem @elastic/endpoint-app-team # Security Intelligence And Analytics -/x-pack/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules @elastic/security-intelligence-analytics +/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules @elastic/security-intelligence-analytics diff --git a/.gitignore b/.gitignore index f843609d32f7e..b3911d0f8d0c2 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,8 @@ package-lock.json *.sublime-* npm-debug.log* .tern-project -x-pack/plugins/apm/tsconfig.json -apm.tsconfig.json -/x-pack/legacy/plugins/apm/e2e/snapshots.js -/x-pack/plugins/apm/e2e/snapshots.js .nyc_output + +# apm plugin +/x-pack/plugins/apm/tsconfig.json +apm.tsconfig.json diff --git a/.node-version b/.node-version index 5b7269c0a98f3..b61c07ffddbd1 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -10.19.0 +10.21.0 diff --git a/.nvmrc b/.nvmrc index 5b7269c0a98f3..b61c07ffddbd1 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -10.19.0 +10.21.0 diff --git a/.sass-lint.yml b/.sass-lint.yml index db895583eb8a7..eb43af293c670 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -4,7 +4,6 @@ files: - 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss' - 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss' - 'src/plugins/vis_type_xy/**/*.s+(a|c)ss' - - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' - 'x-pack/plugins/lens/**/*.s+(a|c)ss' @@ -12,6 +11,7 @@ files: - 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss' - 'x-pack/plugins/maps/**/*.s+(a|c)ss' - 'x-pack/plugins/spaces/**/*.s+(a|c)ss' + - 'x-pack/plugins/security/**/*.s+(a|c)ss' ignore: - 'x-pack/plugins/canvas/shareable_runtime/**/*.s+(a|c)ss' rules: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9a2f609e0913..4bf659345d387 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,26 +13,44 @@ A high level overview of our contributing guidelines. - ["My issue isn't getting enough attention"](#my-issue-isnt-getting-enough-attention) - ["I want to help!"](#i-want-to-help) - [How We Use Git and GitHub](#how-we-use-git-and-github) + - [Forking](#forking) - [Branching](#branching) - [Commits and Merging](#commits-and-merging) + - [Rebasing and fixing merge conflicts](#rebasing-and-fixing-merge-conflicts) - [What Goes Into a Pull Request](#what-goes-into-a-pull-request) - [Contributing Code](#contributing-code) - [Setting Up Your Development Environment](#setting-up-your-development-environment) + - [Increase node.js heap size](#increase-nodejs-heap-size) + - [Running Elasticsearch Locally](#running-elasticsearch-locally) + - [Nightly snapshot (recommended)](#nightly-snapshot-recommended) + - [Keeping data between snapshots](#keeping-data-between-snapshots) + - [Source](#source) + - [Archive](#archive) + - [Sample Data](#sample-data) + - [Running Elasticsearch Remotely](#running-elasticsearch-remotely) + - [Running remote clusters](#running-remote-clusters) + - [Running Kibana](#running-kibana) + - [Running Kibana in Open-Source mode](#running-kibana-in-open-source-mode) + - [Unsupported URL Type](#unsupported-url-type) - [Customizing `config/kibana.dev.yml`](#customizing-configkibanadevyml) + - [Potential Optimization Pitfalls](#potential-optimization-pitfalls) - [Setting Up SSL](#setting-up-ssl) - [Linting](#linting) + - [Setup Guide for VS Code Users](#setup-guide-for-vs-code-users) - [Internationalization](#internationalization) - [Localization](#localization) + - [Styling with SASS](#styling-with-sass) - [Testing and Building](#testing-and-building) - [Debugging server code](#debugging-server-code) - [Instrumenting with Elastic APM](#instrumenting-with-elastic-apm) - - [Debugging Unit Tests](#debugging-unit-tests) - - [Unit Testing Plugins](#unit-testing-plugins) - - [Automated Accessibility Testing](#automated-accessibility-testing) - - [Cross-browser compatibility](#cross-browser-compatibility) - - [Testing compatibility locally](#testing-compatibility-locally) - - [Running Browser Automation Tests](#running-browser-automation-tests) - - [Browser Automation Notes](#browser-automation-notes) + - [Unit testing frameworks](#unit-testing-frameworks) + - [Running specific Kibana tests](#running-specific-kibana-tests) + - [Debugging Unit Tests](#debugging-unit-tests) + - [Unit Testing Plugins](#unit-testing-plugins) + - [Automated Accessibility Testing](#automated-accessibility-testing) + - [Cross-browser compatibility](#cross-browser-compatibility) + - [Testing compatibility locally](#testing-compatibility-locally) + - [Running Browser Automation Tests](#running-browser-automation-tests) - [Building OS packages](#building-os-packages) - [Writing documentation](#writing-documentation) - [Release Notes Process](#release-notes-process) @@ -414,7 +432,7 @@ extract them to a `JSON` file or integrate translations back to Kibana. To know We cannot support accepting contributions to the translations from any source other than the translators we have engaged to do the work. We are still to develop a proper process to accept any contributed translations. We certainly appreciate that people care enough about the localization effort to want to help improve the quality. We aim to build out a more comprehensive localization process for the future and will notify you once contributions can be supported, but for the time being, we are not able to incorporate suggestions. -### Syling with SASS +### Styling with SASS When writing a new component, create a sibling SASS file of the same name and import directly into the JS/TS component file. Doing so ensures the styles are never separated or lost on import and allows for better modularization (smaller individual plugin asset footprint). @@ -467,10 +485,10 @@ macOS users on a machine with a discrete graphics card may see significant speed - Uncheck the "Prefer integrated to discrete GPU" option - Restart iTerm -### Debugging Server Code +#### Debugging Server Code `yarn debug` will start the server with Node's inspect flag. Kibana's development mode will start three processes on ports `9229`, `9230`, and `9231`. Chrome's developer tools need to be configured to connect to all three connections. Add `localhost:` for each Kibana process in Chrome's developer tools connection tab. -### Instrumenting with Elastic APM +#### Instrumenting with Elastic APM Kibana ships with the [Elastic APM Node.js Agent](https://github.com/elastic/apm-agent-nodejs) built-in for debugging purposes. Its default configuration is meant to be used by core Kibana developers only, but it can easily be re-configured to your needs. @@ -490,16 +508,24 @@ module.exports = { }; ``` +APM [Real User Monitoring agent](https://www.elastic.co/guide/en/apm/agent/rum-js/current/index.html) is not available in the Kibana distributables, +however the agent can be enabled by setting `ELASTIC_APM_ACTIVE` to `true`. +flags +``` +ELASTIC_APM_ACTIVE=true yarn start +// activates both Node.js and RUM agent +``` + Once the agent is active, it will trace all incoming HTTP requests to Kibana, monitor for errors, and collect process-level metrics. The collected data will be sent to the APM Server and is viewable in the APM UI in Kibana. -### Unit testing frameworks +#### Unit testing frameworks Kibana is migrating unit testing from Mocha to Jest. Legacy unit tests still exist in Mocha but all new unit tests should be written in Jest. Mocha tests are contained in `__tests__` directories. Whereas Jest tests are stored in the same directory as source code files with the `.test.js` suffix. -### Running specific Kibana tests +#### Running specific Kibana tests The following table outlines possible test file locations and how to invoke them: @@ -532,7 +558,7 @@ Test runner arguments: yarn test:ftr:runner --config test/api_integration/config.js --grep='should return 404 if id does not match any sample data sets' ``` -### Debugging Unit Tests +#### Debugging Unit Tests The standard `yarn test` task runs several sub tasks and can take several minutes to complete, making debugging failures pretty painful. In order to ease the pain specialized tasks provide alternate methods for running the tests. @@ -559,7 +585,7 @@ In the screenshot below, you'll notice the URL is `localhost:9876/debug.html`. Y ![Browser test debugging](http://i.imgur.com/DwHxgfq.png) -### Unit Testing Plugins +#### Unit Testing Plugins This should work super if you're using the [Kibana plugin generator](https://github.com/elastic/kibana/tree/master/packages/kbn-plugin-generator). If you're not using the generator, well, you're on your own. We suggest you look at how the generator works. @@ -570,7 +596,7 @@ yarn test:mocha yarn test:karma:debug # remove the debug flag to run them once and close ``` -### Automated Accessibility Testing +#### Automated Accessibility Testing To run the tests locally: @@ -587,11 +613,11 @@ can be run locally using their browser plugins: - [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US) - [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) -### Cross-browser Compatibility +#### Cross-browser Compatibility -#### Testing Compatibility Locally +##### Testing Compatibility Locally -##### Testing IE on OS X +###### Testing IE on OS X * [Download VMWare Fusion](http://www.vmware.com/products/fusion/fusion-evaluation.html). * [Download IE virtual machines](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/#downloads) for VMWare. @@ -602,7 +628,7 @@ can be run locally using their browser plugins: * Now you can run your VM, open the browser, and navigate to `http://computer.local:5601` to test Kibana. * Alternatively you can use browserstack -#### Running Browser Automation Tests +##### Running Browser Automation Tests [Read about the `FunctionalTestRunner`](https://www.elastic.co/guide/en/kibana/current/development-functional-tests.html) to learn more about how you can run and develop functional tests for Kibana core and plugins. diff --git a/Gruntfile.js b/Gruntfile.js index c33a576d4340f..0216ab12f7cc5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,7 +19,7 @@ require('./src/setup_node_env'); -module.exports = function(grunt) { +module.exports = function (grunt) { // set the config once before calling load-grunt-config // and once during so that we have access to it via // grunt.config.get() within the config files diff --git a/Jenkinsfile b/Jenkinsfile index f435b18c6d824..11dca544f3226 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,9 +41,9 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true) { 'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9), 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), - 'xpack-siemCypress': { processNumber -> - whenChanged(['x-pack/plugins/siem/', 'x-pack/test/siem_cypress/']) { - kibanaPipeline.functionalTestProcess('xpack-siemCypress', './test/scripts/jenkins_siem_cypress.sh')(processNumber) + 'xpack-securitySolutionCypress': { processNumber -> + whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) { + kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber) } }, diff --git a/config/kibana.yml b/config/kibana.yml index 8725888159506..72e0764f849a0 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -90,7 +90,7 @@ # Specifies the path where Kibana creates the process ID file. #pid.file: /var/run/kibana.pid -# Enables you specify a file where Kibana stores log output. +# Enables you to specify a file where Kibana stores log output. #logging.dest: stdout # Set the value of this setting to true to suppress all logging output. diff --git a/docs/api/saved-objects/bulk_get.asciidoc b/docs/api/saved-objects/bulk_get.asciidoc index a6fdeb69ba925..eaf91a662849e 100644 --- a/docs/api/saved-objects/bulk_get.asciidoc +++ b/docs/api/saved-objects/bulk_get.asciidoc @@ -35,7 +35,7 @@ experimental[] Retrieve multiple {kib} saved objects by ID. ==== Response body `saved_objects`:: - (array) Top-level property the contains objects that represent the response for each of the requested objects. The order of the objects in the response is identical to the order of the objects in the request. + (array) Top-level property containing objects that represent the response for each of the requested objects. The order of the objects in the response is identical to the order of the objects in the request. Saved objects that are unable to persist are replaced with an error object. diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index f89f994e59e57..7b771eb662616 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -11,7 +11,7 @@ or, to only show transactions that are slower than a specified time threshold. ==== Example APM app queries * Exclude response times slower than 2000 ms: `transaction.duration.us > 2000000` -* Filter by response status code: `context.response.status_code >= 400` +* Filter by response status code: `context.response.status_code ≥ 400` * Filter by single user ID: `context.user.id : 12` When querying in the APM app, you're merely searching and selecting data from fields in Elasticsearch documents. diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index 3e4b515e009de..7411f37d3c692 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -11,6 +11,12 @@ Some APM app features are provided via a REST API: * <> * <> +[float] +[[apm-api-example]] +=== Using the APIs + +Users interacting with APM APIs must have <>. +In addition, there are request headers to be aware of, like `kbn-xsrf: true`, and `Content-Type: applicaton/json`. Here's an example CURL request that adds an annotation to the APM app: [source,curl] @@ -32,16 +38,8 @@ curl -X POST \ }' ---- -For more information, the Kibana <> provides information on how to use Kibana APIs, -like required request headers and authentication options. - -// AGENT CONFIG API -// GET --> Feature (APM) Read -// CREATE/EDIT/DELETE --> Feature (APM) All - -// ANNOTATION API -// Feature (APM) All -// Index: `observability-annotations`. Privileges: `create_index`, `create_doc`, `manage`, and `read`. +The Kibana <> provides additional information on how to use Kibana APIs, +required request headers, and token-based authentication options. //// ******************************************************* @@ -61,6 +59,8 @@ The following Agent configuration APIs are available: * <> to list all Agent configurations. * <> to search for an Agent configuration. +See <> for information on the privileges required to use this API endpoint. + //// ******************************************************* //// @@ -327,6 +327,8 @@ The following APIs are available: By default, annotations are stored in a newly created `observability-annotations` index. The name of this index can be changed in your `config.yml` by editing `xpack.observability.annotations.index`. +See <> for information on the privileges required to use this API endpoint. + //// ******************************************************* //// diff --git a/docs/apm/apm-app-users.asciidoc b/docs/apm/apm-app-users.asciidoc new file mode 100644 index 0000000000000..442a07d279725 --- /dev/null +++ b/docs/apm/apm-app-users.asciidoc @@ -0,0 +1,256 @@ +[role="xpack"] +[[apm-app-users]] +== APM app users and privileges + +:beat_default_index_prefix: apm +:beat_kib_app: APM app +:annotation_index: `observability-annotations` + +++++ +Users and privileges +++++ + +You can use role-based access control to grant users access to secured +resources. The roles that you set up depend on your organization's security +requirements and the minimum privileges required to use specific features. + +{es-security-features} provides {ref}/built-in-roles.html[built-in roles] that grant a +subset of the privileges needed by APM users. +When possible, assign users the built-in roles to minimize the affect of future changes on your security strategy. +If no built-in role is available, you can assign users the privileges needed to accomplish a specific task. +In general, there are three types of privileges you'll work with: + +* **Elasticsearch cluster privileges**: Manage the actions a user can perform against your cluster. +* **Elasticsearch index privileges**: Control access to the data in specific indices your cluster. +* **Kibana space privileges**: Grant users write or read access to features and apps within Kibana. + +//// +*********************************** *********************************** +//// + +[role="xpack"] +[[apm-app-reader]] +=== APM reader user + +++++ +Create an APM reader user +++++ + +[[apm-app-reader-full]] +==== Full APM reader + +APM reader users typically need to view the APM app, dashboards, and visualizations that contain APM data. +These users might also need to create and edit dashboards, visualizations, and machine learning jobs. + +. Assign the following built-in roles: ++ +[options="header"] +|==== +|Role | Purpose + +|`kibana_admin` +|Grants access to all features in Kibana. + +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices + +|`machine_learning_admin` +|Grants the privileges required to create, update, and view machine learning jobs +|==== + +[[apm-app-reader-partial]] +==== Partial APM reader + +In some instances, you may wish to restrict certain Kibana apps that a user has access to. + +. Assign the following built in roles: ++ +[options="header"] +|==== +|Role | Purpose +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices +|==== + +. Assign space privileges to any Kibana space that the user needs access to. +Here are two examples: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +| `Read` or `All` on the {beat_kib_app} +| Allow the use of the the {beat_kib_app} + +| Spaces +| `Read` or `All` on Dashboards, Visualize, and Discover +| Allow the user to view, edit, and create dashboards, as well as browse data. +|==== + +. Finally, assign the following role if a user needs to enable and edit machine learning features: ++ +[options="header"] +|==== +|Role | Purpose + +|`machine_learning_admin` +|Grants the privileges required to create, update, and view machine learning jobs +|==== + +//// +*********************************** *********************************** +//// + +[role="xpack"] +[[apm-app-central-config-user]] +=== APM app central config user + +++++ +Create a central config user +++++ + +[[apm-app-central-config-manager]] +==== Central configuration manager + +Central configuration users need to be able to view, create, update, and delete Agent configurations. + +. Assign the following built-in roles: ++ +[options="header"] +|==== +|Role | Purpose + +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices +|==== + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`All` on {beat_kib_app} +|Allow full use of the {beat_kib_app} +|==== + +[[apm-app-central-config-reader]] +==== Central configuration reader + +In some instances, you may wish to create a user that can only read central configurations, +but not create, update, or delete them. + +. Assign the following built-in roles: ++ +[options="header"] +|==== +|Role | Purpose +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices +|==== + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`read` on the {beat_kib_app} +|Allow read access to the {beat_kib_app} +|==== + +[[apm-app-central-config-api]] +==== Central configuration API + +See <>. + +//// +*********************************** *********************************** +//// + +[role="xpack"] +[[apm-app-api-user]] +=== APM app API user + +++++ +Create an API user +++++ + +[[apm-app-api-config-manager]] +==== Central configuration API + +Users can list, search, create, update, and delete central configurations via the APM app API. + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`all` on the {beat_kib_app} +|Allow all access to the {beat_kib_app} +|==== + +[[apm-app-api-config-reader]] +==== Central configuration API reader + +Sometimes a user only needs to list and search central configurations via the APM app API. + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`read` on the {beat_kib_app} +|Allow read access to the {beat_kib_app} +|==== + +[[apm-app-api-annotation-manager]] +==== Annotation API + +Users can use the annotation API to create annotations on their APM data. + +. Create a new role, named something like `annotation_role`, +and assign the following privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +|Index +|`manage` on +{annotation_index}+ index +|Check if the +{annotation_index}+ index exists + +|Index +|`read` on +{annotation_index}+ index +|Read the +{annotation_index}+ index + +|Index +|`create_index` on +{annotation_index}+ index +|Create the +{annotation_index}+ index + +|Index +|`create_doc` on +{annotation_index}+ index +|Create new annotations in the +{annotation_index}+ index +|==== + +. Assign the `annotation_role` created previously, +and the following Kibana space privileges to any annotation API users: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`all` on the {beat_kib_app} +|Allow all access to the {beat_kib_app} +|==== + +//LEARN MORE +//Learn more about <>. diff --git a/docs/apm/deployment-annotations.asciidoc b/docs/apm/deployment-annotations.asciidoc index 9abcd9f6efc74..142b0c0193d74 100644 --- a/docs/apm/deployment-annotations.asciidoc +++ b/docs/apm/deployment-annotations.asciidoc @@ -7,13 +7,40 @@ ++++ For enhanced visibility into your deployments, we offer deployment annotations on all transaction charts. -This feature automatically tags new deployments, so you can easily see if your deploy has increased response times -for an end-user, or if the memory/CPU footprint of your application has changed. -Being able to identify bad deployments quickly enables you to rollback and fix issues without causing costly outages. +This feature enables you to easily determine if your deployment has increased response times for an end-user, +or if the memory/CPU footprint of your application has changed. +Being able to quickly identify bad deployments enables you to rollback and fix issues without causing costly outages. + +By default, automatic deployment annotations are enabled. +This means the APM app will create an annotation on your data when the `service.version` of your application changes. + +Alternatively, you can explicitly create deployment annotations with our annotation API. +The API can integrate into your CI/CD pipeline, +so that each time you deploy, a POST request is sent to the annotation API endpoint: + +[source,console] +---- +curl -X POST \ + http://localhost:5601/api/apm/services/${SERVICE_NAME}/annotation \ <1> +-H 'Content-Type: application/json' \ +-H 'kbn-xsrf: true' \ +-H 'Authorization: Basic ${API_KEY}' \ <2> +-d '{ + "@timestamp": "${DEPLOY_TIME}", <3> + "service": { + "version": "${SERVICE_VERSION}" <4> + }, + "message": "${MESSAGE}" <5> + }' +---- +<1> The `service.name` of your application +<2> An APM app API key with sufficient privileges +<3> The time of the deployment +<4> The `service.version` to be displayed in the annotation +<5> A custom message to be displayed in the annotation + +See the <> reference for more information. -Deployment annotations are enabled by default, and can be created with the <>. -If there are no created annotations for the selected time period, -the APM app will automatically annotate your data if the `service.version` of your application changes. NOTE: If custom annotations have been created for the selected time period, any derived annotations, i.e., those created automatically when `service.version` changes, will not be shown. diff --git a/docs/apm/index.asciidoc b/docs/apm/index.asciidoc index 79190efccdff2..53ffee5e061d6 100644 --- a/docs/apm/index.asciidoc +++ b/docs/apm/index.asciidoc @@ -31,6 +31,8 @@ include::getting-started.asciidoc[] include::how-to-guides.asciidoc[] +include::apm-app-users.asciidoc[] + include::settings.asciidoc[] include::api.asciidoc[] diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index 3a6a96fca9d09..db2f85c54c762 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -62,9 +62,9 @@ Machine learning jobs can be created to calculate anomaly scores on APM transact When these jobs are active, service maps will display a color-coded anomaly indicator based on the detected anomaly score: [horizontal] -image:apm/images/green-service.png[APM green service]:: Max anomaly score **<=25**. Service is healthy. +image:apm/images/green-service.png[APM green service]:: Max anomaly score **≤25**. Service is healthy. image:apm/images/yellow-service.png[APM yellow service]:: Max anomaly score **26-74**. Anomalous activity detected. Service may be degraded. -image:apm/images/red-service.png[APM red service]:: Max anomaly score **>=75**. Anomalous activity detected. Service is unhealthy. +image:apm/images/red-service.png[APM red service]:: Max anomaly score **≥75**. Anomalous activity detected. Service is unhealthy. [role="screenshot"] image::apm/images/apm-service-map-anomaly.png[Example view of anomaly scores on service maps in the APM app] @@ -92,10 +92,10 @@ Type and subtype are based on `span.type`, and `span.subtype`. Service maps are supported for the following Agent versions: [horizontal] -Go Agent:: >= v1.7.0 -Java Agent:: >= v1.13.0 -.NET Agent:: >= v1.3.0 -Node.js Agent:: >= v3.6.0 -Python Agent:: >= v5.5.0 -Ruby Agent:: >= v3.6.0 -Real User Monitoring (RUM) Agent:: >= v4.7.0 +Go Agent:: ≥ v1.7.0 +Java Agent:: ≥ v1.13.0 +.NET Agent:: ≥ v1.3.0 +Node.js Agent:: ≥ v3.6.0 +Python Agent:: ≥ v5.5.0 +Ruby Agent:: ≥ v3.6.0 +Real User Monitoring (RUM) Agent:: ≥ v4.7.0 diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index 4149039a3f87b..9c7467bb452fd 100644 --- a/docs/canvas/canvas-elements.asciidoc +++ b/docs/canvas/canvas-elements.asciidoc @@ -27,28 +27,30 @@ By default, most of the elements you create use demo data until you change the d * *Timelion* — Access your time series data using <> queries. To use Timelion queries, you can enter a query using the <>. +Each element can display a different data source. Pages and workpads often contain multiple data sources. + [float] [[canvas-add-object]] ==== Add a saved object -Add a <>, then customize it to fit your display needs. +Add <> to your workpad, such as maps and visualizations. -. Click *Embed object*. +. Click *Add element > Add from Visualize Library*. -. Select the object you want to add. +. Select the saved object you want to add. + [role="screenshot"] image::images/canvas-map-embed.gif[] . To use the customization options, click the panel menu, then select one of the following options: -* *Edit map* — Opens <> so that you can edit the original map. +* *Edit map* — Opens <> or <> so that you can edit the original saved object. -* *Customize panel* — Specifies the object title options. +* *Edit panel title* — Adds a title to the saved object. -* *Inspect* — Allows you to drill down into the element data. +* *Customize time range* — Exposes a time filter dedicated to the saved object. -* *Customize time range* — Exposes a time filter dedicated to the map. +* *Inspect* — Allows you to drill down into the element data. [float] [[canvas-add-image]] @@ -56,7 +58,7 @@ image::images/canvas-map-embed.gif[] To personalize your workpad, add your own logos and graphics. -. Click *Manage assets*. +. Click *Add element > Manage assets*. . On the *Manage workpad assets* window, drag and drop your images. @@ -83,40 +85,25 @@ Move and resize your elements to meet your design needs. [[format-canvas-elements]] ==== Format elements -Align, distribute, and reorder elements for consistency and readability across your workpad pages. - -Access the align, distribute, and reorder options by clicking the *Element options* icon. - -[role="screenshot"] -image::images/canvas_element_options.png[] +For consistency and readability across your workpad pages, align, distribute, and reorder elements. -To align elements: +To align two or more elements: . Press and hold Shift, then select the elements you want to align. -. Click the , then select *Group*. +. Click *Edit > Alignment*, then select the alignment option. -. Click the *Element options* icon, then select *Alignment*. - -. Select the alignment option. - -To distribute elements: +To distribute three or more elements: . Press and hold Shift, then select the elements you want to distribute. -. Click the *Element options* icon, then select *Group*. - -. Click the *Element options* icon, then select *Distribution*. - -. Select the distribution option. +. Click *Edit > Distribution*, then select the distribution option. To reorder elements: . Select the element you want to reorder. -. Click the *Element options* icon, then select *Order*. - -. Select the order option. +. Click *Edit > Order*, then select the order option. [float] [[data-display]] @@ -157,14 +144,14 @@ text.align: center; To use the elements across all workpads, save the elements. -When you're ready to save your element, select the element, then click the *Save as new element* icon. +When you're ready to save your element, select the element, then click *Edit > Save as new element*. [role="screenshot"] image::images/canvas_save_element.png[] -To save a group of elements, press and hold Shift, then select the elements you want to save. +To save a group of elements, press and hold Shift, select the elements you want to save, then click *Edit > Save as new element*. -To access your saved elements, click *Add element*, then select *My elements*. +To access your saved elements, click *Add element > My elements*. [float] [[delete-elements]] @@ -174,9 +161,7 @@ When you no longer need an element, delete it from your workpad. . Select the element you want to delete. -. Click the *Element options* icon. +. Click *Edit > Delete*. + [role="screenshot"] image::images/canvas_element_options.png[] - -. Select *Delete*. diff --git a/docs/canvas/canvas-present-workpad.asciidoc b/docs/canvas/canvas-present-workpad.asciidoc index 9cd4ecc9519e1..e0139ab943104 100644 --- a/docs/canvas/canvas-present-workpad.asciidoc +++ b/docs/canvas/canvas-present-workpad.asciidoc @@ -4,24 +4,20 @@ When you are ready to present your workpad, use and enable the presentation options. -[float] -[[view-fullscreen-mode]] -==== View your workpad in fullscreen mode +. Configure the autoplay options. -Click the *Enter fullscreen mode* icon. +.. From the workpad menu, click *View > Autoplay settings*. +.. Under *Change cycling interval*, select the interval you want to use, or *Set a custom interval*. ++ [role="screenshot"] -image::images/canvas-fullscreen.png[Fullscreen mode] - -[float] -[[enable-autoplay]] -==== Enable autoplay +image::images/canvas-autoplay-interval.png[Element autoplay interval] -Automatically cycle through your workpads pages in fullscreen mode. +. To enable autoplay, click *View > Turn autoplay on*. -. Click the *Control settings* icon. - -. Under *Change cycling interval*, select the interval you want to use. +. To start your presentation, click *View > Enter fullscreen mode*. + [role="screenshot"] -image::images/canvas-refresh-interval.png[Element data refresh interval] +image::images/canvas-fullscreen.png[Fullscreen mode] + +. When you are ready to exit fullscreen mode, press the Esc (Escape) key. diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 5cae3fcc7b531..a095253c6cff3 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -10,14 +10,12 @@ When you've finished your workpad, you can share it outside of {kib}. Create a JSON file of your workpad that you can export outside of {kib}. -. From your workpad, click the *Share workpad* icon. +Click *Share > Download as JSON*. -. Select *Download as JSON*. -+ [role="screenshot"] image::images/canvas-export-workpad.png[Export single workpad] -Want to export multiple workpads? Go to the *Canvas workpads* view, select the workpads you want to export, then click *Export*. +Want to export multiple workpads? Go to the *Canvas* home page, select the workpads you want to export, then click *Export*. [float] [[create-workpad-pdf]] @@ -25,69 +23,43 @@ Want to export multiple workpads? Go to the *Canvas workpads* view, select the w If you have a license that supports the {report-features}, you can create a PDF copy of your workpad that you can save and share outside {kib}. -For more information, refer to <>. - -. From your workpad, click the *Share workpad* icon, then select *PDF reports*. +Click *Share > PDF reports > Generate PDF*. -. Click *Generate PDF*. -+ [role="screenshot"] image::images/canvas-generate-pdf.gif[Generate PDF] +For more information, refer to <>. + [float] [[create-workpad-URL]] ==== Create a POST URL If you have a license that supports the {report-features}, you can create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. -For more information, refer to <>. - -. From your workpad, click the *Share workpad* icon, then select *PDF reports*. +Click *Share > PDF reports > Copy POST URL*. -. Click *Copy POST URL*. -+ [role="screenshot"] image::images/canvas-create-URL.gif[Create POST URL] +For more information, refer to <>. + [float] [[add-workpad-website]] ==== Share the workpad on a website beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. From your workpad, click the *Share this workpad* icon, then select *Share on a website*. +. Click *Share > Share on a website*. -. On the *Share on a website* pane, follow the instructions. +. Follow the *Share on a website* instructions. . To customize the workpad behavior to autoplay the pages or hide the toolbar, use the inline parameters. + To make sure that your data remains secure, the data in the JSON file is not connected to {kib}. Canvas does not display elements that manipulate the data on the workpad. + [role="screenshot"] -image::images/canvas-embed_workpad.gif[Share the workpad on a website] +image::canvas/images/canvas-embed_workpad.gif[Share the workpad on a website] + NOTE: Shareable workpads encode the current state of the workpad in a JSON file. When you make changes to the workpad, the changes do not appear in the shareable workpad on your website. -[float] -[[change-the-workpad-settings]] -==== Change the settings - -After you've added the workpad to your website, you can change the autoplay and toolbar settings. - -To change the autoplay settings: - -. Click the settings icon. - -. Click *Auto Play*, then change the settings. -+ -[role="screenshot"] -image::images/canvas_share_autoplay_480.gif[Autoplay settings] - -To change the toolbar settings: - -. Click the settings icon. - -. Click *Toolbar*, then change the settings. -+ -[role="screenshot"] -image::images/canvas_share_hidetoolbar_480.gif[Hide toolbar settings] +. To change the settings, click the settings icon, then choose the settings you want to use. diff --git a/docs/canvas/canvas-tutorial.asciidoc b/docs/canvas/canvas-tutorial.asciidoc index a38ab4a69598e..9b23817de2767 100644 --- a/docs/canvas/canvas-tutorial.asciidoc +++ b/docs/canvas/canvas-tutorial.asciidoc @@ -10,76 +10,64 @@ To get up and running with Canvas, use the following tutorial where you'll creat For this tutorial, you'll need to add the <>. [float] -=== Create and personalize your workpad +=== Create your workpad Your first step to working with Canvas is to create a workpad. -. Open *Canvas*. +. Open the menu, then click *Kibana > Canvas*. -. Click *Create workpad*. - -. To add a *Name* for your workpad, use the editor. For example, `My Canvas Workpad`. +. On the *Canvas workpads* page, click *Create workpad*. [float] === Customize your workpad with images To customize your workpad to look the way you want, add your own images. -. Click *Add element*, then click *Image*. +. Click *Add element > Image > Image*. + -The default Elastic logo image appears on your page. +The default Elastic logo image appears on the page. . To replace the Elastic logo with your own image, select the image, then use the editor. -. To move the image, click and drag it to your preferred location. - [role="screenshot"] image::images/canvas-image-element.png[] -You'll notice that the image is tagged as an asset, which allows you to reuse the image from *Manage assets*. - [float] === Customize your data with metrics Customize your data by connecting it to the Sample eCommerce orders data. -. Click *Add element*, then click *Metric*. +. Click *Add element > Chart > Metric*. + -By default, the *Metric* element is connected to a demo data source, which enables you to experiment with the element before you connect it to your own data source. - -. To connect the element to your own data source, make sure that the element is selected, then click *Data*. +By default, the element is connected to the demo data, which enables you to experiment with the element before you connect it to your own data source. -.. Click *Change your data source*, then click *Elasticsearch SQL*. +. To connect the element to your own data source, make sure that the element is selected, click *Data > Demo data > Elasticsearch SQL*. -.. In the *Elasticsearch SQL query* field, enter the following query: +.. In the *Query* field, enter the following: + `SELECT sum(taxless_total_price) AS sum_total_price FROM "kibana_sample_data_ecommerce"` -+ -The query selects the total price field and sets it to the sum_total_price field. These fields are pulled from the kibana_sample_data_ecommerce index that you installed. -.. To verify that the data is correct, click *Preview*. If you like what you see, click *Save*. +.. Click *Save*. + -At this point, the element displays an error. +The query selects the total price field and sets it to the sum_total_price field. All fields are pulled from the kibana_sample_data_ecommerce index. -. Specify how to process and display the data. +. At this point, the element appears as an error, so you need to change the element display options. .. Click *Display* -.. Under *Number*, select *Value* from the function drop-down list, then select *sum_total_price* from the column drop-down list. +.. From the *Value* drop-down lists, make sure that *Unique* is selected, then select *sum_total_price*. .. Change the *Label* to `Total sales`. -+ -You'll notice that the error is gone, but the number could use some formatting. -. To format the number, use the Canvas expression language. +. The error is gone, but the element could use some formatting. To format the number, use the Canvas expression language. .. Click *Expression editor*. + You're now looking at the raw data syntax that Canvas uses to display the element. -.. Look for `math "sum_total_price"`, then add `| formatNumber "$0a"`. +.. Change `metricFormat="0,0.[000]"` to `metricFormat="$0a"`. -.. To update the number, click *Run*. +.. Click *Run*. [role="screenshot"] image::images/canvas-metric-element.png[] @@ -89,21 +77,17 @@ image::images/canvas-metric-element.png[] To show what your data can do, add charts, graphs, progress monitors, and more to your workpad. -. Click *Add element*, then click *Area chart*. +. Click *Add element > Chart > Area*. -. To connect the element to your own data source, make sure that the element is selected, then click *Data*. +. Make sure that the element is selected, then click *Data > Demo data > Elasticsearch SQL*. -.. Click *Change your data source*, then click *Elasticsearch SQL*. - -.. To obtain the taxless total price by date, enter the following into the *Elasticsearch SQL query* field: +.. To obtain the taxless total price by date, enter the following in the *Query* field: + `SELECT order_date, taxless_total_price FROM "kibana_sample_data_ecommerce" ORDER BY order_date` -+ -Although you used the Elasticsearch SQL data source for the metric and area chart elements, each element can display a different data source. Pages and workpads often contain multiple data sources. -.. To verify that the data is correct, click *Preview*. If you like what you see, click *Save*. +.. Click *Save*. -. Specify how to display the data. +. Change the display options. .. Click *Display* @@ -117,34 +101,20 @@ image::images/canvas-chart-element.png[] [float] === Show how your data changes over time -To focus your data on a specific time range, add a time filter to your workpad. +To focus your data on a specific time range, add the time filter. -. Click *Add element*, then click *Time filter*. +. Click *Add element > Filter > Time filter*. -. Specify how to display the data. +. Click *Display* -.. Click *Display* - -.. To use the date time field from the sample data, enter `order_date` in the *Column* field, then click *Set*. +. To use the date time field from the sample data, enter `order_date` in the *Column* field, then click *Set*. [role="screenshot"] image::images/canvas-timefilter-element.png[] -To see how the data changes, set the time filter to *Last 7 days*. As you change the time filter options, the metrics dynamically update. - -Your workpad is now complete! From the workpad menu, use the icons to: - -* Configure the refresh rate for your data - -* Refresh the data that displays on your workpad - -* Display your workpad in fullscreen mode - -* Control the zoom options - -* Share your workpad +To see how the data changes, set the time filter to *Last 7 days*. As you change the time filter options, the elements automatically update. -* Hide the editing controls +Your workpad is now complete! [float] === Next steps diff --git a/docs/canvas/canvas-workpad.asciidoc b/docs/canvas/canvas-workpad.asciidoc index 42eedf55c404d..ac2d348920114 100644 --- a/docs/canvas/canvas-workpad.asciidoc +++ b/docs/canvas/canvas-workpad.asciidoc @@ -20,9 +20,7 @@ To create a workpad, choose one of the following options: To use the background colors, images, and data of your choice, start with a blank workpad. -. Open *Canvas*. - -. On the *Canvas workpads* view, click *Create workpad*. +. On the *Canvas workpads* page, click *Create workpad*. . Add a *Name* to your workpad. @@ -35,7 +33,7 @@ For example, click *720p* for a traditional presentation layout. . Click the *Background color* picker, then select the background color for your workpad. + [role="screenshot"] -image::images/canvas-background-color-picker.gif[Canvas color picker] +image::images/canvas-background-color-picker.png[Canvas color picker] [float] [[canvas-template-workpad]] @@ -43,9 +41,7 @@ image::images/canvas-background-color-picker.gif[Canvas color picker] If you're unsure about where to start, you can use one of the preconfigured templates that come with Canvas. -. Open *Canvas*. - -. On the *Canvas workpads* view, select *Templates*. +. On the *Canvas workpads* page, select *Templates*. . Click the preconfigured template that you want to use. @@ -57,9 +53,7 @@ If you're unsure about where to start, you can use one of the preconfigured temp When you want to use a workpad that someone else has already started, import the JSON file into Canvas. -. Open *Canvas*. - -. On the *Canvas workpads* view, click and drag the file to the *Import workpad JSON file* field. +To import a workpad, go to the *Canvas workpads* page, then click and drag the file to the *Import workpad JSON file* field. [float] [[sample-data-workpad]] @@ -96,23 +90,27 @@ background-color: #3990e6; [[configure-auto-refresh-interval]] === Change the auto-refresh interval -Increase or decrease how often the data refreshes on your workpad. +Change how often the data refreshes on your workpad. -. In the top left corner, click the *Control settings* icon. +. Click *View > Auto refresh settings*. -. Under *Change auto-refresh interval*, select the interval you want to use. +. Select the interval you want to use, or *Set a custom interval*. + [role="screenshot"] image::images/canvas-refresh-interval.png[Element data refresh interval] - -TIP: To manually refresh the data, click the *Refresh data* icon. ++ +To manually refresh the data, click image:canvas/images/canvas-refresh-data.png[]. [float] [[zoom-in-out]] === Use the zoom options -In the upper left corner, click the *Zoom controls* icon, then select one of the options. +To get a closer look at a portion of your workpad, use the zoom options. + +. Click *View > Zoom*. +. Select the zoom option. ++ [role="screenshot"] image::images/canvas-zoom-controls.png[Zoom controls] diff --git a/docs/canvas/images/canvas-embed_workpad.gif b/docs/canvas/images/canvas-embed_workpad.gif new file mode 100644 index 0000000000000..1cda5b572acef Binary files /dev/null and b/docs/canvas/images/canvas-embed_workpad.gif differ diff --git a/docs/canvas/images/canvas-refresh-data.png b/docs/canvas/images/canvas-refresh-data.png new file mode 100644 index 0000000000000..7a71686f04491 Binary files /dev/null and b/docs/canvas/images/canvas-refresh-data.png differ diff --git a/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md b/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md index a7e92f310a62e..02c000e88f31d 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md +++ b/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md @@ -4,7 +4,7 @@ ## AppCategory.label property -Label used for cateogry name. Also used as aria-label if one isn't set. +Label used for category name. Also used as aria-label if one isn't set. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.appcategory.md b/docs/development/core/public/kibana-plugin-core-public.appcategory.md index d91727a1bbf29..b0ec377e165b6 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appcategory.md +++ b/docs/development/core/public/kibana-plugin-core-public.appcategory.md @@ -19,6 +19,6 @@ export interface AppCategory | [ariaLabel](./kibana-plugin-core-public.appcategory.arialabel.md) | string | If the visual label isn't appropriate for screen readers, can override it here | | [euiIconType](./kibana-plugin-core-public.appcategory.euiicontype.md) | string | Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined | | [id](./kibana-plugin-core-public.appcategory.id.md) | string | Unique identifier for the categories | -| [label](./kibana-plugin-core-public.appcategory.label.md) | string | Label used for cateogry name. Also used as aria-label if one isn't set. | +| [label](./kibana-plugin-core-public.appcategory.label.md) | string | Label used for category name. Also used as aria-label if one isn't set. | | [order](./kibana-plugin-core-public.appcategory.order.md) | number | The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) | diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md new file mode 100644 index 0000000000000..d428faa500faf --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) > [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) + +## ApplicationStart.applications$ property + +Observable emitting the list of currently registered apps and their associated status. + +Signature: + +```typescript +applications$: Observable>; +``` + +## Remarks + +Applications disabled by [Capabilities](./kibana-plugin-core-public.capabilities.md) will not be present in the map. Applications manually disabled from the client-side using an [application updater](./kibana-plugin-core-public.appupdater.md) are present, with their status properly set as `inaccessible`. + diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md index f36351c8b8f06..055ad9f37e654 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md @@ -6,7 +6,7 @@ Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the `absolute` option to generate an absolute url (http://host:port/basePath/app/my-app) -Note that when generating absolute urls, the protocol, host and port are determined from the browser location. +Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's location. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md index a93bc61bac527..896de2de32dd5 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md @@ -15,6 +15,7 @@ export interface ApplicationStart | Property | Type | Description | | --- | --- | --- | +| [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) | Observable<ReadonlyMap<string, PublicAppInfo | PublicLegacyAppInfo>> | Observable emitting the list of currently registered apps and their associated status. | | [capabilities](./kibana-plugin-core-public.applicationstart.capabilities.md) | RecursiveReadonly<Capabilities> | Gets the read-only capabilities. | | [currentAppId$](./kibana-plugin-core-public.applicationstart.currentappid_.md) | Observable<string | undefined> | An observable that emits the current application id and each subsequent id update. | @@ -22,7 +23,8 @@ export interface ApplicationStart | Method | Description | | --- | --- | -| [getUrlForApp(appId, options)](./kibana-plugin-core-public.applicationstart.geturlforapp.md) | Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the absolute option to generate an absolute url (http://host:port/basePath/app/my-app)Note that when generating absolute urls, the protocol, host and port are determined from the browser location. | +| [getUrlForApp(appId, options)](./kibana-plugin-core-public.applicationstart.geturlforapp.md) | Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the absolute option to generate an absolute url (http://host:port/basePath/app/my-app)Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's location. | | [navigateToApp(appId, options)](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | Navigate to a given app | +| [navigateToUrl(url)](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | Navigate to given url, which can either be an absolute url or a relative path, in a SPA friendly way when possible.If all these criteria are true for the given url: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space) - The pathname segment after the basePath matches any known application route (eg. /app// or any application's appRoute configuration)Then a SPA navigation will be performed using navigateToApp using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using window.location.assign | | [registerMountContext(contextName, provider)](./kibana-plugin-core-public.applicationstart.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md). | diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.navigatetourl.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.navigatetourl.md new file mode 100644 index 0000000000000..86b86776b0b12 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.navigatetourl.md @@ -0,0 +1,45 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) > [navigateToUrl](./kibana-plugin-core-public.applicationstart.navigatetourl.md) + +## ApplicationStart.navigateToUrl() method + +Navigate to given url, which can either be an absolute url or a relative path, in a SPA friendly way when possible. + +If all these criteria are true for the given url: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space) - The pathname segment after the basePath matches any known application route (eg. /app// or any application's `appRoute` configuration) + +Then a SPA navigation will be performed using `navigateToApp` using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using `window.location.assign` + +Signature: + +```typescript +navigateToUrl(url: string): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| url | string | an absolute url, or a relative path, to navigate to. | + +Returns: + +`Promise` + +## Example + + +```ts +// current url: `https://kibana:8080/base-path/s/my-space/app/dashboard` + +// will call `application.navigateToApp('discover', { path: '/some-path?foo=bar'})` +application.navigateToUrl('https://kibana:8080/base-path/s/my-space/app/discover/some-path?foo=bar') +application.navigateToUrl('/base-path/s/my-space/app/discover/some-path?foo=bar') + +// will perform a full page reload using `window.location.assign` +application.navigateToUrl('https://elsewhere:8080/base-path/s/my-space/app/discover/some-path') // origin does not match +application.navigateToUrl('/app/discover/some-path') // does not include the current basePath +application.navigateToUrl('/base-path/s/my-space/app/unknown-app/some-path') // unknown application + +``` + diff --git a/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md index 7f72d6a52fc2a..e898126a553e2 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md +++ b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md @@ -23,10 +23,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route } from 'react-router-dom'; -import { CoreStart, AppMountParams } from 'src/core/public'; +import { CoreStart, AppMountParameters } from 'src/core/public'; import { MyPluginDepsStart } from './plugin'; -export renderApp = ({ element, history, onAppLeave }: AppMountParams) => { +export renderApp = ({ element, history, onAppLeave }: AppMountParameters) => { const { renderApp, hasUnsavedChanges } = await import('./application'); onAppLeave(actions => { if(hasUnsavedChanges()) { diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md index fe95cb38cd97c..e30e8262f40b2 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md @@ -4,7 +4,7 @@ ## ChromeNavLink.euiIconType property -A EUI iconType that will be used for the app's icon. This icon takes precendence over the `icon` property. +A EUI iconType that will be used for the app's icon. This icon takes precedence over the `icon` property. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md new file mode 100644 index 0000000000000..a8af0c997ca78 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavLink](./kibana-plugin-core-public.chromenavlink.md) > [href](./kibana-plugin-core-public.chromenavlink.href.md) + +## ChromeNavLink.href property + +Settled state between `url`, `baseUrl`, and `active` + +Signature: + +```typescript +readonly href?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md index a9fabb38df869..0349e865bff97 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md @@ -20,8 +20,9 @@ export interface ChromeNavLink | [category](./kibana-plugin-core-public.chromenavlink.category.md) | AppCategory | The category the app lives in | | [disabled](./kibana-plugin-core-public.chromenavlink.disabled.md) | boolean | Disables a link from being clickable. | | [disableSubUrlTracking](./kibana-plugin-core-public.chromenavlink.disablesuburltracking.md) | boolean | A flag that tells legacy chrome to ignore the link when tracking sub-urls | -| [euiIconType](./kibana-plugin-core-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | +| [euiIconType](./kibana-plugin-core-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precedence over the icon property. | | [hidden](./kibana-plugin-core-public.chromenavlink.hidden.md) | boolean | Hides a link from the navigation. | +| [href](./kibana-plugin-core-public.chromenavlink.href.md) | string | Settled state between url, baseUrl, and active | | [icon](./kibana-plugin-core-public.chromenavlink.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | | [id](./kibana-plugin-core-public.chromenavlink.id.md) | string | A unique identifier for looking up links. | | [linkToLastSubUrl](./kibana-plugin-core-public.chromenavlink.linktolastsuburl.md) | boolean | Whether or not the subUrl feature should be enabled. | diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md index 7f6dc7e0d5640..bd5a1399cded7 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type ChromeNavLinkUpdateableFields = Partial>; +export declare type ChromeNavLinkUpdateableFields = Partial>; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md new file mode 100644 index 0000000000000..292bf29962839 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) + +## LegacyApp.appUrl property + +Signature: + +```typescript +appUrl: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md new file mode 100644 index 0000000000000..af4d0eb7969d3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) + +## LegacyApp.disableSubUrlTracking property + +Signature: + +```typescript +disableSubUrlTracking?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md new file mode 100644 index 0000000000000..fa1314b74fd83 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) + +## LegacyApp.linkToLastSubUrl property + +Signature: + +```typescript +linkToLastSubUrl?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.md new file mode 100644 index 0000000000000..06533aaa99170 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) + +## LegacyApp interface + + +Signature: + +```typescript +export interface LegacyApp extends AppBase +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) | string | | +| [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) | boolean | | +| [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) | boolean | | +| [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) | string | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md new file mode 100644 index 0000000000000..44a1e52ccd244 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) + +## LegacyApp.subUrlBase property + +Signature: + +```typescript +subUrlBase?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index b2524ec48c757..9e4afe0f5133c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -90,6 +90,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | | [ImageValidation](./kibana-plugin-core-public.imagevalidation.md) | | | [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | +| [LegacyApp](./kibana-plugin-core-public.legacyapp.md) | | | [LegacyCoreSetup](./kibana-plugin-core-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyCoreStart](./kibana-plugin-core-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) | | @@ -162,6 +163,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [NavType](./kibana-plugin-core-public.navtype.md) | | | [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | | +| [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) | +| [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) | Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | | [RecursiveReadonly](./kibana-plugin-core-public.recursivereadonly.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md new file mode 100644 index 0000000000000..c70f3a97a8882 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) + +## PublicAppInfo type + +Public information about a registered [application](./kibana-plugin-core-public.app.md) + +Signature: + +```typescript +export declare type PublicAppInfo = Omit & { + legacy: false; +}; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md new file mode 100644 index 0000000000000..cc3e9de3193cb --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) + +## PublicLegacyAppInfo type + +Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) + +Signature: + +```typescript +export declare type PublicLegacyAppInfo = Omit & { + legacy: true; +}; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.adminclient.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.adminclient.md deleted file mode 100644 index 3fcb855586129..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.adminclient.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) > [adminClient](./kibana-plugin-core-server.elasticsearchservicesetup.adminclient.md) - -## ElasticsearchServiceSetup.adminClient property - -> Warning: This API is now obsolete. -> -> Use [ElasticsearchServiceStart.legacy.client](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) instead. -> -> A client for the `admin` cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). -> - -Signature: - -```typescript -readonly adminClient: IClusterClient; -``` - -## Example - - -```js -const client = core.elasticsearch.adminClient; - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.createclient.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.createclient.md deleted file mode 100644 index 75bf6c6aa461b..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.createclient.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) > [createClient](./kibana-plugin-core-server.elasticsearchservicesetup.createclient.md) - -## ElasticsearchServiceSetup.createClient property - -> Warning: This API is now obsolete. -> -> Use [ElasticsearchServiceStart.legacy.createClient](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) instead. -> -> Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). -> - -Signature: - -```typescript -readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; -``` - -## Example - - -```js -const client = elasticsearch.createCluster('my-app-name', config); -const data = await client.callAsInternalUser(); - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.dataclient.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.dataclient.md deleted file mode 100644 index 867cafa957f42..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.dataclient.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) > [dataClient](./kibana-plugin-core-server.elasticsearchservicesetup.dataclient.md) - -## ElasticsearchServiceSetup.dataClient property - -> Warning: This API is now obsolete. -> -> Use [ElasticsearchServiceStart.legacy.client](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) instead. -> -> A client for the `data` cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). -> - -Signature: - -```typescript -readonly dataClient: IClusterClient; -``` - -## Example - - -```js -const client = core.elasticsearch.dataClient; - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md new file mode 100644 index 0000000000000..e8c4c63dc6a96 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) > [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) + +## ElasticsearchServiceSetup.legacy property + +> Warning: This API is now obsolete. +> +> Use [ElasticsearchServiceStart.legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) instead. +> + +Signature: + +```typescript +legacy: { + readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; + readonly client: IClusterClient; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md index ee56f8b4a6284..c1e23527e9516 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md @@ -15,7 +15,5 @@ export interface ElasticsearchServiceSetup | Property | Type | Description | | --- | --- | --- | -| [adminClient](./kibana-plugin-core-server.elasticsearchservicesetup.adminclient.md) | IClusterClient | | -| [createClient](./kibana-plugin-core-server.elasticsearchservicesetup.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient | | -| [dataClient](./kibana-plugin-core-server.elasticsearchservicesetup.dataclient.md) | IClusterClient | | +| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient;
readonly client: IClusterClient;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md index 08765aaf93d3d..667a36091f232 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md @@ -4,6 +4,11 @@ ## ElasticsearchServiceStart.legacy property +> Warning: This API is now obsolete. +> +> Provided for the backward compatibility. Switch to the new elasticsearch client as soon as https://github.com/elastic/kibana/issues/35508 done. +> + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md index 8437f86d2d48e..8958b49d98b0c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md @@ -21,7 +21,7 @@ registerRouteHandlerContext: (contextName 'myApp', (context, req) => { async function search (id: string) { - return await context.elasticsearch.adminClient.callAsInternalUser('endpoint', id); + return await context.elasticsearch.legacy.client.callAsInternalUser('endpoint', id); } return { search }; } diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 14e01fda3d287..147a72016b235 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -125,7 +125,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginConfigDescriptor](./kibana-plugin-core-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | | [PluginInitializerContext](./kibana-plugin-core-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | | [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | -| [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.dataClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | +| [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.legacy.client](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | | [RouteConfig](./kibana-plugin-core-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-core-server.routeconfigoptions.md) | Additional route options. | | [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md) | Additional body options for a route | diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md index 0d640e52c3a03..7b887d6d421e4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md @@ -13,8 +13,9 @@ core: { typeRegistry: ISavedObjectTypeRegistry; }; elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; + legacy: { + client: IScopedClusterClient; + }; }; uiSettings: { client: IUiSettingsClient; diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md index 0966b91a4ebf2..99be0676bcda3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md @@ -6,7 +6,7 @@ Plugin specific context passed to a route handler. -Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.dataClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request +Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.legacy.client](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request Signature: @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
} | | +| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
legacy: {
client: IScopedClusterClient;
};
};
uiSettings: {
client: IUiSettingsClient;
};
} | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index a62cee7b654fe..1d3cfa9305c18 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -31,6 +31,7 @@ export declare class Field implements IFieldType | [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) | | IndexPattern | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | +| [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) | | boolean | | | [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | | | [scripted](./kibana-plugin-plugins-data-public.indexpatternfield.scripted.md) | | boolean | | | [searchable](./kibana-plugin-plugins-data-public.indexpatternfield.searchable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md new file mode 100644 index 0000000000000..4b012c26a8620 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) + +## IndexPatternField.readFromDocValues property + +Signature: + +```typescript +readFromDocValues?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 02cc34baf7c45..75d3abefc74b9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -114,6 +114,7 @@ | [SearchBar](./kibana-plugin-plugins-data-public.searchbar.md) | | | [SYNC\_SEARCH\_STRATEGY](./kibana-plugin-plugins-data-public.sync_search_strategy.md) | | | [syncQueryStateWithUrl](./kibana-plugin-plugins-data-public.syncquerystatewithurl.md) | Helper to setup syncing of global data with the URL | +| [UI\_SETTINGS](./kibana-plugin-plugins-data-public.ui_settings.md) | | ## Type Aliases diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin._constructor_.md index 5ec2d491295bf..64108a7c7be33 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin._constructor_.md @@ -9,12 +9,12 @@ Constructs a new instance of the `DataPublicPlugin` class Signature: ```typescript -constructor(initializerContext: PluginInitializerContext); +constructor(initializerContext: PluginInitializerContext); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| initializerContext | PluginInitializerContext | | +| initializerContext | PluginInitializerContext<ConfigSchema> | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.md index 6cbc1f441c048..0dad92a0a27ca 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.md @@ -7,14 +7,14 @@ Signature: ```typescript -export declare function plugin(initializerContext: PluginInitializerContext): DataPublicPlugin; +export declare function plugin(initializerContext: PluginInitializerContext): DataPublicPlugin; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| initializerContext | PluginInitializerContext | | +| initializerContext | PluginInitializerContext<ConfigSchema> | | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index 58690300b3bd6..85eb4825bc2e3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index b015ebfcbaada..fc141b8c89c18 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md new file mode 100644 index 0000000000000..a48f4920b3d26 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md @@ -0,0 +1,39 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [UI\_SETTINGS](./kibana-plugin-plugins-data-public.ui_settings.md) + +## UI\_SETTINGS variable + +Signature: + +```typescript +UI_SETTINGS: { + META_FIELDS: string; + DOC_HIGHLIGHT: string; + QUERY_STRING_OPTIONS: string; + QUERY_ALLOW_LEADING_WILDCARDS: string; + SEARCH_QUERY_LANGUAGE: string; + SORT_OPTIONS: string; + COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; + COURIER_SET_REQUEST_PREFERENCE: string; + COURIER_CUSTOM_REQUEST_PREFERENCE: string; + COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; + COURIER_BATCH_SEARCHES: string; + SEARCH_INCLUDE_FROZEN: string; + HISTOGRAM_BAR_TARGET: string; + HISTOGRAM_MAX_BARS: string; + HISTORY_LIMIT: string; + SHORT_DOTS_ENABLE: string; + FORMAT_DEFAULT_TYPE_MAP: string; + FORMAT_NUMBER_DEFAULT_PATTERN: string; + FORMAT_PERCENT_DEFAULT_PATTERN: string; + FORMAT_BYTES_DEFAULT_PATTERN: string; + FORMAT_CURRENCY_DEFAULT_PATTERN: string; + FORMAT_NUMBER_DEFAULT_LOCALE: string; + TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; + TIMEPICKER_QUICK_RANGES: string; + INDEXPATTERN_PLACEHOLDER: string; + FILTERS_PINNED_BY_DEFAULT: string; + FILTERS_EDITOR_SUGGEST_VALUES: string; +} +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.config.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.config.md new file mode 100644 index 0000000000000..49b5f6040fc84 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.config.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [config](./kibana-plugin-plugins-data-server.config.md) + +## config variable + +Signature: + +```typescript +config: PluginConfigDescriptor +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index e756eb9b72905..0efbe8ed4ed64 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -55,12 +55,14 @@ | Variable | Description | | --- | --- | | [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-server.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string | +| [config](./kibana-plugin-plugins-data-server.config.md) | | | [esFilters](./kibana-plugin-plugins-data-server.esfilters.md) | | | [esKuery](./kibana-plugin-plugins-data-server.eskuery.md) | | | [esQuery](./kibana-plugin-plugins-data-server.esquery.md) | | | [fieldFormats](./kibana-plugin-plugins-data-server.fieldformats.md) | | | [indexPatterns](./kibana-plugin-plugins-data-server.indexpatterns.md) | | | [search](./kibana-plugin-plugins-data-server.search.md) | | +| [UI\_SETTINGS](./kibana-plugin-plugins-data-server.ui_settings.md) | | ## Type Aliases diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin._constructor_.md index 454d8a059a252..4a0a159310b9d 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin._constructor_.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin._constructor_.md @@ -9,12 +9,12 @@ Constructs a new instance of the `DataServerPlugin` class Signature: ```typescript -constructor(initializerContext: PluginInitializerContext); +constructor(initializerContext: PluginInitializerContext); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| initializerContext | PluginInitializerContext | | +| initializerContext | PluginInitializerContext<ConfigSchema> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.md index b3ba75ce29ab6..1773871d946a2 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.md @@ -9,14 +9,14 @@ Static code to be shared externally Signature: ```typescript -export declare function plugin(initializerContext: PluginInitializerContext): DataServerPlugin; +export declare function plugin(initializerContext: PluginInitializerContext): DataServerPlugin; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| initializerContext | PluginInitializerContext | | +| initializerContext | PluginInitializerContext<ConfigSchema> | | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md new file mode 100644 index 0000000000000..855cfd11d00ea --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md @@ -0,0 +1,39 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [UI\_SETTINGS](./kibana-plugin-plugins-data-server.ui_settings.md) + +## UI\_SETTINGS variable + +Signature: + +```typescript +UI_SETTINGS: { + META_FIELDS: string; + DOC_HIGHLIGHT: string; + QUERY_STRING_OPTIONS: string; + QUERY_ALLOW_LEADING_WILDCARDS: string; + SEARCH_QUERY_LANGUAGE: string; + SORT_OPTIONS: string; + COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; + COURIER_SET_REQUEST_PREFERENCE: string; + COURIER_CUSTOM_REQUEST_PREFERENCE: string; + COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; + COURIER_BATCH_SEARCHES: string; + SEARCH_INCLUDE_FROZEN: string; + HISTOGRAM_BAR_TARGET: string; + HISTOGRAM_MAX_BARS: string; + HISTORY_LIMIT: string; + SHORT_DOTS_ENABLE: string; + FORMAT_DEFAULT_TYPE_MAP: string; + FORMAT_NUMBER_DEFAULT_PATTERN: string; + FORMAT_PERCENT_DEFAULT_PATTERN: string; + FORMAT_BYTES_DEFAULT_PATTERN: string; + FORMAT_CURRENCY_DEFAULT_PATTERN: string; + FORMAT_NUMBER_DEFAULT_LOCALE: string; + TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; + TIMEPICKER_QUICK_RANGES: string; + INDEXPATTERN_PLACEHOLDER: string; + FILTERS_PINNED_BY_DEFAULT: string; + FILTERS_EDITOR_SUGGEST_VALUES: string; +} +``` diff --git a/docs/images/canvas-add-image.gif b/docs/images/canvas-add-image.gif index a2263e22c4c49..994ec6e1b4f28 100644 Binary files a/docs/images/canvas-add-image.gif and b/docs/images/canvas-add-image.gif differ diff --git a/docs/images/canvas-add-pages.gif b/docs/images/canvas-add-pages.gif index a1fa228645836..c6e09d6f386ae 100644 Binary files a/docs/images/canvas-add-pages.gif and b/docs/images/canvas-add-pages.gif differ diff --git a/docs/images/canvas-autoplay-interval.png b/docs/images/canvas-autoplay-interval.png new file mode 100644 index 0000000000000..68a7ca248d9ee Binary files /dev/null and b/docs/images/canvas-autoplay-interval.png differ diff --git a/docs/images/canvas-background-color-picker.png b/docs/images/canvas-background-color-picker.png new file mode 100644 index 0000000000000..ec38b5c1c5f7e Binary files /dev/null and b/docs/images/canvas-background-color-picker.png differ diff --git a/docs/images/canvas-chart-element.png b/docs/images/canvas-chart-element.png index d0aa7db375a40..bf5e04bf89af5 100644 Binary files a/docs/images/canvas-chart-element.png and b/docs/images/canvas-chart-element.png differ diff --git a/docs/images/canvas-create-URL.gif b/docs/images/canvas-create-URL.gif index 0c9fbf7201d80..11327224fc897 100644 Binary files a/docs/images/canvas-create-URL.gif and b/docs/images/canvas-create-URL.gif differ diff --git a/docs/images/canvas-element-select.gif b/docs/images/canvas-element-select.gif index bd0e49377262e..1bfd1132f25c7 100644 Binary files a/docs/images/canvas-element-select.gif and b/docs/images/canvas-element-select.gif differ diff --git a/docs/images/canvas-export-workpad.png b/docs/images/canvas-export-workpad.png index fa910daf948d7..213bbaa5a26d3 100644 Binary files a/docs/images/canvas-export-workpad.png and b/docs/images/canvas-export-workpad.png differ diff --git a/docs/images/canvas-fullscreen.png b/docs/images/canvas-fullscreen.png index 7e6ec6ad7e7a8..b8a816d290396 100644 Binary files a/docs/images/canvas-fullscreen.png and b/docs/images/canvas-fullscreen.png differ diff --git a/docs/images/canvas-generate-pdf.gif b/docs/images/canvas-generate-pdf.gif index 9ef16dc1e5017..513f6b3b960f9 100644 Binary files a/docs/images/canvas-generate-pdf.gif and b/docs/images/canvas-generate-pdf.gif differ diff --git a/docs/images/canvas-image-element.png b/docs/images/canvas-image-element.png index f869ccb344a46..13c9090e77c76 100644 Binary files a/docs/images/canvas-image-element.png and b/docs/images/canvas-image-element.png differ diff --git a/docs/images/canvas-map-embed.gif b/docs/images/canvas-map-embed.gif index 59ef97e0ceae8..c6ba5c29df42a 100644 Binary files a/docs/images/canvas-map-embed.gif and b/docs/images/canvas-map-embed.gif differ diff --git a/docs/images/canvas-metric-element.png b/docs/images/canvas-metric-element.png index d9735a2fb3e91..03871dcc81862 100644 Binary files a/docs/images/canvas-metric-element.png and b/docs/images/canvas-metric-element.png differ diff --git a/docs/images/canvas-refresh-interval.png b/docs/images/canvas-refresh-interval.png index 99006a5b8f12d..62e88ad4bf7d0 100644 Binary files a/docs/images/canvas-refresh-interval.png and b/docs/images/canvas-refresh-interval.png differ diff --git a/docs/images/canvas-timefilter-element.png b/docs/images/canvas-timefilter-element.png index 8b8356ff5f7ea..e210b0b3288c6 100644 Binary files a/docs/images/canvas-timefilter-element.png and b/docs/images/canvas-timefilter-element.png differ diff --git a/docs/images/canvas-zoom-controls.png b/docs/images/canvas-zoom-controls.png index 892721b627027..5c72d118041e4 100644 Binary files a/docs/images/canvas-zoom-controls.png and b/docs/images/canvas-zoom-controls.png differ diff --git a/docs/images/canvas_element_options.png b/docs/images/canvas_element_options.png index 191348d919b50..41457bab4ff36 100644 Binary files a/docs/images/canvas_element_options.png and b/docs/images/canvas_element_options.png differ diff --git a/docs/images/canvas_save_element.png b/docs/images/canvas_save_element.png index a63f5135f2a0e..8a601efab874a 100644 Binary files a/docs/images/canvas_save_element.png and b/docs/images/canvas_save_element.png differ diff --git a/docs/images/lens_viz_types.png b/docs/images/lens_viz_types.png new file mode 100644 index 0000000000000..fb3961ad8bb28 Binary files /dev/null and b/docs/images/lens_viz_types.png differ diff --git a/docs/images/management-create-rollup-bar-chart.png b/docs/images/management-create-rollup-bar-chart.png new file mode 100644 index 0000000000000..324cfcb9ee5fb Binary files /dev/null and b/docs/images/management-create-rollup-bar-chart.png differ diff --git a/docs/images/management-rollup-index-pattern.png b/docs/images/management-rollup-index-pattern.png new file mode 100644 index 0000000000000..57ac00be7977c Binary files /dev/null and b/docs/images/management-rollup-index-pattern.png differ diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 5474772ab7da8..add91600a34ea 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -3,11 +3,11 @@ :include-xpack: true :lang: en -:kib-repo-dir: {docdir} +:kib-repo-dir: {kibana-root}/docs :blog-ref: https://www.elastic.co/blog/ :wikipedia: https://en.wikipedia.org/wiki -include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] +include::{docs-root}/shared/versions/stack/{source_branch}.asciidoc[] :docker-repo: docker.elastic.co/kibana/kibana :docker-image: docker.elastic.co/kibana/kibana:{version} @@ -18,7 +18,7 @@ include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] :blob: {repo}blob/{branch}/ :security-ref: https://www.elastic.co/community/security/ -include::{asciidoc-dir}/../../shared/attributes.asciidoc[] +include::{docs-root}/shared/attributes.asciidoc[] include::user/index.asciidoc[] diff --git a/docs/infrastructure/infra-ui.asciidoc b/docs/infrastructure/infra-ui.asciidoc index 120a22541717c..96550b4ed5758 100644 --- a/docs/infrastructure/infra-ui.asciidoc +++ b/docs/infrastructure/infra-ui.asciidoc @@ -109,5 +109,5 @@ Depending on the features you have installed and configured, you may also be abl * Select *View APM* to <> in the *APM* app. -* Select *View Uptime* to <> in the *Uptime* app. +* Select *View Uptime* to {uptime-guide}/uptime-app-overview.html[view uptime information] in the *Uptime* app. diff --git a/docs/ingest_manager/index.asciidoc b/docs/ingest_manager/index.asciidoc index 866935d1fa580..1728309f3dfd9 100644 --- a/docs/ingest_manager/index.asciidoc +++ b/docs/ingest_manager/index.asciidoc @@ -110,12 +110,12 @@ fetched by this input should be processed and which Data Stream to send it to. Ingest Management enforces an indexing strategy to allow the system to automatically detect indices and run queries on it. In short the indexing strategy looks as following: ``` -{type}-{dataset}-{namespace} +{dataset.type}-{dataset.name}-{dataset.namespace} ``` -The `{type}` can be `logs` or `metrics`. The `{namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. If there is a dataset or a namespace with a `-` inside, it is recommended to replace it either by a `.` or a `_`. +The `{dataset.type}` can be `logs` or `metrics`. The `{dataset.namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. If there is a dataset or a namespace with a `-` inside, it is recommended to replace it either by a `.` or a `_`. -Note: More `{type}`s might be added in the future like `apm` and `endpoint`. +Note: More `{dataset.type}`s might be added in the future like `traces`. This indexing strategy has a few advantages: @@ -133,7 +133,7 @@ Overall it creates smaller indices in size, makes querying more efficient and al The ingest pipelines for a specific dataset will have the following naming scheme: ``` -{type}-{dataset}-{package.version} +{dataset.type}-{dataset.name}-{package.version} ``` As an example, the ingest pipeline for the Nginx access logs is called `logs-nginx.access-3.4.1`. The same ingest pipeline is used for all namespaces. It is possible that a dataset has multiple ingest pipelines in which case a suffix is added to the name. @@ -151,7 +151,7 @@ Each type template contains an ILM policy. Modifying this default ILM policy wil The templates for a dataset are called as following: ``` -{type}-{dataset} +{dataset.type}-{dataset.name} ``` The pattern used inside the index template is `{type}-{dataset}-*` to match all namespaces. diff --git a/docs/logs/using.asciidoc b/docs/logs/using.asciidoc index 4f5992f945c7a..eb3025f88ce1b 100644 --- a/docs/logs/using.asciidoc +++ b/docs/logs/using.asciidoc @@ -96,5 +96,5 @@ When the machine learning anomaly detection features are enabled, click *Log rat To see other actions related to the event, click *Actions* in the log event details. Depending on the event and the features you have configured, you may also be able to: -* Select *View status in Uptime* to <> in the *Uptime* app. +* Select *View status in Uptime* to {uptime-guide}/uptime-app-overview.html[view related uptime information] in the *Uptime* app. * Select *View in APM* to <> in the *APM* app. diff --git a/docs/management/ingest-pipelines/images/ingest-pipeline-list.png b/docs/management/ingest-pipelines/images/ingest-pipeline-list.png new file mode 100755 index 0000000000000..5080b4e0bd477 Binary files /dev/null and b/docs/management/ingest-pipelines/images/ingest-pipeline-list.png differ diff --git a/docs/management/ingest-pipelines/images/ingest-pipeline-privileges.png b/docs/management/ingest-pipelines/images/ingest-pipeline-privileges.png new file mode 100755 index 0000000000000..ad9451e02e2ea Binary files /dev/null and b/docs/management/ingest-pipelines/images/ingest-pipeline-privileges.png differ diff --git a/docs/management/ingest-pipelines/images/ingest-pipeline-processor.png b/docs/management/ingest-pipelines/images/ingest-pipeline-processor.png new file mode 100755 index 0000000000000..8d8b8aa4b42e3 Binary files /dev/null and b/docs/management/ingest-pipelines/images/ingest-pipeline-processor.png differ diff --git a/docs/management/ingest-pipelines/ingest-pipelines.asciidoc b/docs/management/ingest-pipelines/ingest-pipelines.asciidoc new file mode 100644 index 0000000000000..8c259dae256d4 --- /dev/null +++ b/docs/management/ingest-pipelines/ingest-pipelines.asciidoc @@ -0,0 +1,144 @@ +[role="xpack"] +[[ingest-node-pipelines]] +== Ingest Node Pipelines + +*Ingest Node Pipelines* enables you to create and manage {es} +pipelines that perform common transformations and +enrichments on your data. For example, you might remove a field, +rename an existing field, or set a new field. + +You’ll find *Ingest Node Pipelines* in *Management > Elasticsearch*. With this feature, you can: + +* View a list of your pipelines and drill down into details. +* Create a pipeline that defines a series of tasks, known as processors. +* Test a pipeline before feeding it with real data to ensure the pipeline works as expected. +* Delete a pipeline that is no longer needed. + +[role="screenshot"] +image:management/ingest-pipelines/images/ingest-pipeline-list.png["Ingest node pipeline list"] + +[float] +=== Required permissions + +The minimum required permissions to access *Ingest Node Pipelines* are +the `manage_pipeline` and `cluster:monitor/nodes/info` cluster privileges. + +You can add these privileges in *Management > Security > Roles*. + +[role="screenshot"] +image:management/ingest-pipelines/images/ingest-pipeline-privileges.png["Privileges required for Ingest Node Pipelines"] + +[float] +[[ingest-node-pipelines-manage]] +=== Manage pipelines + +From the list view, you can to drill down into the details of a pipeline. +To +edit, clone, or delete a pipeline, use the *Actions* menu. + +If you don’t have any pipelines, you can create one using the +*Create pipeline* form. You’ll define processors to transform documents +in a specific way. To handle exceptions, you can optionally define +failure processors to execute immediately after a failed processor. +Before creating the pipeline, you can verify it provides the expected output. + +[float] +[[ingest-node-pipelines-example]] +==== Example: Create a pipeline + +In this example, you’ll create a pipeline to handle server logs in the +Common Log Format. The log looks similar to this: + +[source,js] +---------------------------------- +212.87.37.154 - - [05/May/2020:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" +200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) +AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\" +---------------------------------- + +The log contains an IP address, timestamp, and user agent. You want to give +these three items their own field in {es} for fast search and visualization. +You also want to know where the request is coming from. + +. In *Ingest Node Pipelines*, click *Create a pipeline*. +. Provide a name and description for the pipeline. +. Define the processors: ++ +[source,js] +---------------------------------- +[ + { + "grok": { + "field": "message", + "patterns": ["%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \\[%{HTTPDATE:timestamp}\\] \"%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent}"] + } + }, + { + "date": { + "field": "timestamp", + "formats": [ "dd/MMM/YYYY:HH:mm:ss Z" ] + } + }, + { + "geoip": { + "field": "clientip" + } + }, + { + "user_agent": { + "field": "agent" + } + } + ] +---------------------------------- ++ +This code defines four {ref}/ingest-processors.html[processors] that run sequentially: +{ref}/grok-processor.html[grok], {ref}/date-processor.html[date], +{ref}/geoip-processor.html[geoip], and {ref}/user-agent-processor.html[user_agent]. +Your form should look similar to this: ++ +[role="screenshot"] +image:management/ingest-pipelines/images/ingest-pipeline-processor.png["Processors for Ingest Node Pipelines"] + +. To verify that the pipeline gives the expected outcome, click *Test pipeline*. + +. In the *Document* tab, provide the following sample document for testing: ++ +[source,js] +---------------------------------- +[ + { + "_source": { + "message": "212.87.37.154 - - [05/May/2020:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" 200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\"" + } + } +] +---------------------------------- + +. Click *Run the pipeline* and check if the pipeline worked as expected. ++ +You can also +view the verbose output and refresh the output from this view. + +. If everything looks correct, close the panel, and then click *Create pipeline*. ++ +At this point, you’re ready to use the Elasticsearch index API to load +the logs data. + +. In the Kibana Console, index a document with the pipeline +you created. ++ +[source,js] +---------------------------------- +PUT my-index/_doc/1?pipeline=access_logs +{ + "message": "212.87.37.154 - - [05/May/2020:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" 200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\"" +} +---------------------------------- + +. To verify, run: ++ +[source,js] +---------------------------------- +GET my-index/_doc/1 +---------------------------------- diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index da2e190847fdb..e9e4054f3b9ba 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -60,7 +60,7 @@ You can read more at {ref}/rollup-job-config.html[rollup job configuration]. === Try it: Create and visualize rolled up data This example creates a rollup job to capture log data from sample web logs. -To follow along, add the <>. +To follow along, add the <>. In this example, you want data that is older than 7 days in the target index pattern `kibana_sample_data_logs` to roll up once a day into the index `rollup_logstash`. You’ll bucket the @@ -127,19 +127,36 @@ rollup index, or you can remove or archive it using < Index Patterns* so you can +select your rolled up data for visualizations. Click *Create index pattern*, and select *Rollup index pattern* from the dropdown. ++ +[role="screenshot"] +image::images/management-rollup-index-pattern.png[][Create rollup index pattern] + +. Enter *rollup_logstash,kibana_sample_logs* as your *Index Pattern* and `@timestamp` +as the *Time Filter field name*. ++ The notation for a combination index pattern with both raw and rolled up data -is `rollup_logstash,kibana_sample_data_logs`. +is `rollup_logstash,kibana_sample_data_logs`. In this index pattern, `rollup_logstash` +matches the rolled up index pattern and `kibana_sample_data_logs` matches the index +pattern for raw data. +. Go to *Visualize* and create a vertical bar chart. Choose `rollup_logstash,kibana_sample_data_logs` +as your source to see both the raw and rolled up data. ++ [role="screenshot"] -image::images/management_rollup_job_vis.png[][Visualization of rolled up data] +image::images/management-create-rollup-bar-chart.png[][Create visualization of rolled up data] -You can then create a dashboard that contains visualizations of the rolled up -data, raw data, or both. For more information, refer to <>. +. Look at the data in your visualization. ++ +[role="screenshot"] +image::images/management_rollup_job_vis.png[][Visualization of rolled up data] +. Optionally, create a dashboard that contains visualizations of the rolled up +data, raw data, or both. ++ [role="screenshot"] image::images/management_rollup_job_dashboard.png[][Dashboard with rolled up data] diff --git a/docs/maps/images/embed_in_dashboard.jpeg b/docs/maps/images/embed_in_dashboard.jpeg new file mode 100644 index 0000000000000..7be233e7a0364 Binary files /dev/null and b/docs/maps/images/embed_in_dashboard.jpeg differ diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index de90d7adb29c0..6480d64bdd174 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -11,17 +11,41 @@ With *Elastic Maps*, you can: * Create maps with multiple layers and indices. * Upload GeoJSON files into Elasticsearch. -* Embed your map in Dashboards. -* Plot individual documents or use aggregations to plot any data set, no matter how large. -* Create choropleth maps. -* Use data driven styling to symbolize features from property values. -* Focus the data you want to display with searches. +* Embed your map in dashboards. +* Symbolize features using data values. +* Focus in on just the data you want. -Start your tour of *Elastic Maps* with the <>. +*Ready to get started?* Start your tour of *Elastic Maps* with the <>. + +[float] +=== Create maps with multiple layers and indices +You can use multiple layers and indices to show all your data in a single map. This enables your map to show how data sits relative to physical features like weather patterns, human-made features like international borders, and business-specific features like sales regions. You can plot individual documents or use aggregations to plot any data set, no matter how large. [role="screenshot"] image::maps/images/sample_data_ecommerce.png[] +[float] +=== Upload GeoJSON files into Elasticsearch +Elastic Maps makes it easy to import geospatial data into the Elastic Stack. Using the GeoJSON Upload feature, you can drag and drop your point and shape data files directly into Elasticsearch, and then use them as layers in the map. + +[float] +=== Embed your map in dashboards +Viewing data from different angles provides better insights. Dimensions that are obscured in one visualization might be illuminated in another. Add your map to a dashboard and view your geospatial data alongside bar charts, pie charts, tag clouds, and more. + +This choropleth map shows the density of non-emergency service requests in San Diego by council district. The map is embedded in a dashboard, so users can better understand when services are requested and gain insight into the top requested services. + +[role="screenshot"] +image::maps/images/embed_in_dashboard.jpeg[] + +[float] +=== Symbolize features using data values +You can customize each layer to highlight meaningful dimensions in your data. For example, you can use dark colors to symbolize areas with more web log traffic, and lighter colors to symbolize areas with less traffic. + +[float] +=== Focus in on just the data you want +You can search across your Elasticsearch layers to focus in on just the data you want. Draw a polygon on the map or use the shape from features to create spatial filters to narrow search results to documents that either intersect with, are within, or do not intersect with the specified geometry. Filter individual layers to compares facets. + + -- include::maps-getting-started.asciidoc[] diff --git a/docs/settings/ml-settings.asciidoc b/docs/settings/ml-settings.asciidoc index 24e38e73bca9b..83443636fa633 100644 --- a/docs/settings/ml-settings.asciidoc +++ b/docs/settings/ml-settings.asciidoc @@ -6,14 +6,14 @@ ++++ You do not need to configure any settings to use {kib} {ml-features}. They are -enabled by default. +enabled by default. [[general-ml-settings-kb]] ==== General {ml} settings [cols="2*<"] |=== -| `xpack.ml.enabled` +| `xpack.ml.enabled` {ess-icon} | Set to `true` (default) to enable {kib} {ml-features}. + + If set to `false` in `kibana.yml`, the {ml} icon is hidden in this {kib} @@ -23,13 +23,7 @@ enabled by default. |=== -[[data-visualizer-settings]] -==== {data-viz} settings +[[advanced-ml-settings-kb]] +==== Advanced {ml} settings -[cols="2*<"] -|=== -| `xpack.ml.file_data_visualizer.max_file_size` - | Sets the file size limit when importing data in the {data-viz}. The default - value is `100MB`. The highest supported value for this setting is `1GB`. - -|=== +Refer to <>. \ No newline at end of file diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 12ee96b21b0c7..e8029ed1bbe9b 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -93,7 +93,7 @@ Some example translations are shown here: [horizontal] **Environment Variable**:: **Kibana Setting** `SERVER_NAME`:: `server.name` -`KIBANA_DEFAULTAPPID`:: `kibana.defaultAppId` +`SERVER_BASEPATH`:: `server.basePath` `MONITORING_ENABLED`:: `monitoring.enabled` In general, any setting listed in <> can be diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 6596f93a88f51..1be9d5b1ef35b 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -216,7 +216,9 @@ on the {kib} index at startup. {kib} users still need to authenticate with | Enables use of interpreter in Visualize. *Default: `true`* | `kibana.defaultAppId:` - | The default application to load. *Default: `"home"`* + | *deprecated* This setting is deprecated and will get removed in Kibana 8.0. +Please use the `defaultRoute` advanced setting instead. +The default application to load. *Default: `"home"`* | `kibana.index:` | {kib} uses an index in {es} to store saved searches, visualizations, and @@ -627,17 +629,17 @@ Valid locales are: `en`, `zh-CN`, `ja-JP`. *Default: `en`* |=== -include::{docdir}/settings/alert-action-settings.asciidoc[] -include::{docdir}/settings/apm-settings.asciidoc[] -include::{docdir}/settings/dev-settings.asciidoc[] -include::{docdir}/settings/graph-settings.asciidoc[] -include::{docdir}/settings/infrastructure-ui-settings.asciidoc[] -include::{docdir}/settings/i18n-settings.asciidoc[] -include::{docdir}/settings/logs-ui-settings.asciidoc[] -include::{docdir}/settings/ml-settings.asciidoc[] -include::{docdir}/settings/monitoring-settings.asciidoc[] -include::{docdir}/settings/reporting-settings.asciidoc[] +include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[] +include::{kib-repo-dir}/settings/apm-settings.asciidoc[] +include::{kib-repo-dir}/settings/dev-settings.asciidoc[] +include::{kib-repo-dir}/settings/graph-settings.asciidoc[] +include::{kib-repo-dir}/settings/infrastructure-ui-settings.asciidoc[] +include::{kib-repo-dir}/settings/i18n-settings.asciidoc[] +include::{kib-repo-dir}/settings/logs-ui-settings.asciidoc[] +include::{kib-repo-dir}/settings/ml-settings.asciidoc[] +include::{kib-repo-dir}/settings/monitoring-settings.asciidoc[] +include::{kib-repo-dir}/settings/reporting-settings.asciidoc[] include::secure-settings.asciidoc[] -include::{docdir}/settings/security-settings.asciidoc[] -include::{docdir}/settings/spaces-settings.asciidoc[] -include::{docdir}/settings/telemetry-settings.asciidoc[] +include::{kib-repo-dir}/settings/security-settings.asciidoc[] +include::{kib-repo-dir}/settings/spaces-settings.asciidoc[] +include::{kib-repo-dir}/settings/telemetry-settings.asciidoc[] diff --git a/docs/uptime-guide/alerting.asciidoc b/docs/uptime-guide/alerting.asciidoc new file mode 100644 index 0000000000000..bf9e7693fc7a5 --- /dev/null +++ b/docs/uptime-guide/alerting.asciidoc @@ -0,0 +1,33 @@ +[role="xpack"] +[[uptime-alerting]] + +=== Uptime alerting + +The Uptime app integrates with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] +feature. It provides a set of built-in actions and Uptime specific threshold alerts for you to use +and enables central management of all alerts from {kibana-ref}/management.html[Kibana Management]. + +[role="screenshot"] +image::images/create-alert.png[Create alert] + +[float] +==== Monitor status alerts + +To receive alerts when a monitor goes down, use the alerting menu at the top of the +overview page. Use a query in the alert flyout to determine which monitors to check +with your alert. If you already have a query in the overview page search bar it will +be carried over into this box. + +[role="screenshot"] +image::images/monitor-status-alert.png[Create monitor status alert flyout] + +[float] +==== TLS alerts + +Uptime also provides the ability to create an alert that will notify you when one or +more of your monitors have a TLS certificate that will expire within some threshold, +or when its age exceeds a limit. The values for these thresholds are configurable on +the <>. + +[role="screenshot"] +image::images/tls-alert.png[Create TLS alert flyout] diff --git a/docs/uptime-guide/app-overview.asciidoc b/docs/uptime-guide/app-overview.asciidoc new file mode 100644 index 0000000000000..692489a7ad311 --- /dev/null +++ b/docs/uptime-guide/app-overview.asciidoc @@ -0,0 +1,70 @@ +[role="xpack"] +[[uptime-app]] +== Uptime app + +The Uptime app in {kib} enables you to monitor the status of network endpoints via HTTP/S, TCP, and ICMP. +You can explore endpoint status over time, drill down into specific monitors, +and view a high-level snapshot of your environment at any point in time. + +[role="screenshot"] +image::images/uptime-overview.png[Uptime app overview] + +[role="xpack"] +[[uptime-app-overview]] +=== Overview + +The Uptime overview helps you quickly identify and diagnose outages and +other connectivity issues within your network or environment. You can use the date range +selection that is global to the Uptime app, to highlight +an absolute date range, or a relative one, similar to other areas of {kib}. + +[float] +=== Filter bar + +The Filter bar enables you to quickly view specific groups of monitors, or even +an individual monitor if you have defined many. + +This control allows you to use automated filter options, as well as input custom filter +text to select specific monitors by field, URL, ID, and other attributes. + +[role="screenshot"] +image::images/filter-bar.png[Filter bar] + +[float] +=== Snapshot panel + +The Snapshot panel displays the overall +status of the environment you're monitoring or a subset of those monitors. +You can see the total number of detected monitors within the selected +Uptime date range, along with the number of monitors +in an `up` or `down` state, which is based on the last check reported by Heartbeat +for each monitor. + +Next to the counts, there is a histogram displaying the change over time throughout the +selected date range. + +[role="screenshot"] +image::images/snapshot-view.png[Snapshot view] + +[float] +=== Monitor list + +Information about individual monitors is displayed in the monitor list and provides a quick +way to navigate to a more in-depth visualization for interesting hosts or endpoints. + +The information displayed includes the recent status of a host or endpoint, when the monitor was last checked, its +ID and URL, and its IP address. There is also sparkline showing its check status over time. + +[role="screenshot"] +image::images/monitor-list.png[Monitor list] + +[float] +=== Observability integrations + +The Monitor list also contains a menu of available integrations. When Uptime detects Kubernetes or +Docker related host information, it provides links to open the Metrics app or Logs app pre-filtered +for this host. Additionally, to help you quickly determine if these solutions contain data relevant to you, +this feature contains links to filter the other views on the host's IP address. + +[role="screenshot"] +image::images/observability_integrations.png[Observability integrations] diff --git a/docs/uptime-guide/certificates.asciidoc b/docs/uptime-guide/certificates.asciidoc new file mode 100644 index 0000000000000..58db91aa080eb --- /dev/null +++ b/docs/uptime-guide/certificates.asciidoc @@ -0,0 +1,15 @@ +[role="xpack"] +[[uptime-certificates]] + +=== Certificates + +The certificates page enables you to visualize TLS certificate data in your indices. In addition to the +common name, associated monitors, issuer information, and SHA fingerprints, Uptime also assigns a status +derived from the threshold values in the <>. + +Several of the columns on this page are sortable. You can use the search bar at the top of the view +to find values in most of the TLS-related fields in your Uptime indices. Additionally, using the `Alerts` +dropdown at the top of the page you can create a TLS alert. + +[role="screenshot"] +image::images/certificates-page.png[Certificates] diff --git a/docs/uptime-guide/deployment-arch.asciidoc b/docs/uptime-guide/deployment-arch.asciidoc index d8edf290b9a5e..c1b2f596c6665 100644 --- a/docs/uptime-guide/deployment-arch.asciidoc +++ b/docs/uptime-guide/deployment-arch.asciidoc @@ -4,22 +4,24 @@ There are multiple ways to deploy Uptime and Heartbeat. Use the information in this section to determine the best deployment for you. -A guiding principle is that an outage that takes down the service being monitored should not also take down Heartbeat. -You want Heartbeat to be functioning even when your service is not, so the guidelines here help you maximise this possibility. +A guiding principle is that when an outage takes down the service being monitored it should not also take down Heartbeat. +You want Heartbeat to be functioning even when your service is not, so the guidelines here help you maximize this possibility. -Heartbeat is generally run as a centralized service within a data center. +Heartbeat is commonly run as a centralized service within a data center. While it is possible to run it as a separate "sidecar" process paired with each process/container, we recommend against it. Running Heartbeat centrally ensures you will still be able to see monitoring data in the event of an overloaded, disconnected, or otherwise malfunctioning server. -For further redundancy, you may want to deploy multiple Heartbeats across geographic and/or network boundaries to provide more data. - Specify Heartbeat's observer {heartbeat-ref}/configuration-observer-options.html[geo options] to do so. Some examples might be: +For further redundancy, you may want to deploy multiple Heartbeats across geographic and network boundaries to provide more data. +To do so, specify Heartbeat's observer {heartbeat-ref}/configuration-observer-options.html[geo options]. + +Some examples might be: * **A site served from a content delivery network (CDN) with points of presence (POPs) around the globe:** -In this case you may want to have multiple Heartbeat instances at different data centers around the world checking to see if your site is reachable via local CDN POPs. +To check if your site is reachable via CDN POPS, you may want to have multiple Heartbeat instances at different data centers around the world. * **A service within a single data center that is accessed across multiple VPNs:** Set up one Heartbeat instance within the VPN the service operates from, and another within an additional VPN that users access the service from. -Having both instances will help pinpoint network errors in the event of an outage. +Having both instances helps pinpoint network errors in the event of an outage. * **A single service running primarily in a US east coast data center, with a hot failover located in a US west coast data center:** In each data center, run a Heartbeat instance that checks both the local copy of the service and its counterpart across the country. Set up two monitors in each region, one for the local service and one for the remote service. -In the event of a data center failure it will be immediately obvious if the service had a connectivity issue to the outside world or if the failure was only internal. +In the event of a data center failure it will be immediately apparent if the service had a connectivity issue to the outside world or if the failure was only internal. diff --git a/docs/uptime-guide/images/cert-exp.png b/docs/uptime-guide/images/cert-exp.png new file mode 100644 index 0000000000000..cd87668db96dd Binary files /dev/null and b/docs/uptime-guide/images/cert-exp.png differ diff --git a/docs/uptime-guide/images/certificates-page.png b/docs/uptime-guide/images/certificates-page.png new file mode 100644 index 0000000000000..598aae982cd6a Binary files /dev/null and b/docs/uptime-guide/images/certificates-page.png differ diff --git a/docs/uptime-guide/images/check-history.png b/docs/uptime-guide/images/check-history.png new file mode 100644 index 0000000000000..aac5efd9b91d3 Binary files /dev/null and b/docs/uptime-guide/images/check-history.png differ diff --git a/docs/uptime-guide/images/create-alert.png b/docs/uptime-guide/images/create-alert.png new file mode 100644 index 0000000000000..54a0c400cad4c Binary files /dev/null and b/docs/uptime-guide/images/create-alert.png differ diff --git a/docs/uptime-guide/images/crosshair-example.png b/docs/uptime-guide/images/crosshair-example.png new file mode 100644 index 0000000000000..f9e89c4f622e0 Binary files /dev/null and b/docs/uptime-guide/images/crosshair-example.png differ diff --git a/docs/uptime-guide/images/filter-bar.png b/docs/uptime-guide/images/filter-bar.png new file mode 100644 index 0000000000000..b7c424d3d0d91 Binary files /dev/null and b/docs/uptime-guide/images/filter-bar.png differ diff --git a/docs/uptime-guide/images/indices.png b/docs/uptime-guide/images/indices.png new file mode 100644 index 0000000000000..4090747b6726c Binary files /dev/null and b/docs/uptime-guide/images/indices.png differ diff --git a/docs/uptime/images/monitor-charts.png b/docs/uptime-guide/images/monitor-charts.png similarity index 100% rename from docs/uptime/images/monitor-charts.png rename to docs/uptime-guide/images/monitor-charts.png diff --git a/docs/uptime-guide/images/monitor-list.png b/docs/uptime-guide/images/monitor-list.png new file mode 100644 index 0000000000000..c9a8eccf01f6e Binary files /dev/null and b/docs/uptime-guide/images/monitor-list.png differ diff --git a/docs/uptime-guide/images/monitor-status-alert.png b/docs/uptime-guide/images/monitor-status-alert.png new file mode 100644 index 0000000000000..847a0f58f02ce Binary files /dev/null and b/docs/uptime-guide/images/monitor-status-alert.png differ diff --git a/docs/uptime-guide/images/observability_integrations.png b/docs/uptime-guide/images/observability_integrations.png new file mode 100644 index 0000000000000..3b23aa2dbd2a5 Binary files /dev/null and b/docs/uptime-guide/images/observability_integrations.png differ diff --git a/docs/uptime-guide/images/settings.png b/docs/uptime-guide/images/settings.png new file mode 100644 index 0000000000000..d19b7f842ea68 Binary files /dev/null and b/docs/uptime-guide/images/settings.png differ diff --git a/docs/uptime-guide/images/snapshot-view.png b/docs/uptime-guide/images/snapshot-view.png new file mode 100644 index 0000000000000..b6f07fb0721aa Binary files /dev/null and b/docs/uptime-guide/images/snapshot-view.png differ diff --git a/docs/uptime-guide/images/status-bar.png b/docs/uptime-guide/images/status-bar.png new file mode 100644 index 0000000000000..fd72e2b78c2a0 Binary files /dev/null and b/docs/uptime-guide/images/status-bar.png differ diff --git a/docs/uptime-guide/images/tls-alert.png b/docs/uptime-guide/images/tls-alert.png new file mode 100644 index 0000000000000..19efe07838903 Binary files /dev/null and b/docs/uptime-guide/images/tls-alert.png differ diff --git a/docs/uptime-guide/images/uptime-overview.png b/docs/uptime-guide/images/uptime-overview.png new file mode 100644 index 0000000000000..25c88b2d14287 Binary files /dev/null and b/docs/uptime-guide/images/uptime-overview.png differ diff --git a/docs/uptime-guide/index.asciidoc b/docs/uptime-guide/index.asciidoc index 09763182fa88f..01a93cb454ea9 100644 --- a/docs/uptime-guide/index.asciidoc +++ b/docs/uptime-guide/index.asciidoc @@ -1,5 +1,3 @@ -// short-version can be: 8, 7, 6, etc. -:short-version: 8 include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] include::{asciidoc-dir}/../../shared/attributes.asciidoc[] @@ -12,3 +10,13 @@ include::install.asciidoc[] include::deployment-arch.asciidoc[] +include::app-overview.asciidoc[] + +include::monitor.asciidoc[] + +include::settings.asciidoc[] + +include::certificates.asciidoc[] + +include::alerting.asciidoc[] + diff --git a/docs/uptime-guide/install.asciidoc b/docs/uptime-guide/install.asciidoc index 0ed1270ca92ce..05b9c6665562f 100644 --- a/docs/uptime-guide/install.asciidoc +++ b/docs/uptime-guide/install.asciidoc @@ -29,7 +29,7 @@ first see the https://www.elastic.co/support/matrix[Elastic Support Matrix] for [[install-elasticsearch]] === Step 1: Install Elasticsearch -Install an Elasticsearch cluster, start it up, and make sure it's running. +Install an {es} cluster, start it up, and make sure it's running. . Verify that your system meets the https://www.elastic.co/support/matrix#matrix_jvm[minimum JVM requirements] for {es}. @@ -39,7 +39,7 @@ https://www.elastic.co/support/matrix#matrix_jvm[minimum JVM requirements] for { [[install-kibana]] === Step 2: Install Kibana -Install Kibana, start it up, and open up the web interface: +Install {kib}, start it up, and open up the web interface: . {stack-gs}/get-started-elastic-stack.html#install-kibana[Install Kibana]. . {stack-gs}/get-started-elastic-stack.html#_launch_the_kibana_web_interface[Launch the Kibana Web Interface]. @@ -48,27 +48,27 @@ Install Kibana, start it up, and open up the web interface: === Step 3: Install and configure Heartbeat Uptime requires the setup of monitors in Heartbeat. -These monitors provide the data you'll be visualizing in the {kibana-ref}/xpack-uptime.html[Uptime UI]. +These monitors provide the data you'll be visualizing in the {kibana-ref}/xpack-uptime.html[Uptime app]. -See the *Setup Instructions* in Kibana for instructions on installing and configuring Heartbeat. +For instructions on installing and configuring Heartbeat, see the *Setup Instructions* in {kib}. Additional information is available in {heartbeat-ref}/heartbeat-configuration.html[Configure Heartbeat]. [role="screenshot"] image::images/uptime-setup.png[Installation instructions on the Uptime page in Kibana] [[setup-security]] -=== Step 4: Setup Security +=== Step 4: Set up Security Secure your installation by following the {heartbeat-ref}/securing-heartbeat.html[Secure Heartbeat] documentation. [float] ==== Important considerations -* Make sure you're using the same major versions of Heartbeat and Kibana. +* Make sure you're using the same major versions of Heartbeat and {kib}. -* Index patterns tell Kibana which Elasticsearch indices you want to explore. -The Uptime UI requires a +heartbeat-{short-version}*+ index pattern. -If you have configured a different index pattern, you can use {ref}/indices-aliases.html[index aliases] to ensure data is recognized by the UI. +* Index patterns tell {kib} which {es} indices you want to explore. +The Uptime app requires a +heartbeat-{major-version-only}*+ index pattern. +If you have configured a different index pattern, you can use {ref}/indices-aliases.html[index aliases] to ensure data is recognized by the Uptime app. After you install and configure Heartbeat, -the {kibana-ref}/xpack-uptime.html[Uptime UI] will automatically populate with the Heartbeat monitors. +the {kibana-ref}/xpack-uptime.html[Uptime app] is automatically populated with the Heartbeat monitors. diff --git a/docs/uptime-guide/monitor.asciidoc b/docs/uptime-guide/monitor.asciidoc new file mode 100644 index 0000000000000..bb5d315cf63eb --- /dev/null +++ b/docs/uptime-guide/monitor.asciidoc @@ -0,0 +1,59 @@ +[role="xpack"] +[[uptime-monitor]] +=== Monitor + +The Monitor page helps you gain insights into the performance +of a specific network endpoint. A detailed visualization of +the monitor's request duration over time, as well as the `up`/`down` +status over time, is displayed. By configuring Machine Learning jobs +on this page, you can also also detect anomalies in response time data. + + +==== Status panel + +The Status panel displays a quick summary of the latest information +regarding your monitor. You can view its latest status, click a link to +visit the targeted URL, see its most recent request duration, and determine the +amount of time that has elapsed since the last check. + +When two Heartbeat instances are configured in different geographic locations +the map will show each location as a pinpoint on the map, along with the +amount of time elapsed since data was last received from that location. + +[role="screenshot"] +image::images/status-bar.png[Status bar] + + +[float] +==== Monitor charts + +The Monitor charts visualize information over the time specified in the +date range. These charts help you gain insights into how quickly requests are being resolved +by the targeted endpoint, and give you a sense of how frequently a host or endpoint +was down in your selected timespan. + +[role="screenshot"] +image::images/monitor-charts.png[Monitor charts] + +The Monitor duration chart displays request duration information for your monitor. +The area surrounding the line is the range of request time for the corresponding +bucket. The line is the average time. In the upper right hand of this panel +you can enable Anomaly detection using Machine Learning. When response times change +in an unexpected way the time range in which they occurred are highlighted with a color. + +The pings over time chart is a graphical representation of the check statuses over time. +Hover over the charts to display crosshairs with specific numeric data. + +[role="screenshot"] +image::images/crosshair-example.png[Chart crosshair] + +[float] +==== Check history + +The Check history table lists the total count of this monitor's checks for the selected +date range. To help find recent problems on a per-check basis, you can filter the checks +by status and location. This table can help you gain some insight into more granular details +about recent individual data points that Heartbeat is logging about your host or endpoint. + +[role="screenshot"] +image::images/check-history.png[Check history view] diff --git a/docs/uptime-guide/overview.asciidoc b/docs/uptime-guide/overview.asciidoc index c6bd71b1f5574..ab230b27f8cda 100644 --- a/docs/uptime-guide/overview.asciidoc +++ b/docs/uptime-guide/overview.asciidoc @@ -2,10 +2,14 @@ [[uptime-overview]] == Elastic Uptime overview -Elastic Uptime allows you to monitor the availability and response times of applications and services in real time and to detect problems before they affect users. +++++ +Overview +++++ -Elastic Uptime can help you to understand uptime and response time characteristics for your services and applications. -It can be deployed both inside and outside your organization's network, so you can analyze problems from multiple vantage points. +Elastic Uptime enables you to monitor the availability and response times of applications and services in real time and to detect problems before they affect users. + +Elastic Uptime helps you to understand uptime and response time characteristics for your services and applications. +It can be deployed both inside and outside your organization's network, so that you can analyze problems from multiple vantage points. Elastic Uptime uses these components: *Heartbeat*, *Elasticsearch* and *Kibana*. @@ -37,13 +41,11 @@ The {kibana-ref}/xpack-uptime.html[Elasticsearch Uptime app] in Kibana provides // ++ In diagram, should be Uptime app, not Uptime UI, possibly even Elastic Uptime? Also applies to Metrics/Logging/APM. // ++ Need more whitespace around components. -image::images/uptime-simple-deployment.png[Uptime simple deployment] - In this simple deployment, a single instance of Heartbeat is deployed at a single monitoring location to monitor a single service. The Heartbeat instance sends the monitoring data to Elasticsearch. Then you can use the Uptime app in Kibana to view the data from Heartbeat and determine the status of the service. -image::images/uptime-multi-deployment.png[Uptime multiple server deployment] +image::images/uptime-simple-deployment.png[Uptime simple deployment] In this deployment, two instances of Heartbeat are deployed at two different monitoring locations. Both instances monitor the same service. @@ -51,3 +53,5 @@ The Heartbeat instances send the monitoring data to Elasticsearch. As before, you can use the Uptime app in Kibana to view the Heartbeat data and determine the status of the service. When a failure occurs, the multiple monitoring locations enable you to pinpoint the area in which the failure has occurred. +image::images/uptime-multi-deployment.png[Uptime multiple server deployment] + diff --git a/docs/uptime-guide/settings.asciidoc b/docs/uptime-guide/settings.asciidoc new file mode 100644 index 0000000000000..59f9af631bfa7 --- /dev/null +++ b/docs/uptime-guide/settings.asciidoc @@ -0,0 +1,51 @@ +[role="xpack"] +[[uptime-settings]] + +=== Settings + +The Uptime settings page lets you change which Heartbeat indices are displayed +by the uptime app. Users must have the 'all' permission to modify items on this page. +Uptime settings apply to the current space only. Use different settings in different +spaces to segment different uptime use cases and domains. + +==== Indices + +Imagine your organization has one team for internal IT services, and another +for public services. Each team operates independently and is only responsible for its +own services. In this scenario, you might set up separate Heartbeat instances for each team, +writing out to index patterns named `it-heartbeat-\*`, and `external-heartbeat-\*`. You would +create separate roles and users for each in Elasticsearch, each with access to their own spaces, +named `it` and `external` respectively. Within each space you would navigate to the settings page +and set the correct index pattern to match only the indices that space is allowed to access. + +Note: The pattern set here only restricts what the Uptime app shows. Users may still be able +to manually query Elasticsearch for data outside this pattern. + +[role="screenshot"] +image::images/indices.png[Heartbeat indices] + +See the {kibana-ref}/uptime-security.html[Uptime security] and {heartbeat-ref}/securing-heartbeat.html[Heartbeat security] +docs for more information. + +==== Certificate thresholds + +You can modify settings in this section to control how Uptime will visualize your TLS values in +the <>. These settings also determine which certificates will be +selected by any TLS alert you define. + +There are two fields, `age` and `expiration`. Use the `age` threshold to specify when Uptime should warn +you about certificates that have been valid for too long. Use the `expiration` threshold to specify when Uptime should warn you +about certificates that have approaching expiration dates. + +For example, a common security requirement is to make sure that none of your organization's TLS certificates have been +valid for longer than one year. Modifying the `Age limit` field's value to 365 days will help you keep track of which +certificates you may want to refresh. + +Likewise, to see which of your TLS certificates are close to expiring ahead of time, specify +an `Expiration threshold` on this page. When the count of a certificate's remaining valid days falls +below this threshold, Uptime will consider it in a warning state. When you define a TLS alert, you receive a +notification from Uptime about the certificate. + +[role="screenshot"] +image::images/cert-exp.png[Certification expiration thresholds] + diff --git a/docs/uptime/images/alert-flyout.png b/docs/uptime/images/alert-flyout.png deleted file mode 100644 index 7fc1e3d9aefe2..0000000000000 Binary files a/docs/uptime/images/alert-flyout.png and /dev/null differ diff --git a/docs/uptime/images/check-history.png b/docs/uptime/images/check-history.png deleted file mode 100644 index 91565bf59aa7f..0000000000000 Binary files a/docs/uptime/images/check-history.png and /dev/null differ diff --git a/docs/uptime/images/crosshair-example.png b/docs/uptime/images/crosshair-example.png deleted file mode 100644 index a4559eac1c3e7..0000000000000 Binary files a/docs/uptime/images/crosshair-example.png and /dev/null differ diff --git a/docs/uptime/images/filter-bar.png b/docs/uptime/images/filter-bar.png deleted file mode 100644 index dee735d0f4907..0000000000000 Binary files a/docs/uptime/images/filter-bar.png and /dev/null differ diff --git a/docs/uptime/images/monitor-list.png b/docs/uptime/images/monitor-list.png deleted file mode 100644 index 0c8ad473428bd..0000000000000 Binary files a/docs/uptime/images/monitor-list.png and /dev/null differ diff --git a/docs/uptime/images/observability_integrations.png b/docs/uptime/images/observability_integrations.png deleted file mode 100644 index 6589c0c5565dd..0000000000000 Binary files a/docs/uptime/images/observability_integrations.png and /dev/null differ diff --git a/docs/uptime/images/settings.png b/docs/uptime/images/settings.png deleted file mode 100644 index dd36f0a6d702b..0000000000000 Binary files a/docs/uptime/images/settings.png and /dev/null differ diff --git a/docs/uptime/images/snapshot-view.png b/docs/uptime/images/snapshot-view.png deleted file mode 100644 index 1fce2e9592c14..0000000000000 Binary files a/docs/uptime/images/snapshot-view.png and /dev/null differ diff --git a/docs/uptime/images/status-bar.png b/docs/uptime/images/status-bar.png deleted file mode 100644 index 8d242789cdccd..0000000000000 Binary files a/docs/uptime/images/status-bar.png and /dev/null differ diff --git a/docs/uptime/images/uptime-overview.png b/docs/uptime/images/uptime-overview.png new file mode 100644 index 0000000000000..25c88b2d14287 Binary files /dev/null and b/docs/uptime/images/uptime-overview.png differ diff --git a/docs/uptime/index.asciidoc b/docs/uptime/index.asciidoc index a355f8ecf4843..66c9e9357420f 100644 --- a/docs/uptime/index.asciidoc +++ b/docs/uptime/index.asciidoc @@ -1,21 +1,19 @@ +[chapter] [role="xpack"] [[xpack-uptime]] = Uptime -[partintro] --- -Uptime allows you to monitor the status of network endpoints via HTTP/S, TCP, and ICMP. +The Uptime app in {kib} enables you to monitor the status of network endpoints via HTTP/S, TCP, and ICMP. You can explore endpoint status over time, drill down into specific monitors, -and easily view a high-level snapshot of your environment at any point in time. +and view a high-level snapshot of your environment at any point in time. + +[role="screenshot"] +image::images/uptime-overview.png[Uptime app overview] + +[float] +=== Get started To get started with Elastic Uptime, refer to {uptime-guide}/install-uptime.html[Install Uptime]. -* <> -* <> -* <> --- -include::overview.asciidoc[] -include::monitor.asciidoc[] -include::settings.asciidoc[] diff --git a/docs/uptime/monitor.asciidoc b/docs/uptime/monitor.asciidoc deleted file mode 100644 index 8a4be1f11a721..0000000000000 --- a/docs/uptime/monitor.asciidoc +++ /dev/null @@ -1,59 +0,0 @@ -[role="xpack"] -[[uptime-monitor]] -== Monitor - -The Monitor page will help you get further insight into the performance -of a specific network endpoint. You'll see a detailed visualization of -the monitor's request duration over time, as well as the `up`/`down` -status over time. You can also also detect anomalies in response time data -by configuring Machine Learning jobs on this page. - -[float] -=== Status panel - -[role="screenshot"] -image::uptime/images/status-bar.png[Status bar] - -The Status panel displays a quick summary of the latest information -regarding your monitor. You can view its latest status, click a link to -visit the targeted URL, see its most recent request duration, and determine the -amount of time that has elapsed since the last check. - -When two Heartbeat instances are configured in different geographic locations -the map will show each location as a pinpoint on the map, along with the -amount of time elapsed since data was last received from that location. - - -[float] -=== Monitor charts - -[role="screenshot"] -image::uptime/images/monitor-charts.png[Monitor charts] - -The Monitor charts visualize information over the time specified in the -date range. These charts can help you gain insight into how quickly requests are being resolved -by the targeted endpoint, and give you a sense of how frequently a host or endpoint -was down in your selected timespan. - -The Monitor duration chart displays request duration information for your monitor. -The area surrounding the line is the range of request time for the corresponding -bucket. The line is the average time. Anomaly detection using Machine Learning -can be configured in the upper right hand of this panel. When response times change -in an unexpected way the time range in which they occurred will be given filled with a color. - -The pings over time chart is a graphical representation of the check statuses over time. -Hover over the charts to display crosshairs with more specific numeric data. - -[role="screenshot"] -image::uptime/images/crosshair-example.png[Chart crosshair] - -[float] -=== Check history - -[role="screenshot"] -image::uptime/images/check-history.png[Check history view] - -The Check history displays the total count of this monitor's checks for the selected -date range. You can additionally filter the checks by status and location to help find recent problems -on a per-check basis. This table can help you gain some insight into more granular details -about recent individual data points Heartbeat is logging about your host or endpoint. diff --git a/docs/uptime/overview.asciidoc b/docs/uptime/overview.asciidoc deleted file mode 100644 index 71c09c968e512..0000000000000 --- a/docs/uptime/overview.asciidoc +++ /dev/null @@ -1,73 +0,0 @@ -[role="xpack"] -[[uptime-overview]] - -== Overview - -The Uptime overview is intended to help you quickly identify and diagnose outages and -other connectivity issues within your network or environment. There is a date range -selection that is global to the Uptime UI; you can use this selection to highlight -an absolute date range, or a relative one, similar to other areas of Kibana. - -[float] -=== Filter bar - -[role="screenshot"] -image::uptime/images/filter-bar.png[Filter bar] - -The filter bar is designed to let you quickly view specific groups of monitors, or even -an individual monitor, if you have defined many. - -This control allows you to use automated filter options, as well as input custom filter -text to select specific monitors by field, URL, ID, and other attributes. - -[float] -=== Snapshot panel - -[role="screenshot"] -image::uptime/images/snapshot-view.png[Snapshot view] - -This panel is intended to quickly give you a sense of the overall -status of the environment you're monitoring, or a subset of those monitors. -Here, you can see the total number of detected monitors within the selected -Uptime date range. In addition to the total, the counts for the number of monitors -in an `up` or `down` state are displayed, based on the last check reported by Heartbeat -for each monitor. - -Next to the counts, there is a histogram displaying the change over time throughout the -selected date range. - -[float] -=== Monitor list - -[role="screenshot"] -image::uptime/images/monitor-list.png[Monitor list] - -The Monitor list displays information at the level of individual monitors. -The data shown here will flesh out your individual monitors, and provide a quick -way to navigate to a more in-depth visualization for interesting hosts or endpoints. - -This table includes information like the most recent status, when the monitor was last checked, its -ID and URL, its IP address, and a dedicated sparkline showing its check status over time. - -[float] -=== Creating and managing alerts - -[role="screenshot"] -image::uptime/images/alert-flyout.png[Create alert flyout] - -To receive alerts when a monitor goes down, use the alerting menu at the top of the -overview page. Use a query in the alert flyout to determine which monitors to check -with your alert. If you already have a query in the overview page search bar it will -be carried over into this box. - -[float] -=== Observability integrations - -[role="screenshot"] -image::uptime/images/observability_integrations.png[Observability integrations] - -The Monitor list also contains a menu of possible integrations. If Uptime detects Kubernetes or -Docker related host information, it will provide links to open the Metrics app or Logs app pre-filtered -for this host. Additionally, this feature supplies links to simply filter the other views on the host's -IP address, to help you quickly determine if these other solutions contain data relevant to your current -interest. diff --git a/docs/uptime/settings.asciidoc b/docs/uptime/settings.asciidoc deleted file mode 100644 index 55da6e802bec6..0000000000000 --- a/docs/uptime/settings.asciidoc +++ /dev/null @@ -1,27 +0,0 @@ -[role="xpack"] -[[uptime-settings]] - -== Settings - -[role="screenshot"] -image::uptime/images/settings.png[Filter bar] - -The Uptime settings page lets you change which Heartbeat indices are displayed -by the uptime app. Users must have the 'all' permission to modify items on this page. -Uptime settings apply to the current space only. Use different settings in different -spaces to segment different uptime use cases and domains. - -As an example, imagine your organization has one team for internal IT services, and another -for public services. Each team operates independently and is only responsible for its -own services. In this scenario, you might set up separate Heartbeat instances for each team, -writing out to index patterns named `it-heartbeat-\*`, and `external-heartbeat-\*`. You would -create separate roles and users for each in Elasticsearch, each with access to their own spaces, -named `it` and `external` respectively. Within each space you would navigate to the settings page -and set the correct index pattern to match only the indices that space is allowed to access. - -Note that the pattern set here only restricts what the Uptime app shows. Users may still be able -to manually query Elasticsearch for data outside this pattern! - -See the <> -and {heartbeat-ref}/securing-heartbeat.html[Heartbeat security] -docs for more information. diff --git a/docs/user/alerting/index.asciidoc b/docs/user/alerting/index.asciidoc index df11f5f03a7de..6f691f2715bc8 100644 --- a/docs/user/alerting/index.asciidoc +++ b/docs/user/alerting/index.asciidoc @@ -160,7 +160,7 @@ If you are using an *on-premises* Elastic Stack deployment: If you are using an *on-premises* Elastic Stack deployment with <>: -* Transport Layer Security (TLS) must be configured for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. +* You must enable Transport Layer Security (TLS) for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. A proxy will not suffice. [float] [[alerting-security]] diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 98033c5a87f6f..355684f7448a1 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -5,7 +5,7 @@ [partintro] -- -Canvas is a data visualization and presentation tool that sits within Kibana. With Canvas, you can pull live data directly from Elasticsearch, and combine the data with colors, images, text, and your imagination to create dynamic, multi-page, pixel-perfect displays. If you are a little bit creative, a little bit technical, and a whole lot curious, then Canvas is for you. +Canvas is a data visualization and presentation tool that sits within {kib}. With Canvas, you can pull live data directly from {es}, and combine the data with colors, images, text, and your imagination to create dynamic, multi-page, pixel-perfect displays. If you are a little bit creative, a little bit technical, and a whole lot curious, then Canvas is for you. With Canvas, you can: @@ -13,9 +13,7 @@ With Canvas, you can: * Customize your workpad with your own visualizations, such as images and text. -* Customize your data by pulling it directly from Elasticsearch. - -* Show off your data with charts, graphs, progress monitors, and more. +* Pull your data directly from Elasticsearch, then show it off with charts, graphs, progress monitors, and more. * Focus the data you want to display with filters. diff --git a/docs/user/dashboard.asciidoc b/docs/user/dashboard.asciidoc index 301efb2dfe2c0..1614f00f37ac7 100644 --- a/docs/user/dashboard.asciidoc +++ b/docs/user/dashboard.asciidoc @@ -160,7 +160,7 @@ When you're finished adding and arranging the panels, save the dashboard. . Enter the dashboard *Title* and optional *Description*, then *Save* the dashboard. [[sharing-dashboards]] -=== Share the dashboard +== Share the dashboard [[embedding-dashboards]] Share your dashboard outside of {kib}. diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index a4ba320e826b1..1704a80847652 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -1,117 +1,171 @@ [[management]] -= Management += Stack Management [partintro] -- -*Management* is home to UIs for managing all things Elastic Stack— +*Stack Management* is home to UIs for managing all things Elastic Stack— indices, clusters, licenses, UI settings, index patterns, spaces, and more. [float] -[[manage-Elasticsearch]] -== Manage {es} +[[manage-ingest]] +== Ingest [cols="50, 50"] |=== -a| <> +| <> +| Create and manage {es} +pipelines that enable you to perform common transformations and +enrichments on your data. -Replicate indices on a remote cluster and copy them to a follower index on a local cluster. -This is important for -disaster recovery. It also keeps data local for faster queries. +| {logstash-ref}/logstash-centralized-pipeline-management.html[Logstash Pipelines] +| Create, edit, and delete your Logstash pipeline configurations. -| <> - -Create a policy for defining the lifecycle of an index as it ages -through the hot, warm, cold, and delete phases. -Such policies help you control operation costs -because you can put data in different resource tiers. +| <> +| Manage your Beats configurations in a central location and +quickly deploy configuration changes to all Beats running across your enterprise. -a| <> -View index settings, mappings, and statistics and perform operations, such as refreshing, -flushing, and clearing the cache. Practicing good index management ensures -that your data is stored cost effectively. +|=== -| <> +[float] +[[manage-data]] +== Data -View the status of your license, start a trial, or install a new license. For -the full list of features that are included in your license, -see the https://www.elastic.co/subscriptions[subscription page]. +[cols="50, 50"] +|=== -| <> +a| <> +| View index settings, mappings, and statistics and perform operations, such as refreshing, +flushing, and clearing the cache. Practicing good index management ensures +that your data is stored cost effectively. -Manage your remote clusters for use with cross-cluster search and cross-cluster replication. -You can add and remove remote clusters, and check their connectivity. +| <> +|Create a policy for defining the lifecycle of an index as it ages +through the hot, warm, cold, and delete phases. +Such policies help you control operation costs +because you can put data in different resource tiers. -| <> +| <> +|Define a policy that creates, schedules, and automatically deletes snapshots to ensure that you +have backups of your cluster in case something goes wrong. -Create a job that periodically aggregates data from one or more indices, and then +| <> +|Create a job that periodically aggregates data from one or more indices, and then rolls it into a new, compact index. Rollup indices are a good way to store months or years of historical data in combination with your raw data. -| <> +| {ref}/transforms.html[Transforms] +|Use transforms to pivot existing {es} indices into summarized or entity-centric indices. -Define a policy that creates, schedules, and automatically deletes snapshots to ensure that you -have backups of your cluster in case something goes wrong. +| <> +|Replicate indices on a remote cluster and copy them to a follower index on a local cluster. +This is important for +disaster recovery. It also keeps data local for faster queries. -| {ref}/transforms.html[*Transforms*] +| <> +|Manage your remote clusters for use with cross-cluster search and cross-cluster replication. +You can add and remove remote clusters, and check their connectivity. +|=== -Use transforms to pivot existing {es} indices into summarized or entity-centric indices. +[float] +[[manage-alerts-insights]] +== Alerts and Insights -| <> +[cols="50, 50"] +|=== -Identify the issues that you need to address before upgrading to the -next major version of {es}, and then reindex, if needed. +| <> +| Centrally manage your alerts across {kib}. Create and manage reusable +connectors for triggering actions. + +| <> +| Monitor the generation of reports—PDF, PNG, and CSV—and download reports that you previously generated. +A report can contain a dashboard, visualization, saved search, or Canvas workpad. -| <> +| {ml-docs}/ml-jobs.html[Machine Learning Jobs] +| View your {anomaly-jobs} and {dfanalytics-jobs}. Open the Single Metric +Viewer or Anomaly Explorer to see your {ml} results. -Detect changes in your data by creating, managing, and monitoring alerts. -For example, create an alert when the maximum total CPU usage on a machine goes +| <> +| Detect changes in your data by creating, managing, and monitoring alerts. +For example, you might create an alert when the maximum total CPU usage on a machine goes above a certain percentage. |=== [float] -[[manage-kibana]] -== Manage {kib} +[[manage-security]] +== Security [cols="50, 50"] |=== -a| <> +a| <> +|View the users that have been defined on your cluster. +Add or delete users and assign roles that give users +specific privileges. -Customize {kib} to suit your needs. Change the format for displaying dates, turn on dark mode, -set the timespan for notification messages, and much more. - -| <> +| <> +|View the roles that exist on your cluster. Customize +the actions that a user with the role can perform, on a cluster, index, and space level. -Centrally manage your alerts across {kib}. Create and manage reusable -connectors for triggering actions. +| <> +| Create secondary credentials so that you can send requests on behalf of the user. +Secondary credentials have the same or lower access rights. -| <> +| <> +| Assign roles to your users using a set of rules. Role mappings are required +when authenticating via an external identity provider, such as Active Directory, +Kerberos, PKI, OIDC, and SAML. -Create and manage the index patterns that help you retrieve your data from {es}. +|=== -| <> +[float] +[[manage-kibana]] +== {kib} -Monitor the generation of reports—PDF, PNG, and CSV—and download reports that you previously generated. -A report can contain a dashboard, visualization, saved search, or Canvas workpad. +[cols="50, 50"] +|=== -| <> +a| <> +|Create and manage the index patterns that retrieve your data from {es}. -Copy, edit, delete, import, and export your saved objects. +| <> +| Copy, edit, delete, import, and export your saved objects. These include dashboards, visualizations, maps, index patterns, Canvas workpads, and more. -| <> - -Create spaces to organize your dashboards and other saved objects into categories. +| <> +| Create spaces to organize your dashboards and other saved objects into categories. A space is isolated from all other spaces, so you can tailor it to your needs without impacting others. -|   +a| <> +| Customize {kib} to suit your needs. Change the format for displaying dates, turn on dark mode, +set the timespan for notification messages, and much more. + +|=== + +[float] +[[manage-stack]] +== Stack +[cols="50, 50"] |=== +| <> +| View the status of your license, start a trial, or install a new license. For +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. + +|=== + + + -- include::{kib-repo-dir}/management/advanced-options.asciidoc[] @@ -140,6 +194,8 @@ include::{kib-repo-dir}/management/index-lifecycle-policies/example-index-lifecy include::{kib-repo-dir}/management/managing-indices.asciidoc[] +include::{kib-repo-dir}/management/ingest-pipelines/ingest-pipelines.asciidoc[] + include::{kib-repo-dir}/management/managing-fields.asciidoc[] include::{kib-repo-dir}/management/managing-licenses.asciidoc[] diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index 853c735418cea..4b91812660c78 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -2,16 +2,17 @@ [[xpack-security-authorization]] === Granting access to {kib} -The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built-in role], which you can use to grant access to all Kibana features in all spaces. To grant users access to a subset of spaces or features, you can create a custom role that grants the desired Kibana privileges. +The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built-in role], which you can use to grant access to all Kibana features in all spaces. To grant users access to a subset of spaces or features, you can create a custom role that grants the desired Kibana privileges. When you assign a user multiple roles, the user receives a union of the roles’ privileges. Therefore, assigning the `kibana_admin` role in addition to a custom role that grants Kibana privileges is ineffective because `kibana_admin` has access to all the features in all spaces. NOTE: When running multiple tenants of Kibana by changing the `kibana.index` in your `kibana.yml`, you cannot use `kibana_admin` to grant access. You must create custom roles that authorize the user for that specific tenant. Although multi-tenant installations are supported, the recommended approach to securing access to Kibana segments is to grant users access to specific spaces. [role="xpack"] +[[xpack-kibana-role-management]] === {kib} role management -To create a role that grants {kib} privileges, go to **Management -> Security -> Roles** and click **Create role**. +To create a role that grants {kib} privileges, go to **Management -> Security -> Roles** and click **Create role**. [[adding_kibana_privileges]] ==== Adding {kib} privileges @@ -63,7 +64,7 @@ Features are available to users when their roles grant access to the features, * Using the same role, it’s possible to assign different privileges to different spaces. After you’ve added space privileges, click **Add space privilege**. If you’ve already added privileges for either *** Global (all spaces)** or an individual space, you will not be able to select these in the **Spaces** selection control. -Additionally, if you’ve already assigned privileges at *** Global (all spaces)**, you are only able to assign additional privileges to individual spaces. Similar to the behavior of multiple roles granting the union of all privileges, space privileges are also a union. If you’ve already granted the user the **All** privilege at *** Global (all spaces)**, you’re not able to restrict the role to only the **Read** privilege at an individual space. +Additionally, if you’ve already assigned privileges at *** Global (all spaces)**, you are only able to assign additional privileges to individual spaces. Similar to the behavior of multiple roles granting the union of all privileges, space privileges are also a union. If you’ve already granted the user the **All** privilege at *** Global (all spaces)**, you’re not able to restrict the role to only the **Read** privilege at an individual space. ==== Privilege summary @@ -111,4 +112,3 @@ image::user/security/images/privilege-example-2.png[Privilege example 2] [role="screenshot"] image::user/security/images/privilege-example-3.png[Privilege example 3] - diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc index 422afbb201183..38ccb7878a92b 100644 --- a/docs/visualize/lens.asciidoc +++ b/docs/visualize/lens.asciidoc @@ -4,60 +4,51 @@ beta[] -*Lens* provides you with a simple and fast way to create visualizations from your {es} data. With Lens, you can: +*Lens* is a simple and fast way to create visualizations of your {es} data. With *Lens*, +you drag and drop your data fields onto the visualization builder pane, and *Lens* automatically generates +a visualization that best displays your data. -* Quickly build visualizations by dragging and dropping data fields. +With Lens, you can: -* Understand your data with a summary view on each field. +* Explore your data in just a few clicks. -* Easily change the visualization type by selecting the automatically generated visualization suggestions. +* Create visualizations with multiple layers and indices. -* Save your visualization for use in a dashboard. +* Use the automatically generated visualization suggestions to change the visualization type. -[float] -[[drag-drop]] -=== Drag and drop - -The panel shows the data fields for the selected time period. When -you drag a field from the data panel, Lens highlights where you can drop that field. The first time you drag a data field, -you'll see two places highlighted in green: - -* The visualization builder pane +* Add your visualizations to dashboards and Canvas workpads. -* The *X-axis* or *Y-axis* fields - -You can incorporate many fields into your visualization, and Lens uses heuristics to decide how -to apply each one to the visualization. +To get started with *Lens*, click a field in the data panel, then drag and drop the field on a highlighted area. [role="screenshot"] image::images/lens_drag_drop.gif[] -TIP: Drag-and-drop capabilities are available only when Lens knows how to use the data. You can still customize -your visualization if Lens is unable to make a suggestion. +You can incorporate many fields into your visualization, and Lens uses heuristics to decide how to apply each one to the visualization. + +TIP: Drag-and-drop capabilities are available only when Lens knows how to use the data. If *Lens* is unable to automatically generate a visualization, +you can still configure the customization options for your visualization. [float] [[apply-lens-filters]] -==== Find the right data +==== Filter the data panel fields -Lens shows you fields based on the <> you have defined in -{kib}, and the current time range. When you change the index pattern or time filter, -the list of fields are updated. +The fields in the data panel based on your selected <>, and the <>. -To narrow the list of fields, you can: +To change the index pattern, click it, then select a new one. The fields in the data panel automatically update. -* Enter the field name in *Search field names*. +To filter the fields in the data panel: -* Click *Filter by type*, then select the filter. You can also select *Only show fields with data* -to show the full list of fields from the index pattern. +* Enter the name in *Search field names*. + +* Click *Filter by type*, then select the filter. To show all of the fields in the index pattern, deselect *Only show fields with data*. [float] [[view-data-summaries]] ==== Data summaries -To help you decide exactly the data you want to display, get a quick summary of each data field. -The summary shows the distribution of values in the time range. +To help you decide exactly the data you want to display, get a quick summary of each field. The summary shows the distribution of values in the time range. -To view the data information, navigate to a data field, then click *i*. +To view the field summary information, navigate to the field, then click *i*. [role="screenshot"] image::images/lens_data_info.png[] @@ -66,46 +57,40 @@ image::images/lens_data_info.png[] [[change-the-visualization-type]] ==== Change the visualization type -With Lens, you are no longer required to build each visualization from scratch. Lens allows -you to switch between any supported chart type at any time. Lens also provides -suggestions, which are shortcuts to alternate visualizations based on the data you have. +*Lens* enables you to switch between any supported visualization type at any time. -You can switch between suggestions without losing your previous state: +*Suggestions* are shortcuts to alternate visualizations that *Lens* generates for you. [role="screenshot"] image::images/lens_suggestions.gif[] -If you want to switch to a chart type that is not suggested, click the chart type, -then select a chart type. When there is an exclamation point (!) -next to a chart type, Lens is unable to transfer your current data, but +If you'd like to use a visualization type that is not suggested, click the visualization type, +then select a new one. + +[role="screenshot"] +image::images/lens_viz_types.png[] + +When there is an exclamation point (!) +next to a visualization type, Lens is unable to transfer your data, but still allows you to make the change. [float] [[customize-operation]] -==== Customize the data for your visualization +==== Change the aggregation and labels Lens allows some customizations of the data for each visualization. -. Click the index pattern name, then select the new index pattern. -+ -If there is a match, Lens displays the new data. All fields that do not match the index pattern are removed. - -. Change the data field options, such as the aggregation or label. - -.. Click *Drop a field here* or the field name in the column. +. Click *Drop a field here* or the field name in the column. -.. Change the options that appear depending on the type of field. +. Change the options that appear depending on the type of field. [float] [[layers]] -==== Layers in bar, line, and area charts +==== Add layers and indices -The bar, line, and area charts allow you to layer two different series. To add a layer, click *+*. +Bar, line, and area charts allow you to visualize multiple data layers and indices so that you can compare and analyze data from multiple sources. -To remove a layer, click the chart icon next to the index name: - -[role="screenshot"] -image::images/lens_remove_layer.png[] +To add a layer, click *+*, then drag and drop the fields for the new layer. To view a different index, click it, then select a new one. [float] [[lens-tutorial]] @@ -125,50 +110,48 @@ To start, you'll need to add the <>. Drag and drop your data onto the visualization builder pane. -. Open *Visualize*, then click *Create visualization*. +. From the menu, click *Visualize*, then click *Create visualization*. . On the *New Visualization* window, click *Lens*. -. Select the *kibana_sample_data_ecommerce* index. +. Select the *kibana_sample_data_ecommerce* index pattern. -. Click image:images/time-filter-calendar.png[], then click *Last 7 days*. The list of data fields are updated. +. Click image:images/time-filter-calendar.png[], then click *Last 7 days*. ++ +The fields in the data panel update. . Drag and drop the *taxful_total_price* data field to the visualization builder pane. + [role="screenshot"] image::images/lens_tutorial_1.png[Lens tutorial] -Lens has taken your intent to see *taxful_total_price* and added in the *order_date* field to show -average order prices over time. +To display the average order prices over time, *Lens* automatically added in *order_date* field. To break down your data, drag the *category.keyword* field to the visualization builder pane. Lens -understands that you want to show the top categories and compare them across the dates, -and creates a chart that compares the sales for each of the top 3 categories: +knows that you want to show the top categories and compare them across the dates, +and creates a chart that compares the sales for each of the top three categories: [role="screenshot"] image::images/lens_tutorial_2.png[Lens tutorial] [float] [[customize-lens-visualization]] -==== Further customization +==== Customize your visualization -Customize your visualization to look exactly how you want. +Make your visualization look exactly how you want with the customization options. . Click *Average of taxful_total_price*. -.. Change the *Label* to `Sales`, or a name that you prefer for the data. +.. Change the *Label* to `Sales`. . Click *Top values of category.keyword*. -.. Increase *Number of values* to `10`. The visualization updates in the background to show there are only +.. Change *Number of values* to `10`. The visualization updates to show there are only six available categories. ++ +Look at the *Suggestions*. An area chart is not an option, but for sales data, a stacked area chart might be the best option. -. Look at the suggestions. None of them show an area chart, but for sales data, a stacked area chart -might make sense. To switch the chart type: - -.. Click *Stacked bar chart* in the column. - -.. Click *Stacked area*. +. To switch the chart type, click *Stacked bar chart* in the column, then click *Stacked area* from the *Select a visualizations* window. + [role="screenshot"] image::images/lens_tutorial_3.png[Lens tutorial] @@ -177,6 +160,6 @@ image::images/lens_tutorial_3.png[Lens tutorial] [[lens-tutorial-next-steps]] ==== Next steps -Now that you've created your visualization in Lens, you can add it to a Dashboard. +Now that you've created your visualization, you can add it to a dashboard or Canvas workpad. -For more information, see <>. +For more information, refer to <> or <>. diff --git a/examples/alerting_example/kibana.json b/examples/alerting_example/kibana.json index 76c549adc7970..2b6389649cef9 100644 --- a/examples/alerting_example/kibana.json +++ b/examples/alerting_example/kibana.json @@ -4,6 +4,6 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerting", "actions"], + "requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerts", "actions"], "optionalPlugins": [] } diff --git a/examples/alerting_example/public/alert_types/always_firing.tsx b/examples/alerting_example/public/alert_types/always_firing.tsx index b7add1f6d43ce..130519308d3c3 100644 --- a/examples/alerting_example/public/alert_types/always_firing.tsx +++ b/examples/alerting_example/public/alert_types/always_firing.tsx @@ -71,7 +71,7 @@ export const AlwaysFiringExpression: React.FunctionComponent { + onChange={(event) => { setAlertParams('instances', event.target.valueAsNumber); }} /> diff --git a/examples/alerting_example/public/alert_types/astros.tsx b/examples/alerting_example/public/alert_types/astros.tsx index 3411c6722ccd6..d52223cb6b988 100644 --- a/examples/alerting_example/public/alert_types/astros.tsx +++ b/examples/alerting_example/public/alert_types/astros.tsx @@ -32,12 +32,12 @@ import { import { i18n } from '@kbn/i18n'; import { flatten } from 'lodash'; import { ALERTING_EXAMPLE_APP_ID, Craft, Operator } from '../../common/constants'; -import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; -import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; +import { SanitizedAlert } from '../../../../x-pack/plugins/alerts/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerts/public'; import { AlertTypeModel } from '../../../../x-pack/plugins/triggers_actions_ui/public'; -export function registerNavigation(alerting: AlertingSetup) { - alerting.registerNavigation( +export function registerNavigation(alerts: AlertingSetup) { + alerts.registerNavigation( ALERTING_EXAMPLE_APP_ID, 'example.people-in-space', (alert: SanitizedAlert) => `/astros/${alert.id}` @@ -51,7 +51,7 @@ interface PeopleinSpaceParamsProps { } function isValueInEnum(enumeratin: Record, value: any): boolean { - return !!Object.values(enumeratin).find(enumVal => enumVal === value); + return !!Object.values(enumeratin).find((enumVal) => enumVal === value); } export function getAlertType(): AlertTypeModel { @@ -139,7 +139,7 @@ export const PeopleinSpaceExpression: React.FunctionComponent - errs.map(e => ( + errs.map((e) => (

{field}:`: ${errs}`

@@ -189,7 +189,7 @@ export const PeopleinSpaceExpression: React.FunctionComponent { + onChange={(event) => { setAlertParams('craft', event.target.value); setCraftTrigger({ craft: event.target.value, @@ -238,7 +238,7 @@ export const PeopleinSpaceExpression: React.FunctionComponent { + onChange={(event) => { setAlertParams('op', event.target.value); setOuterSpaceCapacity({ ...outerSpaceCapacityTrigger, @@ -258,7 +258,7 @@ export const PeopleinSpaceExpression: React.FunctionComponent { + onChange={(event) => { setAlertParams('outerSpaceCapacity', event.target.valueAsNumber); setOuterSpaceCapacity({ ...outerSpaceCapacityTrigger, diff --git a/examples/alerting_example/public/alert_types/index.ts b/examples/alerting_example/public/alert_types/index.ts index 96d9c09d15836..db9f855b573e8 100644 --- a/examples/alerting_example/public/alert_types/index.ts +++ b/examples/alerting_example/public/alert_types/index.ts @@ -19,15 +19,15 @@ import { registerNavigation as registerPeopleInSpaceNavigation } from './astros'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; -import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; -import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; +import { SanitizedAlert } from '../../../../x-pack/plugins/alerts/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerts/public'; -export function registerNavigation(alerting: AlertingSetup) { +export function registerNavigation(alerts: AlertingSetup) { // register default navigation - alerting.registerDefaultNavigation( + alerts.registerDefaultNavigation( ALERTING_EXAMPLE_APP_ID, (alert: SanitizedAlert) => `/alert/${alert.id}` ); - registerPeopleInSpaceNavigation(alerting); + registerPeopleInSpaceNavigation(alerts); } diff --git a/examples/alerting_example/public/components/view_alert.tsx b/examples/alerting_example/public/components/view_alert.tsx index e418ed91096eb..75a515bfa1b25 100644 --- a/examples/alerting_example/public/components/view_alert.tsx +++ b/examples/alerting_example/public/components/view_alert.tsx @@ -36,7 +36,7 @@ import { Alert, AlertTaskState, BASE_ALERT_API_PATH, -} from '../../../../x-pack/plugins/alerting/common'; +} from '../../../../x-pack/plugins/alerts/common'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; type Props = RouteComponentProps & { diff --git a/examples/alerting_example/public/components/view_astros_alert.tsx b/examples/alerting_example/public/components/view_astros_alert.tsx index 296269180dd7b..19f235a3f3e4e 100644 --- a/examples/alerting_example/public/components/view_astros_alert.tsx +++ b/examples/alerting_example/public/components/view_astros_alert.tsx @@ -38,7 +38,7 @@ import { Alert, AlertTaskState, BASE_ALERT_API_PATH, -} from '../../../../x-pack/plugins/alerting/common'; +} from '../../../../x-pack/plugins/alerts/common'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; type Props = RouteComponentProps & { diff --git a/examples/alerting_example/public/plugin.tsx b/examples/alerting_example/public/plugin.tsx index e3748e3235f47..524ff18bd434e 100644 --- a/examples/alerting_example/public/plugin.tsx +++ b/examples/alerting_example/public/plugin.tsx @@ -18,7 +18,7 @@ */ import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; -import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerting/public'; +import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerts/public'; import { ChartsPluginStart } from '../../../src/plugins/charts/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../x-pack/plugins/triggers_actions_ui/public'; import { DataPublicPluginStart } from '../../../src/plugins/data/public'; @@ -30,12 +30,12 @@ export type Setup = void; export type Start = void; export interface AlertingExamplePublicSetupDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; } export interface AlertingExamplePublicStartDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; charts: ChartsPluginStart; data: DataPublicPluginStart; @@ -44,7 +44,7 @@ export interface AlertingExamplePublicStartDeps { export class AlertingExamplePlugin implements Plugin { public setup( core: CoreSetup, - { alerting, triggers_actions_ui }: AlertingExamplePublicSetupDeps + { alerts, triggers_actions_ui }: AlertingExamplePublicSetupDeps ) { core.application.register({ id: 'AlertingExample', @@ -59,7 +59,7 @@ export class AlertingExamplePlugin implements Plugin { - services - .alertInstanceFactory(name) - .replaceState({ craft }) - .scheduleActions('default'); + services.alertInstanceFactory(name).replaceState({ craft }).scheduleActions('default'); }); } diff --git a/examples/alerting_example/server/plugin.ts b/examples/alerting_example/server/plugin.ts index b5dabe51e8685..cdb005feca35c 100644 --- a/examples/alerting_example/server/plugin.ts +++ b/examples/alerting_example/server/plugin.ts @@ -18,20 +18,20 @@ */ import { Plugin, CoreSetup } from 'kibana/server'; -import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerting/server'; +import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerts/server'; import { alertType as alwaysFiringAlert } from './alert_types/always_firing'; import { alertType as peopleInSpaceAlert } from './alert_types/astros'; // this plugin's dependendencies export interface AlertingExampleDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; } export class AlertingExamplePlugin implements Plugin { - public setup(core: CoreSetup, { alerting }: AlertingExampleDeps) { - alerting.registerType(alwaysFiringAlert); - alerting.registerType(peopleInSpaceAlert); + public setup(core: CoreSetup, { alerts }: AlertingExampleDeps) { + alerts.registerType(alwaysFiringAlert); + alerts.registerType(peopleInSpaceAlert); } public start() {} diff --git a/examples/bfetch_explorer/public/components/count_until/index.tsx b/examples/bfetch_explorer/public/components/count_until/index.tsx index ce48ce9dfe61f..73cbcf4cbdb1c 100644 --- a/examples/bfetch_explorer/public/components/count_until/index.tsx +++ b/examples/bfetch_explorer/public/components/count_until/index.tsx @@ -82,7 +82,7 @@ export const CountUntil: React.FC = ({ fetchStreaming }) => { setData(Number(e.target.value))} + onChange={(e) => setData(Number(e.target.value))} /> diff --git a/examples/bfetch_explorer/public/components/double_integers/index.tsx b/examples/bfetch_explorer/public/components/double_integers/index.tsx index d8fbe33ec73be..58940c23b1a6c 100644 --- a/examples/bfetch_explorer/public/components/double_integers/index.tsx +++ b/examples/bfetch_explorer/public/components/double_integers/index.tsx @@ -49,18 +49,18 @@ export const DoubleIntegers: React.FC = ({ double }) => { setShowingResults(true); const nums = numbers .split('\n') - .map(num => num.trim()) + .map((num) => num.trim()) .filter(Boolean) .map(Number); counter.set(nums.length); - nums.forEach(num => { + nums.forEach((num) => { double({ num }).then( - result => { + (result) => { if (!isMounted()) return; counter.dec(); pushResult({ num, result }); }, - error => { + (error) => { if (!isMounted()) return; counter.dec(); pushResult({ num, error }); @@ -94,7 +94,7 @@ export const DoubleIntegers: React.FC = ({ double }) => { fullWidth placeholder="Enter numbers in milliseconds separated by new line" value={numbers} - onChange={e => setNumbers(e.target.value)} + onChange={(e) => setNumbers(e.target.value)} /> diff --git a/examples/bfetch_explorer/public/containers/app/index.tsx b/examples/bfetch_explorer/public/containers/app/index.tsx index a448c9e4f3a6a..13dee8ad9e61f 100644 --- a/examples/bfetch_explorer/public/containers/app/index.tsx +++ b/examples/bfetch_explorer/public/containers/app/index.tsx @@ -30,7 +30,7 @@ export const App: React.FC = () => { const routeElements: React.ReactElement[] = []; for (const { items } of routes) { for (const { id, component } of items) { - routeElements.push( component} />); + routeElements.push( component} />); } } diff --git a/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx b/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx index cc50698e05908..029076adea666 100644 --- a/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx +++ b/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx @@ -39,7 +39,7 @@ export const Sidebar: React.FC = () => { id, name: title, isSelected: true, - items: items.map(route => ({ + items: items.map((route) => ({ id: route.id, name: route.title, onClick: () => history.push(`/${route.id}`), diff --git a/examples/bfetch_explorer/server/plugin.ts b/examples/bfetch_explorer/server/plugin.ts index bf3b7f50ca6c8..2bfb63edefa3d 100644 --- a/examples/bfetch_explorer/server/plugin.ts +++ b/examples/bfetch_explorer/server/plugin.ts @@ -54,7 +54,7 @@ export class BfetchExplorerPlugin implements Plugin { // Validate inputs. if (num < 0) throw new Error('Invalid number'); // Wait number of specified milliseconds. - await new Promise(r => setTimeout(r, num)); + await new Promise((r) => setTimeout(r, num)); // Double the number and send it back. return { num: 2 * num }; }, diff --git a/examples/demo_search/server/async_demo_search_strategy.ts b/examples/demo_search/server/async_demo_search_strategy.ts index d2d40891a5d93..7ed5062acba48 100644 --- a/examples/demo_search/server/async_demo_search_strategy.ts +++ b/examples/demo_search/server/async_demo_search_strategy.ts @@ -40,7 +40,7 @@ const totalMap = new Map(); export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider = () => { return { - search: async request => { + search: async (request) => { const id = request.id ?? generateId(); const loaded = (loadedMap.get(id) ?? 0) + 1; @@ -52,7 +52,7 @@ export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider { + cancel: async (id) => { loadedMap.delete(id); totalMap.delete(id); }, diff --git a/examples/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts index 5b0883be1fc51..a1fd0e45dbc8e 100644 --- a/examples/demo_search/server/demo_search_strategy.ts +++ b/examples/demo_search/server/demo_search_strategy.ts @@ -22,7 +22,7 @@ import { DEMO_SEARCH_STRATEGY } from '../common'; export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { return { - search: request => { + search: (request) => { return Promise.resolve({ greeting: request.mood === 'happy' diff --git a/examples/embeddable_examples/public/list_container/list_container_component.tsx b/examples/embeddable_examples/public/list_container/list_container_component.tsx index da27889a27603..ae4de1c765154 100644 --- a/examples/embeddable_examples/public/list_container/list_container_component.tsx +++ b/examples/embeddable_examples/public/list_container/list_container_component.tsx @@ -40,7 +40,7 @@ function renderList( embeddableServices: EmbeddableStart ) { let number = 0; - const list = Object.values(panels).map(panel => { + const list = Object.values(panels).map((panel) => { const child = embeddable.getChild(panel.explicitInput.id); number++; return ( diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx index b2882c97ef501..c059a884f08a2 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx @@ -55,7 +55,7 @@ function wrapSearchTerms(task: string, search?: string) { } function renderTasks(tasks: MultiTaskTodoInput['tasks'], search?: string) { - return tasks.map(task => ( + return tasks.map((task) => ( task.match(search)); + const match = tasks.find((task) => task.match(search)); if (match) return true; return false; diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx index 49dbce74788bf..f5fe01734bfa5 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx @@ -65,12 +65,12 @@ export class SearchableListContainerComponentInner extends Component { + props.embeddable.getChildIds().forEach((id) => { checked[id] = false; const output = props.embeddable.getChild(id).getOutput(); hasMatch[id] = hasHasMatchOutput(output) && output.hasMatch; }); - props.embeddable.getChildIds().forEach(id => (checked[id] = false)); + props.embeddable.getChildIds().forEach((id) => (checked[id] = false)); this.state = { checked, hasMatch, @@ -78,13 +78,13 @@ export class SearchableListContainerComponentInner extends Component { + this.props.embeddable.getChildIds().forEach((id) => { this.subscriptions[id] = this.props.embeddable .getChild(id) .getOutput$() - .subscribe(output => { + .subscribe((output) => { if (hasHasMatchOutput(output)) { - this.setState(prevState => ({ + this.setState((prevState) => ({ hasMatch: { ...prevState.hasMatch, [id]: output.hasMatch, @@ -96,7 +96,7 @@ export class SearchableListContainerComponentInner extends Component sub.unsubscribe()); + Object.values(this.subscriptions).forEach((sub) => sub.unsubscribe()); } private updateSearch = (search: string) => { @@ -104,7 +104,7 @@ export class SearchableListContainerComponentInner extends Component { - Object.values(this.props.input.panels).map(panel => { + Object.values(this.props.input.panels).map((panel) => { if (this.state.checked[panel.explicitInput.id]) { this.props.embeddable.removeEmbeddable(panel.explicitInput.id); this.subscriptions[panel.explicitInput.id].unsubscribe(); @@ -115,7 +115,7 @@ export class SearchableListContainerComponentInner extends Component { const { input, embeddable } = this.props; const checked: { [key: string]: boolean } = {}; - Object.values(input.panels).map(panel => { + Object.values(input.panels).map((panel) => { const child = embeddable.getChild(panel.explicitInput.id); const output = child.getOutput(); if (hasHasMatchOutput(output) && output.hasMatch) { @@ -126,7 +126,7 @@ export class SearchableListContainerComponentInner extends Component { - this.setState(prevState => ({ checked: { ...prevState.checked, [id]: isChecked } })); + this.setState((prevState) => ({ checked: { ...prevState.checked, [id]: isChecked } })); }; public renderControls() { @@ -156,7 +156,7 @@ export class SearchableListContainerComponentInner extends Component this.updateSearch(ev.target.value)} + onChange={(ev) => this.updateSearch(ev.target.value)} /> @@ -183,7 +183,7 @@ export class SearchableListContainerComponentInner extends Component { + const list = Object.values(input.panels).map((panel) => { const childEmbeddable = embeddable.getChild(panel.explicitInput.id); id++; return childEmbeddable ? ( @@ -195,7 +195,7 @@ export class SearchableListContainerComponentInner extends Component this.toggleCheck(e.target.checked, childEmbeddable.id)} + onChange={(e) => this.toggleCheck(e.target.checked, childEmbeddable.id)} /> diff --git a/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx b/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx index bc577ca36d793..a46c3ce9ed8e9 100644 --- a/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx +++ b/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx @@ -34,7 +34,7 @@ function TaskInput({ onSave }: { onSave: (task: string) => void }) { data-test-subj="taskInputField" value={task} placeholder="Enter task here" - onChange={e => setTask(e.target.value)} + onChange={(e) => setTask(e.target.value)} /> onSave(task)}> Save @@ -69,7 +69,7 @@ export class TodoEmbeddableFactory */ public getExplicitInput = async () => { const { openModal } = await this.getStartServices(); - return new Promise<{ task: string }>(resolve => { + return new Promise<{ task: string }>((resolve) => { const onSave = (task: string) => resolve({ task }); const overlay = openModal( toMountPoint( diff --git a/examples/embeddable_explorer/public/app.tsx b/examples/embeddable_explorer/public/app.tsx index e18012b4b3d80..3fc7fbb3b89ef 100644 --- a/examples/embeddable_explorer/public/app.tsx +++ b/examples/embeddable_explorer/public/app.tsx @@ -51,7 +51,7 @@ type NavProps = RouteComponentProps & { }; const Nav = withRouter(({ history, navigateToApp, pages }: NavProps) => { - const navItems = pages.map(page => ({ + const navItems = pages.map((page) => ({ id: page.id, name: page.title, onClick: () => history.push(`/${page.id}`), @@ -122,7 +122,7 @@ const EmbeddableExplorerApp = ({ ]; const routes = pages.map((page, i) => ( - page.component} /> + page.component} /> )); return ( diff --git a/examples/embeddable_explorer/public/embeddable_panel_example.tsx b/examples/embeddable_explorer/public/embeddable_panel_example.tsx index 54cd7c5b5b2c0..98f30632ebf43 100644 --- a/examples/embeddable_explorer/public/embeddable_panel_example.tsx +++ b/examples/embeddable_explorer/public/embeddable_panel_example.tsx @@ -84,7 +84,7 @@ export function EmbeddablePanelExample({ embeddableServices }: Props) { const factory = embeddableServices.getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER); const promise = factory?.create(searchableInput); if (promise) { - promise.then(e => { + promise.then((e) => { if (ref.current) { setEmbeddable(e); } diff --git a/examples/embeddable_explorer/public/todo_embeddable_example.tsx b/examples/embeddable_explorer/public/todo_embeddable_example.tsx index 2af6c713593c6..f43a81c3e7651 100644 --- a/examples/embeddable_explorer/public/todo_embeddable_example.tsx +++ b/examples/embeddable_explorer/public/todo_embeddable_example.tsx @@ -82,7 +82,7 @@ export class TodoEmbeddableExample extends React.Component { icon: 'broom', title: 'Trash', }) - .then(embeddable => { + .then((embeddable) => { this.embeddable = embeddable; this.setState({ loading: false }); }); @@ -135,7 +135,7 @@ export class TodoEmbeddableExample extends React.Component { this.setState({ title: ev.target.value })} + onChange={(ev) => this.setState({ title: ev.target.value })} /> @@ -143,7 +143,7 @@ export class TodoEmbeddableExample extends React.Component { this.setState({ icon: ev.target.value })} + onChange={(ev) => this.setState({ icon: ev.target.value })} /> @@ -153,7 +153,7 @@ export class TodoEmbeddableExample extends React.Component { fullWidth resize="horizontal" data-test-subj="taskTodo" - onChange={ev => this.setState({ task: ev.target.value })} + onChange={(ev) => this.setState({ task: ev.target.value })} /> diff --git a/examples/search_explorer/public/application.tsx b/examples/search_explorer/public/application.tsx index ea6d65d9e2113..a7072936f268d 100644 --- a/examples/search_explorer/public/application.tsx +++ b/examples/search_explorer/public/application.tsx @@ -51,7 +51,7 @@ type NavProps = RouteComponentProps & { }; const Nav = withRouter(({ history, navigateToApp, pages }: NavProps) => { - const navItems = pages.map(page => ({ + const navItems = pages.map((page) => ({ id: page.id, name: page.title, onClick: () => history.push(`/${page.id}`), @@ -103,7 +103,7 @@ const SearchApp = ({ basename, data, application }: SearchBarComponentParams) => ]; const routes = pages.map((page, i) => ( - buildPage(page)} /> + buildPage(page)} /> )); return ( diff --git a/examples/search_explorer/public/async_demo_strategy.tsx b/examples/search_explorer/public/async_demo_strategy.tsx index 40ddcc1f48fe7..9cea556c32d54 100644 --- a/examples/search_explorer/public/async_demo_strategy.tsx +++ b/examples/search_explorer/public/async_demo_strategy.tsx @@ -73,7 +73,7 @@ export class AsyncDemoStrategy extends React.Component { this.setState({ fibonacciNumbers: parseFloat(e.target.value) })} + onChange={(e) => this.setState({ fibonacciNumbers: parseFloat(e.target.value) })} /> diff --git a/examples/search_explorer/public/demo_strategy.tsx b/examples/search_explorer/public/demo_strategy.tsx index 7c6c21d2b7aed..3de6827818e14 100644 --- a/examples/search_explorer/public/demo_strategy.tsx +++ b/examples/search_explorer/public/demo_strategy.tsx @@ -81,7 +81,7 @@ export class DemoStrategy extends React.Component { this.setState({ name: e.target.value })} + onChange={(e) => this.setState({ name: e.target.value })} /> @@ -90,7 +90,7 @@ export class DemoStrategy extends React.Component { this.setState({ mood: e.target.value })} + onChange={(e) => this.setState({ mood: e.target.value })} /> diff --git a/examples/search_explorer/public/do_search.tsx b/examples/search_explorer/public/do_search.tsx index a6b6b9b57db4a..deadb06b16f5f 100644 --- a/examples/search_explorer/public/do_search.tsx +++ b/examples/search_explorer/public/do_search.tsx @@ -61,10 +61,10 @@ export class DoSearch extends React.Component { this.abortController = new AbortController(); this.props.search(this.abortController.signal).subscribe( - response => { + (response) => { this.setState({ response, error: undefined }); }, - error => { + (error) => { this.setState({ error, searching: false, response: undefined }); }, () => { diff --git a/examples/search_explorer/public/es_strategy.tsx b/examples/search_explorer/public/es_strategy.tsx index aaf9dada90341..bc6223c478bf5 100644 --- a/examples/search_explorer/public/es_strategy.tsx +++ b/examples/search_explorer/public/es_strategy.tsx @@ -92,7 +92,7 @@ export class EsSearchTest extends React.Component { this.setState({ index: e.target.value, changes: true })} + onChange={(e) => this.setState({ index: e.target.value, changes: true })} /> @@ -101,7 +101,7 @@ export class EsSearchTest extends React.Component { this.setState({ query: e.target.value, changes: true })} + onChange={(e) => this.setState({ query: e.target.value, changes: true })} /> diff --git a/examples/search_explorer/public/guide_section.tsx b/examples/search_explorer/public/guide_section.tsx index 1562e33b14c2f..c13c11dc5864c 100644 --- a/examples/search_explorer/public/guide_section.tsx +++ b/examples/search_explorer/public/guide_section.tsx @@ -59,7 +59,7 @@ export class GuideSection extends React.Component { } if (props.codeSections) { - props.codeSections.forEach(section => { + props.codeSections.forEach((section) => { this.tabs.push({ name: section.title, displayName: section.title, @@ -79,7 +79,7 @@ export class GuideSection extends React.Component { }; renderTabs() { - return this.tabs.map(tab => ( + return this.tabs.map((tab) => ( this.onSelectedTabChanged(tab.name)} isSelected={tab.name === this.state.selectedTab} @@ -98,7 +98,7 @@ export class GuideSection extends React.Component { if (!this.props.codeSections) { return undefined; } - const section = this.props.codeSections.find(s => s.title === this.state.selectedTab); + const section = this.props.codeSections.find((s) => s.title === this.state.selectedTab); if (!section) { throw new Error('No section named ' + this.state.selectedTab); diff --git a/examples/state_containers_examples/public/todo/todo.tsx b/examples/state_containers_examples/public/todo/todo.tsx index 597c2b19be0f6..b6f4f6550026b 100644 --- a/examples/state_containers_examples/public/todo/todo.tsx +++ b/examples/state_containers_examples/public/todo/todo.tsx @@ -61,7 +61,7 @@ const defaultGlobalState: GlobalState = { text: '' }; const globalStateContainer = createStateContainer( defaultGlobalState, { - setText: state => text => ({ ...state, text }), + setText: (state) => (text) => ({ ...state, text }), } ); @@ -81,7 +81,7 @@ const TodoApp: React.FC = ({ filter }) => { const { text } = GlobalStateHelpers.useState(); const { edit: editTodo, delete: deleteTodo, add: addTodo } = useTransitions(); const todos = useState().todos; - const filteredTodos = todos.filter(todo => { + const filteredTodos = todos.filter((todo) => { if (!filter) return true; if (filter === 'completed') return todo.completed; if (filter === 'not-completed') return !todo.completed; @@ -111,13 +111,13 @@ const TodoApp: React.FC = ({ filter }) => {
    - {filteredTodos.map(todo => ( + {filteredTodos.map((todo) => (
  • { + onChange={(e) => { editTodo({ ...todo, completed: e.target.checked, @@ -139,7 +139,7 @@ const TodoApp: React.FC = ({ filter }) => { ))}
{ + onSubmit={(e) => { const inputRef = (e.target as HTMLFormElement).elements.namedItem( 'newTodo' ) as HTMLInputElement; @@ -147,7 +147,7 @@ const TodoApp: React.FC = ({ filter }) => { addTodo({ text: inputRef.value, completed: false, - id: todos.map(todo => todo.id).reduce((a, b) => Math.max(a, b), 0) + 1, + id: todos.map((todo) => todo.id).reduce((a, b) => Math.max(a, b), 0) + 1, }); inputRef.value = ''; e.preventDefault(); @@ -157,7 +157,7 @@ const TodoApp: React.FC = ({ filter }) => {
- setText(e.target.value)} /> + setText(e.target.value)} />
); @@ -173,7 +173,7 @@ export const TodoAppPage: React.FC<{ appTitle: string; appBasePath: string; isInitialRoute: () => boolean; -}> = props => { +}> = (props) => { const initialAppUrl = React.useRef(window.location.href); const [useHashedUrl, setUseHashedUrl] = React.useState(false); @@ -181,7 +181,7 @@ export const TodoAppPage: React.FC<{ * Replicates what src/legacy/ui/public/chrome/api/nav.ts did * Persists the url in sessionStorage and tries to restore it on "componentDidMount" */ - useUrlTracker(`lastUrlTracker:${props.appInstanceId}`, props.history, urlToRestore => { + useUrlTracker(`lastUrlTracker:${props.appInstanceId}`, props.history, (urlToRestore) => { // shouldRestoreUrl: // App decides if it should restore url or not // In this specific case, restore only if navigated to initial route diff --git a/examples/state_containers_examples/public/with_data_services/components/app.tsx b/examples/state_containers_examples/public/with_data_services/components/app.tsx index c820929d8a61d..baf627fd62a32 100644 --- a/examples/state_containers_examples/public/with_data_services/components/app.tsx +++ b/examples/state_containers_examples/public/with_data_services/components/app.tsx @@ -135,7 +135,7 @@ const App = ({ appStateContainer.set({ ...appState, name: e.target.value })} + onChange={(e) => appStateContainer.set({ ...appState, name: e.target.value })} aria-label="My name" /> @@ -217,7 +217,7 @@ function useAppStateSyncing( stateContainer: { ...appStateContainer, // stateSync utils requires explicit handling of default state ("null") - set: state => state && appStateContainer.set(state), + set: (state) => state && appStateContainer.set(state), }, }); diff --git a/examples/ui_actions_explorer/public/actions/actions.tsx b/examples/ui_actions_explorer/public/actions/actions.tsx index 64a820ab6d194..4ef8d5bf4d9c6 100644 --- a/examples/ui_actions_explorer/public/actions/actions.tsx +++ b/examples/ui_actions_explorer/public/actions/actions.tsx @@ -47,14 +47,14 @@ export interface PhoneContext { export const makePhoneCallAction = createAction({ type: ACTION_CALL_PHONE_NUMBER, getDisplayName: () => 'Call phone number', - execute: async context => alert(`Pretend calling ${context.phone}...`), + execute: async (context) => alert(`Pretend calling ${context.phone}...`), }); export const lookUpWeatherAction = createAction({ type: ACTION_TRAVEL_GUIDE, getIconType: () => 'popout', getDisplayName: () => 'View travel guide', - execute: async context => { + execute: async (context) => { window.open(`https://www.worldtravelguide.net/?s=${context.country}`, '_blank'); }, }); @@ -67,7 +67,7 @@ export const viewInMapsAction = createAction({ type: ACTION_VIEW_IN_MAPS, getIconType: () => 'popout', getDisplayName: () => 'View in maps', - execute: async context => { + execute: async (context) => { window.open(`https://www.google.com/maps/place/${context.country}`, '_blank'); }, }); @@ -90,7 +90,7 @@ function EditUserModal({ const [name, setName] = useState(user.name); return ( - setName(e.target.value)} /> + setName(e.target.value)} /> { update({ ...user, name }); diff --git a/examples/ui_actions_explorer/public/app.tsx b/examples/ui_actions_explorer/public/app.tsx index f08b8bb29bdd3..1b0667962a3c2 100644 --- a/examples/ui_actions_explorer/public/app.tsx +++ b/examples/ui_actions_explorer/public/app.tsx @@ -72,7 +72,7 @@ const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => { from. Using the UI Action and Trigger API makes your plugin extensible by other plugins. Any actions attached to the `HELLO_WORLD_TRIGGER_ID` will show up here!

- setName(e.target.value)} /> + setName(e.target.value)} /> { diff --git a/examples/ui_actions_explorer/public/trigger_context_example.tsx b/examples/ui_actions_explorer/public/trigger_context_example.tsx index 4b88652103966..05a9895d3fac4 100644 --- a/examples/ui_actions_explorer/public/trigger_context_example.tsx +++ b/examples/ui_actions_explorer/public/trigger_context_example.tsx @@ -105,7 +105,7 @@ export function TriggerContextExample({ uiActionsApi }: Props) { ]; const updateUser = (newUser: User, oldName: string) => { - const index = rows.findIndex(u => u.name === oldName); + const index = rows.findIndex((u) => u.name === oldName); const newRows = [...rows]; newRows.splice(index, 1, createRowData(newUser, uiActionsApi, updateUser)); setRows(newRows); diff --git a/examples/url_generators_examples/public/app.tsx b/examples/url_generators_examples/public/app.tsx index c39cd876ea9b1..82f36fa13ea71 100644 --- a/examples/url_generators_examples/public/app.tsx +++ b/examples/url_generators_examples/public/app.tsx @@ -67,7 +67,7 @@ export const Routes: React.FC<{}> = () => { export const LinksExample: React.FC<{ appBasePath: string; -}> = props => { +}> = (props) => { const history = React.useMemo( () => createBrowserHistory({ diff --git a/examples/url_generators_examples/public/url_generator.ts b/examples/url_generators_examples/public/url_generator.ts index f21b1c9295e66..c74ade8bf743d 100644 --- a/examples/url_generators_examples/public/url_generator.ts +++ b/examples/url_generators_examples/public/url_generator.ts @@ -38,7 +38,7 @@ export const createHelloPageLinkGenerator = ( getStartServices: () => Promise<{ appBasePath: string }> ): UrlGeneratorsDefinition => ({ id: HELLO_URL_GENERATOR, - createUrl: async state => { + createUrl: async (state) => { const startServices = await getStartServices(); const appBasePath = startServices.appBasePath; const parsedUrl = url.parse(window.location.href); @@ -72,7 +72,7 @@ export type LegacyHelloLinkGeneratorState = UrlGeneratorState< export const helloPageLinkGeneratorV1: UrlGeneratorsDefinition = { id: HELLO_URL_GENERATOR_V1, isDeprecated: true, - migrate: async state => { + migrate: async (state) => { return { id: HELLO_URL_GENERATOR, state: { firstName: state.name, lastName: '' } }; }, }; diff --git a/examples/url_generators_explorer/public/app.tsx b/examples/url_generators_explorer/public/app.tsx index 77e804ae08c5f..50dd2e075c528 100644 --- a/examples/url_generators_explorer/public/app.tsx +++ b/examples/url_generators_explorer/public/app.tsx @@ -81,7 +81,7 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => { const updateLinks = async () => { const updatedLinks = await Promise.all( - persistedLinks.map(async savedLink => { + persistedLinks.map(async (savedLink) => { const generator = getLinkGenerator(savedLink.id); const link = await generator.createUrl(savedLink.state); return { @@ -109,11 +109,11 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => { { + onChange={(e) => { setFirstName(e.target.value); }} /> - setLastName(e.target.value)} /> + setLastName(e.target.value)} /> setPersistedLinks([ @@ -142,7 +142,7 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => { {buildingLinks ? (
loading...
) : ( - migratedLinks.map(link => ( + migratedLinks.map((link) => ( =10.17.17 <10.20.0", "@types/node-forge": "^0.9.0", "@types/normalize-path": "^3.0.0", - "@types/numeral": "^0.0.26", "@types/opn": "^5.1.0", "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.3.2", @@ -396,8 +395,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.15.0", - "@typescript-eslint/parser": "^2.15.0", + "@typescript-eslint/eslint-plugin": "^2.33.0", + "@typescript-eslint/parser": "^2.33.0", "angular-mocks": "^1.7.9", "archiver": "^3.1.1", "axe-core": "^3.4.1", @@ -408,7 +407,7 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chromedriver": "^81.0.0", + "chromedriver": "^83.0.0", "classnames": "2.2.6", "dedent": "^0.7.0", "delete-empty": "^2.0.0", @@ -417,7 +416,7 @@ "enzyme-adapter-utils": "^1.13.0", "enzyme-to-json": "^3.4.4", "eslint": "^6.8.0", - "eslint-config-prettier": "^6.9.0", + "eslint-config-prettier": "^6.11.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "^1.4.0", "eslint-plugin-cypress": "^2.8.1", @@ -428,7 +427,7 @@ "eslint-plugin-no-unsanitized": "^3.0.2", "eslint-plugin-node": "^11.0.0", "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.17.0", "eslint-plugin-react-hooks": "^2.3.0", "eslint-plugin-react-perf": "^3.2.3", @@ -482,7 +481,7 @@ "pngjs": "^3.4.0", "postcss": "^7.0.26", "postcss-url": "^8.0.0", - "prettier": "^1.19.1", + "prettier": "^2.0.5", "proxyquire": "1.8.0", "react-popper-tooltip": "^2.10.1", "react-textarea-autosize": "^7.1.2", @@ -505,7 +504,7 @@ "zlib": "^1.0.5" }, "engines": { - "node": "10.19.0", + "node": "10.21.0", "yarn": "^1.21.1" } } diff --git a/packages/elastic-datemath/__tests__/index.js b/packages/elastic-datemath/__tests__/index.js index 6afe09b01ef22..8f06ff0ab4aad 100644 --- a/packages/elastic-datemath/__tests__/index.js +++ b/packages/elastic-datemath/__tests__/index.js @@ -36,7 +36,7 @@ function momentClone() { return require('moment'); } -describe('dateMath', function() { +describe('dateMath', function () { // Test each of these intervals when testing relative time const spans = ['s', 'm', 'h', 'd', 'w', 'M', 'y', 'ms']; const anchor = '2014-01-01T06:06:06.666Z'; @@ -45,53 +45,53 @@ describe('dateMath', function() { const format = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; let clock; - describe('errors', function() { - it('should return undefined if passed something falsy', function() { + describe('errors', function () { + it('should return undefined if passed something falsy', function () { expect(dateMath.parse()).to.be(undefined); }); - it('should return undefined if I pass an operator besides [+-/]', function() { + it('should return undefined if I pass an operator besides [+-/]', function () { expect(dateMath.parse('now&1d')).to.be(undefined); }); - it('should return undefined if I pass a unit besides' + spans.toString(), function() { + it('should return undefined if I pass a unit besides' + spans.toString(), function () { expect(dateMath.parse('now+5f')).to.be(undefined); }); - it('should return undefined if rounding unit is not 1', function() { + it('should return undefined if rounding unit is not 1', function () { expect(dateMath.parse('now/2y')).to.be(undefined); expect(dateMath.parse('now/0.5y')).to.be(undefined); }); - it('should not go into an infinite loop when missing a unit', function() { + it('should not go into an infinite loop when missing a unit', function () { expect(dateMath.parse('now-0')).to.be(undefined); expect(dateMath.parse('now-00')).to.be(undefined); expect(dateMath.parse('now-000')).to.be(undefined); }); - describe('forceNow', function() { - it('should throw an Error if passed a string', function() { + describe('forceNow', function () { + it('should throw an Error if passed a string', function () { const fn = () => dateMath.parse('now', { forceNow: '2000-01-01T00:00:00.000Z' }); expect(fn).to.throwError(); }); - it('should throw an Error if passed a moment', function() { + it('should throw an Error if passed a moment', function () { expect(() => dateMath.parse('now', { forceNow: moment() })).to.throwError(); }); - it('should throw an Error if passed an invalid date', function() { + it('should throw an Error if passed an invalid date', function () { expect(() => dateMath.parse('now', { forceNow: new Date('foobar') })).to.throwError(); }); }); }); - describe('objects and strings', function() { + describe('objects and strings', function () { let mmnt; let date; let string; let now; - beforeEach(function() { + beforeEach(function () { clock = sinon.useFakeTimers(unix); now = moment(); mmnt = moment(anchor); @@ -99,61 +99,61 @@ describe('dateMath', function() { string = mmnt.format(format); }); - afterEach(function() { + afterEach(function () { clock.restore(); }); - it('should return the same moment if passed a moment', function() { + it('should return the same moment if passed a moment', function () { expect(dateMath.parse(mmnt)).to.eql(mmnt); }); - it('should return a moment if passed a date', function() { + it('should return a moment if passed a date', function () { expect(dateMath.parse(date).format(format)).to.eql(mmnt.format(format)); }); - it('should return a moment if passed an ISO8601 string', function() { + it('should return a moment if passed an ISO8601 string', function () { expect(dateMath.parse(string).format(format)).to.eql(mmnt.format(format)); }); - it('should return the current time when parsing now', function() { + it('should return the current time when parsing now', function () { expect(dateMath.parse('now').format(format)).to.eql(now.format(format)); }); - it('should use the forceNow parameter when parsing now', function() { + it('should use the forceNow parameter when parsing now', function () { expect(dateMath.parse('now', { forceNow: anchoredDate }).valueOf()).to.eql(unix); }); }); - describe('subtraction', function() { + describe('subtraction', function () { let now; let anchored; - beforeEach(function() { + beforeEach(function () { clock = sinon.useFakeTimers(unix); now = moment(); anchored = moment(anchor); }); - afterEach(function() { + afterEach(function () { clock.restore(); }); - [5, 12, 247].forEach(len => { - spans.forEach(span => { + [5, 12, 247].forEach((len) => { + spans.forEach((span) => { const nowEx = `now-${len}${span}`; const thenEx = `${anchor}||-${len}${span}`; - it('should return ' + len + span + ' ago', function() { + it('should return ' + len + span + ' ago', function () { const parsed = dateMath.parse(nowEx).format(format); expect(parsed).to.eql(now.subtract(len, span).format(format)); }); - it('should return ' + len + span + ' before ' + anchor, function() { + it('should return ' + len + span + ' before ' + anchor, function () { const parsed = dateMath.parse(thenEx).format(format); expect(parsed).to.eql(anchored.subtract(len, span).format(format)); }); - it('should return ' + len + span + ' before forceNow', function() { + it('should return ' + len + span + ' before forceNow', function () { const parsed = dateMath.parse(nowEx, { forceNow: anchoredDate }).valueOf(); expect(parsed).to.eql(anchored.subtract(len, span).valueOf()); }); @@ -161,36 +161,36 @@ describe('dateMath', function() { }); }); - describe('addition', function() { + describe('addition', function () { let now; let anchored; - beforeEach(function() { + beforeEach(function () { clock = sinon.useFakeTimers(unix); now = moment(); anchored = moment(anchor); }); - afterEach(function() { + afterEach(function () { clock.restore(); }); - [5, 12, 247].forEach(len => { - spans.forEach(span => { + [5, 12, 247].forEach((len) => { + spans.forEach((span) => { const nowEx = `now+${len}${span}`; const thenEx = `${anchor}||+${len}${span}`; - it('should return ' + len + span + ' from now', function() { + it('should return ' + len + span + ' from now', function () { expect(dateMath.parse(nowEx).format(format)).to.eql(now.add(len, span).format(format)); }); - it('should return ' + len + span + ' after ' + anchor, function() { + it('should return ' + len + span + ' after ' + anchor, function () { expect(dateMath.parse(thenEx).format(format)).to.eql( anchored.add(len, span).format(format) ); }); - it('should return ' + len + span + ' after forceNow', function() { + it('should return ' + len + span + ' after forceNow', function () { expect(dateMath.parse(nowEx, { forceNow: anchoredDate }).valueOf()).to.eql( anchored.add(len, span).valueOf() ); @@ -199,40 +199,40 @@ describe('dateMath', function() { }); }); - describe('rounding', function() { + describe('rounding', function () { let now; let anchored; - beforeEach(function() { + beforeEach(function () { clock = sinon.useFakeTimers(unix); now = moment(); anchored = moment(anchor); }); - afterEach(function() { + afterEach(function () { clock.restore(); }); - spans.forEach(span => { - it(`should round now to the beginning of the ${span}`, function() { + spans.forEach((span) => { + it(`should round now to the beginning of the ${span}`, function () { expect(dateMath.parse('now/' + span).format(format)).to.eql( now.startOf(span).format(format) ); }); - it(`should round now to the beginning of forceNow's ${span}`, function() { + it(`should round now to the beginning of forceNow's ${span}`, function () { expect(dateMath.parse('now/' + span, { forceNow: anchoredDate }).valueOf()).to.eql( anchored.startOf(span).valueOf() ); }); - it(`should round now to the end of the ${span}`, function() { + it(`should round now to the end of the ${span}`, function () { expect(dateMath.parse('now/' + span, { roundUp: true }).format(format)).to.eql( now.endOf(span).format(format) ); }); - it(`should round now to the end of forceNow's ${span}`, function() { + it(`should round now to the end of forceNow's ${span}`, function () { expect( dateMath.parse('now/' + span, { roundUp: true, forceNow: anchoredDate }).valueOf() ).to.eql(anchored.endOf(span).valueOf()); @@ -240,61 +240,46 @@ describe('dateMath', function() { }); }); - describe('math and rounding', function() { + describe('math and rounding', function () { let now; let anchored; - beforeEach(function() { + beforeEach(function () { clock = sinon.useFakeTimers(unix); now = moment(); anchored = moment(anchor); }); - afterEach(function() { + afterEach(function () { clock.restore(); }); - it('should round to the nearest second with 0 value', function() { + it('should round to the nearest second with 0 value', function () { const val = dateMath.parse('now-0s/s').format(format); expect(val).to.eql(now.startOf('s').format(format)); }); - it('should subtract 17s, rounded to the nearest second', function() { + it('should subtract 17s, rounded to the nearest second', function () { const val = dateMath.parse('now-17s/s').format(format); - expect(val).to.eql( - now - .startOf('s') - .subtract(17, 's') - .format(format) - ); + expect(val).to.eql(now.startOf('s').subtract(17, 's').format(format)); }); - it('should add 555ms, rounded to the nearest millisecond', function() { + it('should add 555ms, rounded to the nearest millisecond', function () { const val = dateMath.parse('now+555ms/ms').format(format); - expect(val).to.eql( - now - .add(555, 'ms') - .startOf('ms') - .format(format) - ); + expect(val).to.eql(now.add(555, 'ms').startOf('ms').format(format)); }); - it('should subtract 555ms, rounded to the nearest second', function() { + it('should subtract 555ms, rounded to the nearest second', function () { const val = dateMath.parse('now-555ms/s').format(format); - expect(val).to.eql( - now - .subtract(555, 'ms') - .startOf('s') - .format(format) - ); + expect(val).to.eql(now.subtract(555, 'ms').startOf('s').format(format)); }); - it('should round weeks to Sunday by default', function() { + it('should round weeks to Sunday by default', function () { const val = dateMath.parse('now-1w/w'); expect(val.isoWeekday()).to.eql(7); }); - it('should round weeks based on the passed moment locale start of week setting', function() { + it('should round weeks based on the passed moment locale start of week setting', function () { const m = momentClone(); // Define a locale, that has Tuesday as beginning of the week m.defineLocale('x-test', { @@ -304,7 +289,7 @@ describe('dateMath', function() { expect(val.isoWeekday()).to.eql(2); }); - it('should round up weeks based on the passed moment locale start of week setting', function() { + it('should round up weeks based on the passed moment locale start of week setting', function () { const m = momentClone(); // Define a locale, that has Tuesday as beginning of the week m.defineLocale('x-test', { @@ -319,7 +304,7 @@ describe('dateMath', function() { expect(val.isoWeekday()).to.eql(3 - 1); }); - it('should round relative to forceNow', function() { + it('should round relative to forceNow', function () { const val = dateMath.parse('now-0s/s', { forceNow: anchoredDate }).valueOf(); expect(val).to.eql(anchored.startOf('s').valueOf()); }); @@ -329,15 +314,15 @@ describe('dateMath', function() { }); }); - describe('used momentjs instance', function() { - it('should use the default moment instance if parameter not specified', function() { + describe('used momentjs instance', function () { + it('should use the default moment instance if parameter not specified', function () { const momentSpy = sinon.spy(moment, 'isMoment'); dateMath.parse('now'); expect(momentSpy.called).to.be(true); momentSpy.restore(); }); - it('should not use default moment instance if parameter is specified', function() { + it('should not use default moment instance if parameter is specified', function () { const m = momentClone(); const momentSpy = sinon.spy(moment, 'isMoment'); const cloneSpy = sinon.spy(m, 'isMoment'); @@ -348,7 +333,7 @@ describe('dateMath', function() { cloneSpy.restore(); }); - it('should work with multiple different instances', function() { + it('should work with multiple different instances', function () { const m1 = momentClone(); const m2 = momentClone(); const m1Spy = sinon.spy(m1, 'isMoment'); @@ -365,7 +350,7 @@ describe('dateMath', function() { m2Spy.restore(); }); - it('should use global instance after passing an instance', function() { + it('should use global instance after passing an instance', function () { const m = momentClone(); const momentSpy = sinon.spy(moment, 'isMoment'); const cloneSpy = sinon.spy(m, 'isMoment'); @@ -382,12 +367,12 @@ describe('dateMath', function() { }); }); - describe('units', function() { - it('should have units descending for unitsDesc', function() { + describe('units', function () { + it('should have units descending for unitsDesc', function () { expect(dateMath.unitsDesc).to.eql(['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms']); }); - it('should have units ascending for unitsAsc', function() { + it('should have units ascending for unitsAsc', function () { expect(dateMath.unitsAsc).to.eql(['ms', 's', 'm', 'h', 'd', 'w', 'M', 'y']); }); }); diff --git a/packages/elastic-datemath/src/index.js b/packages/elastic-datemath/src/index.js index afedad3ef6f72..52ce12ddf7027 100644 --- a/packages/elastic-datemath/src/index.js +++ b/packages/elastic-datemath/src/index.js @@ -34,9 +34,9 @@ const units = Object.keys(unitsMap).sort((a, b) => unitsMap[b].weight - unitsMap const unitsDesc = [...units]; const unitsAsc = [...units].reverse(); -const isDate = d => Object.prototype.toString.call(d) === '[object Date]'; +const isDate = (d) => Object.prototype.toString.call(d) === '[object Date]'; -const isValidDate = d => isDate(d) && !isNaN(d.valueOf()); +const isValidDate = (d) => isDate(d) && !isNaN(d.valueOf()); /* * This is a simplified version of elasticsearch's date parser. diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 624ee4679a3b9..c0f8bf0ecb508 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -39,6 +39,10 @@ module.exports = { to: false, disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` }, + { + from: 'numeral', + to: '@elastic/numeral', + }, { from: '@kbn/elastic-idx', to: false, @@ -52,6 +56,15 @@ module.exports = { from: 'react-router', to: 'react-router-dom', }, + { + from: '@kbn/ui-shared-deps/monaco', + to: '@kbn/monaco', + }, + { + from: 'monaco-editor', + to: false, + disallowedMessage: `Don't import monaco directly, use or add exports to @kbn/monaco` + }, ], ], }, diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 34b1b0fec376f..d9aef63c0115c 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.15.0", - "@typescript-eslint/parser": "^2.15.0", + "@typescript-eslint/eslint-plugin": "^2.33.0", + "@typescript-eslint/parser": "^2.33.0", "babel-eslint": "^10.0.3", "eslint": "^6.8.0", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/kbn-analytics/scripts/build.js b/packages/kbn-analytics/scripts/build.js index bb28c1460c9c2..448d1ca9332f2 100644 --- a/packages/kbn-analytics/scripts/build.js +++ b/packages/kbn-analytics/scripts/build.js @@ -31,7 +31,7 @@ const padRight = (width, str) => run( async ({ log, flags }) => { - await withProcRunner(log, async proc => { + await withProcRunner(log, async (proc) => { log.info('Deleting old output'); await del(BUILD_DIR); @@ -43,7 +43,7 @@ run( log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); await Promise.all([ - ...['web', 'node'].map(subTask => + ...['web', 'node'].map((subTask) => proc.run(padRight(10, `babel:${subTask}`), { cmd: 'babel', args: [ diff --git a/packages/kbn-analytics/src/report.ts b/packages/kbn-analytics/src/report.ts index 58891e48aa3a6..d9303d2d3af1d 100644 --- a/packages/kbn-analytics/src/report.ts +++ b/packages/kbn-analytics/src/report.ts @@ -86,7 +86,7 @@ export class ReportManager { }; } assignReports(newMetrics: Metric | Metric[]) { - wrapArray(newMetrics).forEach(newMetric => this.assignReport(this.report, newMetric)); + wrapArray(newMetrics).forEach((newMetric) => this.assignReport(this.report, newMetric)); return { report: this.report }; } static createMetricKey(metric: Metric): string { diff --git a/packages/kbn-analytics/src/reporter.ts b/packages/kbn-analytics/src/reporter.ts index cbcdf6af63052..b20ddc0e58ba7 100644 --- a/packages/kbn-analytics/src/reporter.ts +++ b/packages/kbn-analytics/src/reporter.ts @@ -115,7 +115,7 @@ export class Reporter { eventNames: string | string[], count?: number ) => { - const metrics = wrapArray(eventNames).map(eventName => { + const metrics = wrapArray(eventNames).map((eventName) => { this.log(`${type} Metric -> (${appName}:${eventName}):`); const report = createUiStatsMetric({ type, appName, eventName, count }); this.log(report); diff --git a/packages/kbn-babel-code-parser/src/code_parser.js b/packages/kbn-babel-code-parser/src/code_parser.js index 9431eb639e2e5..91927780363ac 100644 --- a/packages/kbn-babel-code-parser/src/code_parser.js +++ b/packages/kbn-babel-code-parser/src/code_parser.js @@ -79,7 +79,7 @@ export async function parseEntries(cwd, entries, strategy, results, wasParsed = const sanitizedCwd = cwd || process.cwd(); // Test each entry against canRequire function - const entriesQueue = entries.map(entry => canRequire(entry)); + const entriesQueue = entries.map((entry) => canRequire(entry)); while (entriesQueue.length) { // Get the first element in the queue as diff --git a/packages/kbn-babel-code-parser/src/strategies.js b/packages/kbn-babel-code-parser/src/strategies.js index f116abde9e0e6..2369692ad434b 100644 --- a/packages/kbn-babel-code-parser/src/strategies.js +++ b/packages/kbn-babel-code-parser/src/strategies.js @@ -59,7 +59,7 @@ export async function dependenciesParseStrategy( // Get dependencies from a single file and filter // out node native modules from the result const dependencies = (await parseSingleFile(mainEntry, dependenciesVisitorsGenerator)).filter( - dep => !builtinModules.includes(dep) + (dep) => !builtinModules.includes(dep) ); // Return the list of all the new entries found into diff --git a/packages/kbn-babel-code-parser/src/strategies.test.js b/packages/kbn-babel-code-parser/src/strategies.test.js index dc2c599e81c39..e61c784cdcd54 100644 --- a/packages/kbn-babel-code-parser/src/strategies.test.js +++ b/packages/kbn-babel-code-parser/src/strategies.test.js @@ -84,7 +84,7 @@ describe('Code Parser Strategies', () => { cb(null, `require('./relative_dep')`); }); - canRequire.mockImplementation(entry => { + canRequire.mockImplementation((entry) => { if (entry === `${mockCwd}dep1/relative_dep`) { return `${entry}/index.js`; } diff --git a/packages/kbn-babel-code-parser/src/visitors.js b/packages/kbn-babel-code-parser/src/visitors.js index 30014941d2a27..b159848d424fa 100644 --- a/packages/kbn-babel-code-parser/src/visitors.js +++ b/packages/kbn-babel-code-parser/src/visitors.js @@ -21,7 +21,7 @@ export function dependenciesVisitorsGenerator(dependenciesAcc) { // raw values on require + require.resolve CallExpression: ({ node }) => { // AST check for require expressions - const isRequire = node => { + const isRequire = (node) => { return matches({ callee: { type: 'Identifier', @@ -31,7 +31,7 @@ export function dependenciesVisitorsGenerator(dependenciesAcc) { }; // AST check for require.resolve expressions - const isRequireResolve = node => { + const isRequireResolve = (node) => { return matches({ callee: { type: 'MemberExpression', @@ -66,7 +66,7 @@ export function dependenciesVisitorsGenerator(dependenciesAcc) { // raw values on import ImportDeclaration: ({ node }) => { // AST check for supported import expressions - const isImport = node => { + const isImport = (node) => { return matches({ type: 'ImportDeclaration', source: { @@ -85,7 +85,7 @@ export function dependenciesVisitorsGenerator(dependenciesAcc) { // raw values on export from ExportNamedDeclaration: ({ node }) => { // AST check for supported export from expressions - const isExportFrom = node => { + const isExportFrom = (node) => { return matches({ type: 'ExportNamedDeclaration', source: { @@ -104,7 +104,7 @@ export function dependenciesVisitorsGenerator(dependenciesAcc) { // raw values on export * from ExportAllDeclaration: ({ node }) => { // AST check for supported export * from expressions - const isExportAllFrom = node => { + const isExportAllFrom = (node) => { return matches({ type: 'ExportAllDeclaration', source: { diff --git a/packages/kbn-babel-code-parser/src/visitors.test.js b/packages/kbn-babel-code-parser/src/visitors.test.js index 6a29d144a4dee..d2704fa9dfb72 100644 --- a/packages/kbn-babel-code-parser/src/visitors.test.js +++ b/packages/kbn-babel-code-parser/src/visitors.test.js @@ -21,7 +21,7 @@ import * as parser from '@babel/parser'; import traverse from '@babel/traverse'; import { dependenciesVisitorsGenerator } from './visitors'; -const visitorsApplier = code => { +const visitorsApplier = (code) => { const result = []; traverse( parser.parse(code, { diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index 71c0ae4bff1f9..06342127b0d89 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -10,7 +10,8 @@ "kbn:bootstrap": "yarn build" }, "devDependencies": { - "typescript": "3.7.2" + "typescript": "3.7.2", + "tsd": "^0.7.4" }, "peerDependencies": { "joi": "^13.5.2", diff --git a/packages/kbn-config-schema/src/errors/schema_error.test.ts b/packages/kbn-config-schema/src/errors/schema_error.test.ts index 0f632b781e9a6..d5cbb5a718a6a 100644 --- a/packages/kbn-config-schema/src/errors/schema_error.test.ts +++ b/packages/kbn-config-schema/src/errors/schema_error.test.ts @@ -26,8 +26,8 @@ import { SchemaError } from '.'; export const cleanStack = (stack: string) => stack .split('\n') - .filter(line => !line.includes('node_modules/') && !line.includes('internal/')) - .map(line => { + .filter((line) => !line.includes('node_modules/') && !line.includes('internal/')) + .map((line) => { const parts = /.*\((.*)\).?/.exec(line) || []; if (parts.length === 0) { diff --git a/packages/kbn-config-schema/src/errors/validation_error.ts b/packages/kbn-config-schema/src/errors/validation_error.ts index 2a4f887bc4349..0c86b61ba1e2a 100644 --- a/packages/kbn-config-schema/src/errors/validation_error.ts +++ b/packages/kbn-config-schema/src/errors/validation_error.ts @@ -26,12 +26,12 @@ export class ValidationError extends SchemaError { let message = error.message; if (error instanceof SchemaTypesError) { const indentLevel = level || 0; - const childErrorMessages = error.errors.map(childError => + const childErrorMessages = error.errors.map((childError) => ValidationError.extractMessage(childError, namespace, indentLevel + 1) ); message = `${message}\n${childErrorMessages - .map(childErrorMessage => `${' '.repeat(indentLevel)}- ${childErrorMessage}`) + .map((childErrorMessage) => `${' '.repeat(indentLevel)}- ${childErrorMessage}`) .join('\n')}`; } diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index 5d387f327e58f..2319fe4395e3f 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -44,6 +44,7 @@ import { ObjectType, ObjectTypeOptions, Props, + NullableProps, RecordOfOptions, RecordOfType, StringOptions, @@ -57,7 +58,7 @@ import { StreamType, } from './types'; -export { ObjectType, TypeOf, Type }; +export { ObjectType, TypeOf, Type, Props, NullableProps }; export { ByteSizeValue } from './byte_size_value'; export { SchemaTypeError, ValidationError } from './errors'; export { isConfigSchema } from './typeguards'; diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index f84e14d2f741d..f3756aaf90793 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -42,9 +42,7 @@ function isMap(o: any): o is Map { const anyCustomRule: Rules = { name: 'custom', params: { - validator: Joi.func() - .maxArity(1) - .required(), + validator: Joi.func().maxArity(1).required(), }, validate(params, value, state, options) { let validationResultMessage; diff --git a/packages/kbn-config-schema/src/typeguards/is_config_schema.test.ts b/packages/kbn-config-schema/src/typeguards/is_config_schema.test.ts index e0ef3835ca0a3..485251055d2b8 100644 --- a/packages/kbn-config-schema/src/typeguards/is_config_schema.test.ts +++ b/packages/kbn-config-schema/src/typeguards/is_config_schema.test.ts @@ -47,7 +47,7 @@ describe('isConfigSchema', () => { expect(isConfigSchema(undefined)).toBe(false); expect(isConfigSchema([1, 2, 3])).toBe(false); expect(isConfigSchema({ foo: 'bar' })).toBe(false); - expect(isConfigSchema(function() {})).toBe(false); + expect(isConfigSchema(function () {})).toBe(false); }); it('returns true as long as `__isKbnConfigSchemaType` is true', () => { diff --git a/packages/kbn-config-schema/src/types/array_type.ts b/packages/kbn-config-schema/src/types/array_type.ts index 0df0d44a37951..2fe1fa24222a1 100644 --- a/packages/kbn-config-schema/src/types/array_type.ts +++ b/packages/kbn-config-schema/src/types/array_type.ts @@ -28,10 +28,7 @@ export type ArrayOptions = TypeOptions & { export class ArrayType extends Type { constructor(type: Type, options: ArrayOptions = {}) { - let schema = internals - .array() - .items(type.getSchema().optional()) - .sparse(false); + let schema = internals.array().items(type.getSchema().optional()).sparse(false); if (options.minSize !== undefined) { schema = schema.min(options.minSize); diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index 9db79b8bf9e00..c7900e1923e78 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -29,7 +29,7 @@ export { LiteralType } from './literal_type'; export { MaybeType } from './maybe_type'; export { MapOfOptions, MapOfType } from './map_type'; export { NumberOptions, NumberType } from './number_type'; -export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type'; +export { ObjectType, ObjectTypeOptions, Props, NullableProps, TypeOf } from './object_type'; export { RecordOfOptions, RecordOfType } from './record_type'; export { StreamType } from './stream_type'; export { StringOptions, StringType } from './string_type'; diff --git a/packages/kbn-config-schema/src/types/object_type.test.ts b/packages/kbn-config-schema/src/types/object_type.test.ts index 5ab59d1c02077..334e814aa52e4 100644 --- a/packages/kbn-config-schema/src/types/object_type.test.ts +++ b/packages/kbn-config-schema/src/types/object_type.test.ts @@ -17,6 +17,7 @@ * under the License. */ +import { expectType } from 'tsd'; import { schema } from '..'; import { TypeOf } from './object_type'; @@ -360,17 +361,142 @@ test('handles optional properties', () => { type SchemaType = TypeOf; - let foo: SchemaType = { + expectType({ required: 'foo', - }; - foo = { + }); + expectType({ required: 'hello', optional: undefined, - }; - foo = { + }); + expectType({ required: 'hello', optional: 'bar', - }; + }); +}); + +describe('#extends', () => { + it('allows to extend an existing schema by adding new properties', () => { + const origin = schema.object({ + initial: schema.string(), + }); + + const extended = origin.extends({ + added: schema.number(), + }); + + expect(() => { + extended.validate({ initial: 'foo' }); + }).toThrowErrorMatchingInlineSnapshot( + `"[added]: expected value of type [number] but got [undefined]"` + ); + + expect(() => { + extended.validate({ initial: 'foo', added: 42 }); + }).not.toThrowError(); - expect(foo).toBeDefined(); + expectType>({ + added: 12, + initial: 'foo', + }); + }); + + it('allows to extend an existing schema by removing properties', () => { + const origin = schema.object({ + string: schema.string(), + number: schema.number(), + }); + + const extended = origin.extends({ number: undefined }); + + expect(() => { + extended.validate({ string: 'foo', number: 12 }); + }).toThrowErrorMatchingInlineSnapshot(`"[number]: definition for this key is missing"`); + + expect(() => { + extended.validate({ string: 'foo' }); + }).not.toThrowError(); + + expectType>({ + string: 'foo', + }); + }); + + it('allows to extend an existing schema by overriding an existing properties', () => { + const origin = schema.object({ + string: schema.string(), + mutated: schema.number(), + }); + + const extended = origin.extends({ + mutated: schema.string(), + }); + + expect(() => { + extended.validate({ string: 'foo', mutated: 12 }); + }).toThrowErrorMatchingInlineSnapshot( + `"[mutated]: expected value of type [string] but got [number]"` + ); + + expect(() => { + extended.validate({ string: 'foo', mutated: 'bar' }); + }).not.toThrowError(); + + expectType>({ + string: 'foo', + mutated: 'bar', + }); + }); + + it('properly infer the type from optional properties', () => { + const origin = schema.object({ + original: schema.maybe(schema.string()), + mutated: schema.maybe(schema.number()), + removed: schema.maybe(schema.string()), + }); + + const extended = origin.extends({ + removed: undefined, + mutated: schema.string(), + }); + + expect(() => { + extended.validate({ original: 'foo' }); + }).toThrowErrorMatchingInlineSnapshot( + `"[mutated]: expected value of type [string] but got [undefined]"` + ); + expect(() => { + extended.validate({ original: 'foo' }); + }).toThrowErrorMatchingInlineSnapshot( + `"[mutated]: expected value of type [string] but got [undefined]"` + ); + expect(() => { + extended.validate({ original: 'foo', mutated: 'bar' }); + }).not.toThrowError(); + + expectType>({ + original: 'foo', + mutated: 'bar', + }); + expectType>({ + mutated: 'bar', + }); + }); + + it(`allows to override the original schema's options`, () => { + const origin = schema.object( + { + initial: schema.string(), + }, + { defaultValue: { initial: 'foo' } } + ); + + const extended = origin.extends( + { + added: schema.number(), + }, + { defaultValue: { initial: 'bar', added: 42 } } + ); + + expect(extended.validate(undefined)).toEqual({ initial: 'bar', added: 42 }); + }); }); diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index fee2d02c1bfb9..431b6e905bcd4 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -24,6 +24,8 @@ import { ValidationError } from '../errors'; export type Props = Record>; +export type NullableProps = Record | undefined | null>; + export type TypeOf> = RT['type']; type OptionalProperties = Pick< @@ -47,6 +49,24 @@ export type ObjectResultType

= Readonly< { [K in keyof RequiredProperties

]: TypeOf } >; +type DefinedProperties = Pick< + Base, + { + [Key in keyof Base]: undefined extends Base[Key] ? never : null extends Base[Key] ? never : Key; + }[keyof Base] +>; + +type ExtendedProps

= Omit & + { [K in keyof DefinedProperties]: NP[K] }; + +type ExtendedObjectType

= ObjectType< + ExtendedProps +>; + +type ExtendedObjectTypeOptions

= ObjectTypeOptions< + ExtendedProps +>; + interface UnknownOptions { /** * Options for dealing with unknown keys: @@ -61,10 +81,13 @@ export type ObjectTypeOptions

= TypeOptions extends Type> { - private props: Record; + private props: P; + private options: ObjectTypeOptions

; + private propSchemas: Record; - constructor(props: P, { unknowns = 'forbid', ...typeOptions }: ObjectTypeOptions

= {}) { + constructor(props: P, options: ObjectTypeOptions

= {}) { const schemaKeys = {} as Record; + const { unknowns = 'forbid', ...typeOptions } = options; for (const [key, value] of Object.entries(props)) { schemaKeys[key] = value.getSchema(); } @@ -77,7 +100,93 @@ export class ObjectType

extends Type> .options({ stripUnknown: { objects: unknowns === 'ignore' } }); super(schema, typeOptions); - this.props = schemaKeys; + this.props = props; + this.propSchemas = schemaKeys; + this.options = options; + } + + /** + * Return a new `ObjectType` instance extended with given `newProps` properties. + * Original properties can be deleted from the copy by passing a `null` or `undefined` value for the key. + * + * @example + * How to add a new key to an object schema + * ```ts + * const origin = schema.object({ + * initial: schema.string(), + * }); + * + * const extended = origin.extends({ + * added: schema.number(), + * }); + * ``` + * + * How to remove an existing key from an object schema + * ```ts + * const origin = schema.object({ + * initial: schema.string(), + * toRemove: schema.number(), + * }); + * + * const extended = origin.extends({ + * toRemove: undefined, + * }); + * ``` + * + * How to override the schema's options + * ```ts + * const origin = schema.object({ + * initial: schema.string(), + * }, { defaultValue: { initial: 'foo' }}); + * + * const extended = origin.extends({ + * added: schema.number(), + * }, { defaultValue: { initial: 'foo', added: 'bar' }}); + * + * @remarks + * `extends` only support extending first-level properties. It's currently not possible to perform deep/nested extensions. + * + * ```ts + * const origin = schema.object({ + * foo: schema.string(), + * nested: schema.object({ + * a: schema.string(), + * b: schema.string(), + * }), + * }); + * + * const extended = origin.extends({ + * nested: schema.object({ + * c: schema.string(), + * }), + * }); + * + * // TypeOf is `{ foo: string; nested: { c: string } }` + * ``` + */ + public extends( + newProps: NP, + newOptions?: ExtendedObjectTypeOptions + ): ExtendedObjectType { + const extendedProps = Object.entries({ + ...this.props, + ...newProps, + }).reduce((memo, [key, value]) => { + if (value !== null && value !== undefined) { + return { + ...memo, + [key]: value, + }; + } + return memo; + }, {} as ExtendedProps); + + const extendedOptions = { + ...this.options, + ...newOptions, + } as ExtendedObjectTypeOptions; + + return new ObjectType(extendedProps, extendedOptions); } protected handleError(type: string, { reason, value }: Record) { @@ -95,10 +204,10 @@ export class ObjectType

extends Type> } validateKey(key: string, value: any) { - if (!this.props[key]) { + if (!this.propSchemas[key]) { throw new Error(`${key} is not a valid part of this schema`); } - const { value: validatedValue, error } = this.props[key].validate(value); + const { value: validatedValue, error } = this.propSchemas[key].validate(value); if (error) { throw new ValidationError(error as any, key); } diff --git a/packages/kbn-config-schema/src/types/string_type.ts b/packages/kbn-config-schema/src/types/string_type.ts index 7f49440b8d7e2..cb780bcbbc6bd 100644 --- a/packages/kbn-config-schema/src/types/string_type.ts +++ b/packages/kbn-config-schema/src/types/string_type.ts @@ -36,14 +36,14 @@ export class StringType extends Type { let schema = options.hostname === true ? internals.string().hostname() - : internals.any().custom(value => { + : internals.any().custom((value) => { if (typeof value !== 'string') { return `expected value of type [string] but got [${typeDetect(value)}]`; } }); if (options.minLength !== undefined) { - schema = schema.custom(value => { + schema = schema.custom((value) => { if (value.length < options.minLength!) { return `value has length [${value.length}] but it must have a minimum length of [${options.minLength}].`; } @@ -51,7 +51,7 @@ export class StringType extends Type { } if (options.maxLength !== undefined) { - schema = schema.custom(value => { + schema = schema.custom((value) => { if (value.length > options.maxLength!) { return `value has length [${value.length}] but it must have a maximum length of [${options.maxLength}].`; } diff --git a/packages/kbn-config-schema/src/types/union_type.ts b/packages/kbn-config-schema/src/types/union_type.ts index f4de829204e80..80fa8443e75d0 100644 --- a/packages/kbn-config-schema/src/types/union_type.ts +++ b/packages/kbn-config-schema/src/types/union_type.ts @@ -24,7 +24,7 @@ import { Type, TypeOptions } from './type'; export class UnionType>, T> extends Type { constructor(types: RTS, options?: TypeOptions) { - const schema = internals.alternatives(types.map(type => type.getSchema())); + const schema = internals.alternatives(types.map((type) => type.getSchema())); super(schema, options); } diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts index 4e91289610432..b38a27fdc1b48 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts @@ -145,7 +145,7 @@ export class CiStatsReporter { `failed to reach kibana-ci-stats service [reason=${reason}], retrying in ${attempt} seconds` ); - await new Promise(resolve => setTimeout(resolve, attempt * 1000)); + await new Promise((resolve) => setTimeout(resolve, attempt * 1000)); } } } diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts index 4244006f4a3a3..ea4159de55749 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts @@ -66,7 +66,7 @@ export interface ReqOptions { } const delay = (ms: number) => - new Promise(resolve => { + new Promise((resolve) => { setTimeout(resolve, ms); }); diff --git a/packages/kbn-dev-utils/src/proc_runner/observe_readable.ts b/packages/kbn-dev-utils/src/proc_runner/observe_readable.ts index 1a292aff303af..4951bef91c446 100644 --- a/packages/kbn-dev-utils/src/proc_runner/observe_readable.ts +++ b/packages/kbn-dev-utils/src/proc_runner/observe_readable.ts @@ -33,7 +33,7 @@ export function observeReadable(readable: Readable): Rx.Observable { Rx.fromEvent(readable, 'error').pipe( first(), - mergeMap(err => Rx.throwError(err)) + mergeMap((err) => Rx.throwError(err)) ) ); } diff --git a/packages/kbn-dev-utils/src/proc_runner/proc.ts b/packages/kbn-dev-utils/src/proc_runner/proc.ts index c899293191f2a..59512cbb133b3 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc.ts @@ -118,7 +118,7 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { // observe first error event Rx.fromEvent(childProcess, 'error').pipe( take(1), - mergeMap(err => Rx.throwError(err)) + mergeMap((err) => Rx.throwError(err)) ) ).pipe(share()); @@ -126,7 +126,7 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { observeLines(childProcess.stdout), observeLines(childProcess.stderr) ).pipe( - tap(line => log.write(` ${chalk.gray('proc')} [${chalk.gray(name)}] ${line}`)), + tap((line) => log.write(` ${chalk.gray('proc')} [${chalk.gray(name)}] ${line}`)), share() ); diff --git a/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts b/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts index 1759ca2840c5b..c879b4595b451 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts @@ -50,7 +50,7 @@ export class ProcRunner { constructor(private log: ToolingLog) { this.signalUnsubscribe = exitHook(() => { - this.teardown().catch(error => { + this.teardown().catch((error) => { log.error(`ProcRunner teardown error: ${error.stack}`); }); }); @@ -105,9 +105,9 @@ export class ProcRunner { // wait for process to log matching line await Rx.race( proc.lines$.pipe( - filter(line => wait.test(line)), + filter((line) => wait.test(line)), first(), - catchError(err => { + catchError((err) => { if (err.name !== 'EmptyError') { throw createCliError(`[${name}] exited without matching pattern: ${wait}`); } else { @@ -159,7 +159,7 @@ export class ProcRunner { * @return {Promise} */ async waitForAllToStop() { - await Promise.all(this.procs.map(proc => proc.outcomePromise)); + await Promise.all(this.procs.map((proc) => proc.outcomePromise)); } /** @@ -181,19 +181,19 @@ export class ProcRunner { this.log.warning( '%d processes left running, stop them with procs.stop(name):', this.procs.length, - this.procs.map(proc => proc.name) + this.procs.map((proc) => proc.name) ); } await Promise.all( - this.procs.map(async proc => { + this.procs.map(async (proc) => { await proc.stop(signal === 'exit' ? 'SIGKILL' : signal); }) ); } private getProc(name: string) { - return this.procs.find(proc => { + return this.procs.find((proc) => { return proc.name === name; }); } @@ -209,14 +209,14 @@ export class ProcRunner { // tie into proc outcome$, remove from _procs on compete proc.outcome$.subscribe({ - next: code => { + next: (code) => { const duration = moment.duration(Date.now() - startMs); this.log.info('[%s] exited with %s after %s', name, code, duration.humanize()); }, complete: () => { remove(); }, - error: error => { + error: (error) => { if (this.closing) { this.log.error(error); } diff --git a/packages/kbn-dev-utils/src/proc_runner/with_proc_runner.test.ts b/packages/kbn-dev-utils/src/proc_runner/with_proc_runner.test.ts index e37bdcc40ca1c..89127069f4b8d 100644 --- a/packages/kbn-dev-utils/src/proc_runner/with_proc_runner.test.ts +++ b/packages/kbn-dev-utils/src/proc_runner/with_proc_runner.test.ts @@ -22,14 +22,14 @@ import { withProcRunner } from './with_proc_runner'; import { ProcRunner } from './proc_runner'; it('passes proc runner to a function', async () => { - await withProcRunner(new ToolingLog(), async proc => { + await withProcRunner(new ToolingLog(), async (proc) => { expect(proc).toBeInstanceOf(ProcRunner); }); }); it('calls procRunner.teardown() if function returns synchronously', async () => { let teardownSpy; - await withProcRunner(new ToolingLog(), async proc => { + await withProcRunner(new ToolingLog(), async (proc) => { teardownSpy = jest.spyOn(proc, 'teardown'); }); @@ -41,7 +41,7 @@ it('calls procRunner.teardown() if function throw synchronous error, and rejects let teardownSpy; await expect( - withProcRunner(new ToolingLog(), async proc => { + withProcRunner(new ToolingLog(), async (proc) => { teardownSpy = jest.spyOn(proc, 'teardown'); throw error; }) @@ -53,8 +53,8 @@ it('calls procRunner.teardown() if function throw synchronous error, and rejects it('waits for promise to resolve before tearing down proc', async () => { let teardownSpy; - await withProcRunner(new ToolingLog(), async proc => { - await new Promise(resolve => setTimeout(resolve, 500)); + await withProcRunner(new ToolingLog(), async (proc) => { + await new Promise((resolve) => setTimeout(resolve, 500)); teardownSpy = jest.spyOn(proc, 'teardown'); }); @@ -67,8 +67,8 @@ it('waits for promise to reject before tearing down proc and rejecting with the let teardownSpy; await expect( - withProcRunner(new ToolingLog(), async proc => { - await new Promise(resolve => setTimeout(resolve, 500)); + withProcRunner(new ToolingLog(), async (proc) => { + await new Promise((resolve) => setTimeout(resolve, 500)); teardownSpy = jest.spyOn(proc, 'teardown'); throw error; }) diff --git a/packages/kbn-dev-utils/src/run/fail.ts b/packages/kbn-dev-utils/src/run/fail.ts index a2501fc9513bf..f10ef1d52ef04 100644 --- a/packages/kbn-dev-utils/src/run/fail.ts +++ b/packages/kbn-dev-utils/src/run/fail.ts @@ -61,7 +61,7 @@ export function combineErrors(errors: Array) { .filter(isFailError) .reduce((acc, error) => Math.max(acc, error.exitCode), 1); - const showHelp = errors.some(error => isFailError(error) && error.showHelp); + const showHelp = errors.some((error) => isFailError(error) && error.showHelp); const message = errors.reduce((acc, error) => { if (isFailError(error)) { diff --git a/packages/kbn-dev-utils/src/run/run.ts b/packages/kbn-dev-utils/src/run/run.ts index 35477e988d837..894db0d3fdadb 100644 --- a/packages/kbn-dev-utils/src/run/run.ts +++ b/packages/kbn-dev-utils/src/run/run.ts @@ -62,7 +62,7 @@ export async function run(fn: RunFn, options: Options = {}) { writeTo: process.stdout, }); - process.on('unhandledRejection', error => { + process.on('unhandledRejection', (error) => { log.error('UNHANDLED PROMISE REJECTION'); log.error( error instanceof Error @@ -110,7 +110,7 @@ export async function run(fn: RunFn, options: Options = {}) { } try { - await withProcRunner(log, async procRunner => { + await withProcRunner(log, async (procRunner) => { await fn({ log, flags, diff --git a/packages/kbn-dev-utils/src/tooling_log/tooling_log.test.ts b/packages/kbn-dev-utils/src/tooling_log/tooling_log.test.ts index 21f02325cac66..4a0f5ca5f8a5f 100644 --- a/packages/kbn-dev-utils/src/tooling_log/tooling_log.test.ts +++ b/packages/kbn-dev-utils/src/tooling_log/tooling_log.test.ts @@ -80,29 +80,31 @@ describe('#indent()', () => { }); }); -(['verbose', 'debug', 'info', 'success', 'warning', 'error', 'write'] as const).forEach(method => { - describe(`#${method}()`, () => { - it(`sends a msg of type "${method}" to each writer with indent and arguments`, () => { - const log = new ToolingLog(); - const writeA = jest.fn(); - const writeB = jest.fn(); - - log.setWriters([{ write: writeA }, { write: writeB }]); - - if (method === 'error') { - const error = new Error('error message'); - error.stack = '... stack trace ...'; - log.error(error); - log.error('string message'); - } else { - log[method]('foo', 'bar', 'baz'); - } - - expect(writeA.mock.calls).toMatchSnapshot(); - expect(writeA.mock.calls).toEqual(writeB.mock.calls); +(['verbose', 'debug', 'info', 'success', 'warning', 'error', 'write'] as const).forEach( + (method) => { + describe(`#${method}()`, () => { + it(`sends a msg of type "${method}" to each writer with indent and arguments`, () => { + const log = new ToolingLog(); + const writeA = jest.fn(); + const writeB = jest.fn(); + + log.setWriters([{ write: writeA }, { write: writeB }]); + + if (method === 'error') { + const error = new Error('error message'); + error.stack = '... stack trace ...'; + log.error(error); + log.error('string message'); + } else { + log[method]('foo', 'bar', 'baz'); + } + + expect(writeA.mock.calls).toMatchSnapshot(); + expect(writeA.mock.calls).toEqual(writeB.mock.calls); + }); }); - }); -}); + } +); describe('#getWritten$()', () => { async function testWrittenMsgs(writers: Writer[]) { @@ -110,10 +112,7 @@ describe('#getWritten$()', () => { log.setWriters(writers); const done$ = new Rx.Subject(); - const promise = log - .getWritten$() - .pipe(takeUntil(done$), toArray()) - .toPromise(); + const promise = log.getWritten$().pipe(takeUntil(done$), toArray()).toPromise(); log.debug('foo'); log.info('bar'); diff --git a/packages/kbn-dev-utils/src/tooling_log/tooling_log_collecting_writer.ts b/packages/kbn-dev-utils/src/tooling_log/tooling_log_collecting_writer.ts index 46026bdc369d4..7e79077032156 100644 --- a/packages/kbn-dev-utils/src/tooling_log/tooling_log_collecting_writer.ts +++ b/packages/kbn-dev-utils/src/tooling_log/tooling_log_collecting_writer.ts @@ -26,7 +26,7 @@ export class ToolingLogCollectingWriter extends ToolingLogTextWriter { super({ level: 'verbose', writeTo: { - write: msg => { + write: (msg) => { // trim trailing new line this.messages.push(msg.slice(0, -1)); }, diff --git a/packages/kbn-es/src/artifact.js b/packages/kbn-es/src/artifact.js index 83dcd1cf36d2e..7d4c7a3fd2c33 100644 --- a/packages/kbn-es/src/artifact.js +++ b/packages/kbn-es/src/artifact.js @@ -60,7 +60,7 @@ async function retry(log, fn) { } log.warning('...failure, retrying in 5 seconds:', error.message); - await new Promise(resolve => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 5000)); log.info('...retrying'); return await doAttempt(attempt + 1); } @@ -120,7 +120,7 @@ async function getArtifactSpecForSnapshot(urlVersion, license, log) { const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64'; const archive = manifest.archives.find( - archive => + (archive) => archive.version === desiredVersion && archive.platform === platform && archive.license === desiredLicense && diff --git a/packages/kbn-es/src/artifact.test.js b/packages/kbn-es/src/artifact.test.js index 02e4d5318f63f..bbcf664006046 100644 --- a/packages/kbn-es/src/artifact.test.js +++ b/packages/kbn-es/src/artifact.test.js @@ -52,14 +52,14 @@ const createArchive = (params = {}) => { }; }; -const mockFetch = mock => +const mockFetch = (mock) => fetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(mock)))); const previousEnvVars = {}; const ENV_VARS_TO_RESET = ['ES_SNAPSHOT_MANIFEST', 'KBN_ES_SNAPSHOT_USE_UNVERIFIED']; beforeAll(() => { - ENV_VARS_TO_RESET.forEach(key => { + ENV_VARS_TO_RESET.forEach((key) => { if (key in process.env) { previousEnvVars[key] = process.env[key]; delete process.env[key]; @@ -68,7 +68,7 @@ beforeAll(() => { }); afterAll(() => { - Object.keys(previousEnvVars).forEach(key => { + Object.keys(previousEnvVars).forEach((key) => { process.env[key] = previousEnvVars[key]; }); }); diff --git a/packages/kbn-es/src/cli.js b/packages/kbn-es/src/cli.js index ed81e01ccf8ab..61019d27bf383 100644 --- a/packages/kbn-es/src/cli.js +++ b/packages/kbn-es/src/cli.js @@ -26,7 +26,7 @@ const { log } = require('./utils'); function help() { const availableCommands = Object.keys(commands).map( - name => `${name} - ${commands[name].description}` + (name) => `${name} - ${commands[name].description}` ); console.log(dedent` diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index ceb4a5b6aece1..68bcc37c65600 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -40,8 +40,8 @@ const readFile = util.promisify(fs.readFile); // listen to data on stream until map returns anything but undefined const first = (stream, map) => - new Promise(resolve => { - const onData = data => { + new Promise((resolve) => { + const onData = (data) => { const result = map(data); if (result !== undefined) { resolve(result); @@ -180,7 +180,7 @@ exports.Cluster = class Cluster { await Promise.race([ // wait for native realm to be setup and es to be started Promise.all([ - first(this._process.stdout, data => { + first(this._process.stdout, (data) => { if (/started/.test(data)) { return true; } @@ -207,7 +207,7 @@ exports.Cluster = class Cluster { this._exec(installPath, options); // log native realm setup errors so they aren't uncaught - this._nativeRealmSetup.catch(error => { + this._nativeRealmSetup.catch((error) => { this._log.error(error); this.stop(); }); @@ -287,7 +287,7 @@ exports.Cluster = class Cluster { }); // parse log output to find http port - const httpPort = first(this._process.stdout, data => { + const httpPort = first(this._process.stdout, (data) => { const match = data.toString('utf8').match(/HttpServer.+publish_address {[0-9.]+:([0-9]+)/); if (match) { @@ -296,7 +296,7 @@ exports.Cluster = class Cluster { }); // once the http port is available setup the native realm - this._nativeRealmSetup = httpPort.then(async port => { + this._nativeRealmSetup = httpPort.then(async (port) => { const caCert = await this._caCertPromise; const nativeRealm = new NativeRealm({ port, @@ -309,19 +309,19 @@ exports.Cluster = class Cluster { }); // parse and forward es stdout to the log - this._process.stdout.on('data', data => { + this._process.stdout.on('data', (data) => { const lines = parseEsLog(data.toString()); - lines.forEach(line => { + lines.forEach((line) => { this._log.info(line.formattedMessage); }); }); // forward es stderr to the log - this._process.stderr.on('data', data => this._log.error(chalk.red(data.toString()))); + this._process.stderr.on('data', (data) => this._log.error(chalk.red(data.toString()))); // observe the exit code of the process and reflect in _outcome promies - const exitCode = new Promise(resolve => this._process.once('exit', resolve)); - this._outcome = exitCode.then(code => { + const exitCode = new Promise((resolve) => this._process.once('exit', resolve)); + this._outcome = exitCode.then((code) => { if (this._stopCalled) { return; } diff --git a/packages/kbn-es/src/errors.js b/packages/kbn-es/src/errors.js index 099b5214bcbdb..7b39251f1327c 100644 --- a/packages/kbn-es/src/errors.js +++ b/packages/kbn-es/src/errors.js @@ -17,12 +17,12 @@ * under the License. */ -exports.createCliError = function(message) { +exports.createCliError = function (message) { const error = new Error(message); error.isCliError = true; return error; }; -exports.isCliError = function(error) { +exports.isCliError = function (error) { return error && error.isCliError; }; diff --git a/packages/kbn-es/src/install/source.js b/packages/kbn-es/src/install/source.js index e78e9f1ff4b25..bfeff736f8cdc 100644 --- a/packages/kbn-es/src/install/source.js +++ b/packages/kbn-es/src/install/source.js @@ -99,15 +99,11 @@ async function sourceInfo(cwd, license, log = defaultLog) { etag.update(sha); // for changed files, use last modified times in hash calculation - status.files.forEach(file => { + status.files.forEach((file) => { etag.update(fs.statSync(path.join(cwd, file.path)).mtime.toString()); }); - const cwdHash = crypto - .createHash('md5') - .update(cwd) - .digest('hex') - .substr(0, 8); + const cwdHash = crypto.createHash('md5').update(cwd).digest('hex').substr(0, 8); const basename = `${branch}-${task}-${cwdHash}`; const filename = `${basename}.${ext}`; diff --git a/packages/kbn-es/src/integration_tests/__fixtures__/es_bin.js b/packages/kbn-es/src/integration_tests/__fixtures__/es_bin.js index d374abe5db068..b860664443d1a 100644 --- a/packages/kbn-es/src/integration_tests/__fixtures__/es_bin.js +++ b/packages/kbn-es/src/integration_tests/__fixtures__/es_bin.js @@ -71,7 +71,7 @@ const delayServerClose = () => { server.on('request', delayServerClose); server.on('listening', delayServerClose); -server.listen(0, '127.0.0.1', function() { +server.listen(0, '127.0.0.1', function () { const { port, address: hostname } = server.address(); serverUrl = new URL( formatUrl({ diff --git a/packages/kbn-es/src/integration_tests/cluster.test.js b/packages/kbn-es/src/integration_tests/cluster.test.js index dfbc04477bd40..0ae0ac0aac27a 100644 --- a/packages/kbn-es/src/integration_tests/cluster.test.js +++ b/packages/kbn-es/src/integration_tests/cluster.test.js @@ -37,7 +37,7 @@ jest.mock('../utils/extract_config_files', () => ({ const log = new ToolingLog(); function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } async function ensureNoResolve(promise) { @@ -77,7 +77,7 @@ function mockEsBin({ exitCode, start }) { beforeEach(() => { jest.resetAllMocks(); - extractConfigFiles.mockImplementation(config => config); + extractConfigFiles.mockImplementation((config) => config); }); describe('#installSource()', () => { @@ -85,7 +85,7 @@ describe('#installSource()', () => { let resolveInstallSource; installSource.mockImplementationOnce( () => - new Promise(resolve => { + new Promise((resolve) => { resolveInstallSource = () => { resolve({ installPath: 'foo' }); }; @@ -124,7 +124,7 @@ describe('#installSnapshot()', () => { let resolveInstallSnapshot; installSnapshot.mockImplementationOnce( () => - new Promise(resolve => { + new Promise((resolve) => { resolveInstallSnapshot = () => { resolve({ installPath: 'foo' }); }; @@ -163,7 +163,7 @@ describe('#installArchive(path)', () => { let resolveInstallArchive; installArchive.mockImplementationOnce( () => - new Promise(resolve => { + new Promise((resolve) => { resolveInstallArchive = () => { resolve({ installPath: 'foo' }); }; diff --git a/packages/kbn-es/src/settings.ts b/packages/kbn-es/src/settings.ts index 58eedff207b4d..bf7160b9fee7b 100644 --- a/packages/kbn-es/src/settings.ts +++ b/packages/kbn-es/src/settings.ts @@ -25,7 +25,7 @@ const SECURE_SETTINGS_LIST = [ ]; function isSecureSetting(settingName: string) { - return SECURE_SETTINGS_LIST.some(secureSettingNameRegex => + return SECURE_SETTINGS_LIST.some((secureSettingNameRegex) => secureSettingNameRegex.test(settingName) ); } diff --git a/packages/kbn-es/src/utils/build_snapshot.js b/packages/kbn-es/src/utils/build_snapshot.js index 3173df700e303..ce0dc88ac1d7c 100644 --- a/packages/kbn-es/src/utils/build_snapshot.js +++ b/packages/kbn-es/src/utils/build_snapshot.js @@ -25,7 +25,7 @@ const { createCliError } = require('../errors'); const { findMostRecentlyChanged } = require('../utils'); const { GRADLE_BIN } = require('../paths'); -const onceEvent = (emitter, event) => new Promise(resolve => emitter.once(event, resolve)); +const onceEvent = (emitter, event) => new Promise((resolve) => emitter.once(event, resolve)); /** * Creates archive from source @@ -59,13 +59,13 @@ exports.buildSnapshot = async ({ license, sourcePath, log, platform = os.platfor const stdout = readline.createInterface({ input: build.stdout }); const stderr = readline.createInterface({ input: build.stderr }); - stdout.on('line', line => log.debug(line)); - stderr.on('line', line => log.error(line)); + stdout.on('line', (line) => log.debug(line)); + stderr.on('line', (line) => log.error(line)); const [exitCode] = await Promise.all([ Promise.race([ onceEvent(build, 'exit'), - onceEvent(build, 'error').then(error => { + onceEvent(build, 'error').then((error) => { throw createCliError(`Error spawning gradle: ${error.message}`); }), ]), diff --git a/packages/kbn-es/src/utils/decompress.js b/packages/kbn-es/src/utils/decompress.js index b4299594c5062..1fdb647b1dc0d 100644 --- a/packages/kbn-es/src/utils/decompress.js +++ b/packages/kbn-es/src/utils/decompress.js @@ -50,15 +50,12 @@ function decompressZip(input, output) { resolve(); }); - zipfile.on('error', err => { + zipfile.on('error', (err) => { reject(err); }); - zipfile.on('entry', entry => { - const zipPath = entry.fileName - .split(/\/|\\/) - .slice(1) - .join(path.sep); + zipfile.on('entry', (entry) => { + const zipPath = entry.fileName.split(/\/|\\/).slice(1).join(path.sep); const fileName = path.resolve(output, zipPath); if (/\/$/.test(entry.fileName)) { @@ -83,7 +80,7 @@ function decompressZip(input, output) { }); } -exports.decompress = async function(input, output) { +exports.decompress = async function (input, output) { const ext = path.extname(input); switch (path.extname(input)) { diff --git a/packages/kbn-es/src/utils/extract_config_files.js b/packages/kbn-es/src/utils/extract_config_files.js index a8a44a149e9b3..d535528cef239 100644 --- a/packages/kbn-es/src/utils/extract_config_files.js +++ b/packages/kbn-es/src/utils/extract_config_files.js @@ -32,7 +32,7 @@ exports.extractConfigFiles = function extractConfigFiles(config, dest, options = const originalConfig = typeof config === 'string' ? [config] : config; const localConfig = []; - originalConfig.forEach(prop => { + originalConfig.forEach((prop) => { const [key, value] = prop.split('='); if (isFile(value)) { diff --git a/packages/kbn-es/src/utils/find_most_recently_changed.js b/packages/kbn-es/src/utils/find_most_recently_changed.js index 0fcd87978c357..3ba8865e88d92 100644 --- a/packages/kbn-es/src/utils/find_most_recently_changed.js +++ b/packages/kbn-es/src/utils/find_most_recently_changed.js @@ -32,7 +32,7 @@ exports.findMostRecentlyChanged = function findMostRecentlyChanged(pattern) { throw new TypeError(`Pattern must be absolute, got ${pattern}`); } - const ctime = path => fs.statSync(path).ctime.getTime(); + const ctime = (path) => fs.statSync(path).ctime.getTime(); return glob .sync(pattern) diff --git a/packages/kbn-es/src/utils/find_most_recently_changed.test.js b/packages/kbn-es/src/utils/find_most_recently_changed.test.js index ed90576990c72..ee032686bc621 100644 --- a/packages/kbn-es/src/utils/find_most_recently_changed.test.js +++ b/packages/kbn-es/src/utils/find_most_recently_changed.test.js @@ -18,7 +18,7 @@ */ jest.mock('fs', () => ({ - statSync: jest.fn().mockImplementation(path => { + statSync: jest.fn().mockImplementation((path) => { if (path.includes('oldest')) { return { ctime: new Date(2018, 2, 1), diff --git a/packages/kbn-es/src/utils/native_realm.js b/packages/kbn-es/src/utils/native_realm.js index f3f5f7bbdf431..573944a8cc6d0 100644 --- a/packages/kbn-es/src/utils/native_realm.js +++ b/packages/kbn-es/src/utils/native_realm.js @@ -77,7 +77,7 @@ exports.NativeRealm = class NativeRealm { const reservedUsers = await this.getReservedUsers(); await Promise.all( - reservedUsers.map(async user => { + reservedUsers.map(async (user) => { await this.setPassword(user, options[`password.${user}`]); }) ); @@ -87,7 +87,7 @@ exports.NativeRealm = class NativeRealm { return await this._autoRetry(async () => { const resp = await this._client.security.getUser(); const usernames = Object.keys(resp.body).filter( - user => resp.body[user].metadata._reserved === true + (user) => resp.body[user].metadata._reserved === true ); if (!usernames?.length) { @@ -125,7 +125,7 @@ exports.NativeRealm = class NativeRealm { const sec = 1.5 * attempt; this._log.warning(`assuming ES isn't initialized completely, trying again in ${sec} seconds`); - await new Promise(resolve => setTimeout(resolve, sec * 1000)); + await new Promise((resolve) => setTimeout(resolve, sec * 1000)); return await this._autoRetry(fn, attempt + 1); } } diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_is_path_request.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_is_path_request.js index e26660f76a550..8472aaf0fc029 100644 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_is_path_request.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_is_path_request.js @@ -31,6 +31,6 @@ // const PATH_IMPORT_RE = /^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/; -exports.getIsPathRequest = function(source) { +exports.getIsPathRequest = function (source) { return PATH_IMPORT_RE.test(source); }; diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_kibana_path.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_kibana_path.js index 93c0f907d628d..3856281d15320 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_kibana_path.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_kibana_path.js @@ -26,7 +26,7 @@ const DEFAULT_PLUGIN_PATH = '../..'; /* * Resolves the path to Kibana, either from default setting or config */ -exports.getKibanaPath = function(config, projectRoot) { +exports.getKibanaPath = function (config, projectRoot) { const inConfig = config != null && config.kibanaPath; // We only allow `.` in the config as we need it for Kibana itself diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_path_type.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_path_type.js index 3fb5b5dab7776..445a3cad385fc 100644 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_path_type.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_path_type.js @@ -49,10 +49,10 @@ function getPathType(path) { return type; } -exports.isDirectory = function(path) { +exports.isDirectory = function (path) { return getPathType(path) === DIR; }; -exports.isFile = function(path) { +exports.isFile = function (path) { return getPathType(path) === FILE; }; diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_plugins.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_plugins.js index 319b959883a8a..84481783b22fc 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_plugins.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_plugins.js @@ -21,8 +21,8 @@ const { dirname, resolve } = require('path'); const glob = require('glob-all'); -exports.getPlugins = function(config, kibanaPath, projectRoot) { - const resolveToRoot = path => resolve(projectRoot, path); +exports.getPlugins = function (config, kibanaPath, projectRoot) { + const resolveToRoot = (path) => resolve(projectRoot, path); const pluginDirs = [ ...(config.pluginDirs || []).map(resolveToRoot), @@ -39,11 +39,11 @@ exports.getPlugins = function(config, kibanaPath, projectRoot) { ]; const globPatterns = [ - ...pluginDirs.map(dir => resolve(dir, '*/package.json')), - ...pluginPaths.map(path => resolve(path, 'package.json')), + ...pluginDirs.map((dir) => resolve(dir, '*/package.json')), + ...pluginPaths.map((path) => resolve(path, 'package.json')), ]; - const pluginsFromMap = Object.keys(config.pluginMap || {}).map(name => { + const pluginsFromMap = Object.keys(config.pluginMap || {}).map((name) => { const directory = resolveToRoot(config.pluginMap[name]); return { name, @@ -53,7 +53,7 @@ exports.getPlugins = function(config, kibanaPath, projectRoot) { }); return pluginsFromMap.concat( - glob.sync(globPatterns).map(pkgJsonPath => { + glob.sync(globPatterns).map((pkgJsonPath) => { const path = dirname(pkgJsonPath); const pkg = require(pkgJsonPath); // eslint-disable-line import/no-dynamic-require return { diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_project_root.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_project_root.js index 5c70d63bf147b..fed40298d513f 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_project_root.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_project_root.js @@ -55,7 +55,7 @@ function getRootPackageDir(dirRoot, dir, rootPackageName) { } } -exports.getProjectRoot = function(file, config) { +exports.getProjectRoot = function (file, config) { const { root, dir } = parse(resolve(file)); const { rootPackageName } = config; diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js index da0b799b338ed..6cb2f3d2901d3 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js @@ -22,7 +22,7 @@ const { resolve } = require('path'); const { debug } = require('./debug'); const { getPlugins } = require('./get_plugins'); -exports.getWebpackConfig = function(kibanaPath, projectRoot, config) { +exports.getWebpackConfig = function (kibanaPath, projectRoot, config) { const fromKibana = (...path) => resolve(kibanaPath, ...path); const alias = { @@ -39,7 +39,7 @@ exports.getWebpackConfig = function(kibanaPath, projectRoot, config) { test_utils: fromKibana('src/test_utils/public'), }; - getPlugins(config, kibanaPath, projectRoot).forEach(plugin => { + getPlugins(config, kibanaPath, projectRoot).forEach((plugin) => { alias[`plugins/${plugin.name}`] = plugin.publicDirectory; }); diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/is_probably_webpack_shim.js b/packages/kbn-eslint-import-resolver-kibana/lib/is_probably_webpack_shim.js index 2af7d1c2f5349..9eb3234fca7b4 100644 --- a/packages/kbn-eslint-import-resolver-kibana/lib/is_probably_webpack_shim.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/is_probably_webpack_shim.js @@ -32,8 +32,8 @@ function readShimNames(shimDirectory) { } return readdirSync(shimDirectory) - .filter(name => !name.startsWith('.') && !name.startsWith('_')) - .map(name => (name.endsWith('.js') ? name.slice(0, -3) : name)); + .filter((name) => !name.startsWith('.') && !name.startsWith('_')) + .map((name) => (name.endsWith('.js') ? name.slice(0, -3) : name)); } function findRelativeWebpackShims(directory) { @@ -53,7 +53,7 @@ function findRelativeWebpackShims(directory) { return allShims; } -exports.isProbablyWebpackShim = function(source, file) { +exports.isProbablyWebpackShim = function (source, file) { const shims = findRelativeWebpackShims(dirname(file)); - return shims.some(shim => source === shim || source.startsWith(shim + '/')); + return shims.some((shim) => source === shim || source.startsWith(shim + '/')); }; diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/resolve_webpack_alias.js b/packages/kbn-eslint-import-resolver-kibana/lib/resolve_webpack_alias.js index a7bb391f9b1c6..00b07f469bd9c 100644 --- a/packages/kbn-eslint-import-resolver-kibana/lib/resolve_webpack_alias.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/resolve_webpack_alias.js @@ -25,7 +25,7 @@ * @param {Array<[alias,path]>} aliasEntries * @return {string|undefined} */ -exports.resolveWebpackAlias = function(source, aliasEntries) { +exports.resolveWebpackAlias = function (source, aliasEntries) { for (const [alias, path] of aliasEntries) { if (source === alias) { return path; diff --git a/packages/kbn-eslint-plugin-eslint/lib.js b/packages/kbn-eslint-plugin-eslint/lib.js index 56684746c479f..a7431be00e054 100644 --- a/packages/kbn-eslint-plugin-eslint/lib.js +++ b/packages/kbn-eslint-plugin-eslint/lib.js @@ -31,7 +31,7 @@ exports.normalizeWhitespace = function normalizeWhitespace(string) { return string.replace(/\s+/g, ' '); }; -exports.init = function(context, program, initStep) { +exports.init = function (context, program, initStep) { try { return initStep(); } catch (error) { diff --git a/packages/kbn-eslint-plugin-eslint/rules/disallow_license_headers.js b/packages/kbn-eslint-plugin-eslint/rules/disallow_license_headers.js index 0567307d18968..6b5564f1f8004 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/disallow_license_headers.js +++ b/packages/kbn-eslint-plugin-eslint/rules/disallow_license_headers.js @@ -39,7 +39,7 @@ module.exports = { }, ], }, - create: context => { + create: (context) => { return { Program(program) { const licenses = init(context, program, () => { @@ -70,8 +70,8 @@ module.exports = { sourceCode .getAllComments() - .filter(node => licenses.includes(normalizeWhitespace(node.value))) - .forEach(node => { + .filter((node) => licenses.includes(normalizeWhitespace(node.value))) + .forEach((node) => { context.report({ node, message: 'This license header is not allowed in this file.', diff --git a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js index 8119d338ee536..6027a939f1a65 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js +++ b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js @@ -22,7 +22,7 @@ const KIBANA_ROOT = path.resolve(__dirname, '../../..'); function checkModuleNameNode(context, mappings, node) { const mapping = mappings.find( - mapping => mapping.from === node.value || node.value.startsWith(`${mapping.from}/`) + (mapping) => mapping.from === node.value || node.value.startsWith(`${mapping.from}/`) ); if (!mapping) { @@ -105,7 +105,7 @@ module.exports = { }, ], }, - create: context => { + create: (context) => { const mappings = context.options[0]; return { diff --git a/packages/kbn-eslint-plugin-eslint/rules/require_license_header.js b/packages/kbn-eslint-plugin-eslint/rules/require_license_header.js index f3c9fcef1985e..915ac3ed7922e 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/require_license_header.js +++ b/packages/kbn-eslint-plugin-eslint/rules/require_license_header.js @@ -40,10 +40,10 @@ module.exports = { }, ], }, - create: context => { + create: (context) => { return { Program(program) { - const license = init(context, program, function() { + const license = init(context, program, function () { const options = context.options[0] || {}; const license = options.license; @@ -69,7 +69,7 @@ module.exports = { const sourceCode = context.getSourceCode(); const comment = sourceCode .getAllComments() - .find(node => normalizeWhitespace(node.value) === license.nodeValue); + .find((node) => normalizeWhitespace(node.value) === license.nodeValue); // no licence comment if (!comment) { diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index 0764451c74575..62e1a35f00399 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -31,7 +31,7 @@ const padRight = (width, str) => run( async ({ log, flags }) => { - await withProcRunner(log, async proc => { + await withProcRunner(log, async (proc) => { log.info('Deleting old output'); await del(BUILD_DIR); @@ -43,7 +43,7 @@ run( log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); await Promise.all([ - ...['web', 'node'].map(subTask => + ...['web', 'node'].map((subTask) => proc.run(padRight(10, `babel:${subTask}`), { cmd: 'babel', args: [ diff --git a/packages/kbn-i18n/src/angular/filter.test.ts b/packages/kbn-i18n/src/angular/filter.test.ts index 78bc279689357..5336926a64139 100644 --- a/packages/kbn-i18n/src/angular/filter.test.ts +++ b/packages/kbn-i18n/src/angular/filter.test.ts @@ -28,17 +28,14 @@ import * as i18n from '../core/i18n'; import { i18nFilter as angularI18nFilter } from './filter'; import { I18nProvider, I18nServiceType } from './provider'; -angular - .module('app', []) - .provider('i18n', I18nProvider) - .filter('i18n', angularI18nFilter); +angular.module('app', []).provider('i18n', I18nProvider).filter('i18n', angularI18nFilter); describe('i18nFilter', () => { let filter: I18nServiceType; beforeEach(angular.mock.module('app')); beforeEach( - angular.mock.inject(i18nFilter => { + angular.mock.inject((i18nFilter) => { filter = i18nFilter; }) ); diff --git a/packages/kbn-i18n/src/loader.ts b/packages/kbn-i18n/src/loader.ts index 21f540f588f46..8231ed36928d8 100644 --- a/packages/kbn-i18n/src/loader.ts +++ b/packages/kbn-i18n/src/loader.ts @@ -127,7 +127,7 @@ export function getRegisteredLocales() { */ export async function getTranslationsByLocale(locale: string): Promise { const files = translationsRegistry[locale] || []; - const notLoadedFiles = files.filter(file => !loadedFiles[file]); + const notLoadedFiles = files.filter((file) => !loadedFiles[file]); if (notLoadedFiles.length) { await loadAndCacheFiles(notLoadedFiles); diff --git a/packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx b/packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx index db879fbae6ff1..3271ae7c98d2f 100644 --- a/packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx +++ b/packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx @@ -37,7 +37,7 @@ function translateFormattedMessageUsingPseudoLocale(message: string) { if (formattedMessageDelimiter !== null) { return message .split(formattedMessageDelimiter[0]) - .map(part => (part.startsWith('ELEMENT-') ? part : translateUsingPseudoLocale(part))) + .map((part) => (part.startsWith('ELEMENT-') ? part : translateUsingPseudoLocale(part))) .join(formattedMessageDelimiter[0]); } diff --git a/packages/kbn-interpreter/src/common/lib/arg.js b/packages/kbn-interpreter/src/common/lib/arg.js index 0aa2b52e35acb..2ab74e5035866 100644 --- a/packages/kbn-interpreter/src/common/lib/arg.js +++ b/packages/kbn-interpreter/src/common/lib/arg.js @@ -30,7 +30,7 @@ export function Arg(config) { this.multi = config.multi == null ? false : config.multi; this.resolve = config.resolve == null ? true : config.resolve; this.options = config.options || []; - this.accepts = type => { + this.accepts = (type) => { if (!this.types.length) return true; return includes(config.types, type); }; diff --git a/packages/kbn-interpreter/src/common/lib/ast.js b/packages/kbn-interpreter/src/common/lib/ast.js index 61cfe94ac955c..98123f475cd92 100644 --- a/packages/kbn-interpreter/src/common/lib/ast.js +++ b/packages/kbn-interpreter/src/common/lib/ast.js @@ -55,7 +55,7 @@ function getExpressionArgs(block, level = 0) { const argKeys = Object.keys(args); const MAX_LINE_LENGTH = 80; // length before wrapping arguments - return argKeys.map(argKey => + return argKeys.map((argKey) => args[argKey].reduce((acc, arg) => { const argString = getArgumentString(arg, argKey, level); const lineLength = acc.split('\n').pop().length; @@ -86,7 +86,7 @@ function getExpression(chain, level = 0) { const separator = level > 0 ? ' | ' : '\n| '; return chain - .map(chainObj => { + .map((chainObj) => { const type = getType(chainObj); if (type === 'function') { diff --git a/packages/kbn-interpreter/src/common/lib/fn.js b/packages/kbn-interpreter/src/common/lib/fn.js index c6b2fcbe67799..5561c08f9c7d0 100644 --- a/packages/kbn-interpreter/src/common/lib/fn.js +++ b/packages/kbn-interpreter/src/common/lib/fn.js @@ -39,7 +39,7 @@ export function Fn(config) { this.context = config.context || {}; - this.accepts = type => { + this.accepts = (type) => { if (!this.context.types) return true; // If you don't tell us about context, we'll assume you don't care what you get return includes(this.context.types, type); // Otherwise, check it }; diff --git a/packages/kbn-interpreter/src/common/lib/get_by_alias.js b/packages/kbn-interpreter/src/common/lib/get_by_alias.js index d7bb1bbf9e79d..04c435216b946 100644 --- a/packages/kbn-interpreter/src/common/lib/get_by_alias.js +++ b/packages/kbn-interpreter/src/common/lib/get_by_alias.js @@ -26,7 +26,7 @@ export function getByAlias(specs, name) { const lowerCaseName = name.toLowerCase(); return Object.values(specs).find(({ name, aliases }) => { if (name.toLowerCase() === lowerCaseName) return true; - return (aliases || []).some(alias => { + return (aliases || []).some((alias) => { return alias.toLowerCase() === lowerCaseName; }); }); diff --git a/packages/kbn-interpreter/src/common/lib/registry.js b/packages/kbn-interpreter/src/common/lib/registry.js index 3b22704b9e9c8..25b122f400711 100644 --- a/packages/kbn-interpreter/src/common/lib/registry.js +++ b/packages/kbn-interpreter/src/common/lib/registry.js @@ -48,7 +48,7 @@ export class Registry { } toArray() { - return Object.keys(this._indexed).map(key => this.get(key)); + return Object.keys(this._indexed).map((key) => this.get(key)); } get(name) { diff --git a/packages/kbn-interpreter/src/common/registries.js b/packages/kbn-interpreter/src/common/registries.js index 2c68f5647ca73..9d73433bb2d26 100644 --- a/packages/kbn-interpreter/src/common/registries.js +++ b/packages/kbn-interpreter/src/common/registries.js @@ -24,7 +24,7 @@ * @param {*} newRegistries - The new set of registries */ export function addRegistries(registries, newRegistries) { - Object.keys(newRegistries).forEach(registryName => { + Object.keys(newRegistries).forEach((registryName) => { if (registries[registryName]) { throw new Error(`There is already a registry named "${registryName}".`); } @@ -41,7 +41,7 @@ export function addRegistries(registries, newRegistries) { * @param {*} specs - The specs to be regsitered (e.g. { types: [], browserFunctions: [] }) */ export function register(registries, specs) { - Object.keys(specs).forEach(registryName => { + Object.keys(specs).forEach((registryName) => { if (!registries[registryName]) { throw new Error(`There is no registry named "${registryName}".`); } @@ -49,7 +49,7 @@ export function register(registries, specs) { if (!registries[registryName].register) { throw new Error(`Registry "${registryName}" must have a register function.`); } - specs[registryName].forEach(f => registries[registryName].register(f)); + specs[registryName].forEach((f) => registries[registryName].register(f)); }); return registries; diff --git a/packages/kbn-interpreter/tasks/build/cli.js b/packages/kbn-interpreter/tasks/build/cli.js index 86df21ee566ac..970e0f8847882 100644 --- a/packages/kbn-interpreter/tasks/build/cli.js +++ b/packages/kbn-interpreter/tasks/build/cli.js @@ -56,7 +56,7 @@ if (flags.help) { process.exit(); } -withProcRunner(log, async proc => { +withProcRunner(log, async (proc) => { log.info('Deleting old output'); await del(BUILD_DIR); @@ -87,7 +87,7 @@ withProcRunner(log, async proc => { ]); log.success('Complete'); -}).catch(error => { +}).catch((error) => { log.error(error); process.exit(1); }); diff --git a/packages/kbn-monaco/README.md b/packages/kbn-monaco/README.md new file mode 100644 index 0000000000000..c5d7dd7dbfed5 --- /dev/null +++ b/packages/kbn-monaco/README.md @@ -0,0 +1,5 @@ +# @kbn/monaco + +A customized version of monaco that is automatically configured the way we want it to be when imported as `@kbn/monaco`. Additionally, imports to this package are automatically shared with all plugins using `@kbn/ui-shared-deps`. + +Includes custom xjson language support. The `es_ui_shared` plugin has an example of how to use it, in the future we will likely expose helpers from this package to make using it everywhere a little more seamless. \ No newline at end of file diff --git a/packages/kbn-monaco/package.json b/packages/kbn-monaco/package.json new file mode 100644 index 0000000000000..170c014e6e326 --- /dev/null +++ b/packages/kbn-monaco/package.json @@ -0,0 +1,27 @@ +{ + "name": "@kbn/monaco", + "version": "1.0.0", + "private": true, + "main": "./target/index.js", + "license": "Apache-2.0", + "scripts": { + "build": "node ./scripts/build.js", + "kbn:bootstrap": "yarn build --dev" + }, + "dependencies": { + "regenerator-runtime": "^0.13.3", + "monaco-editor": "~0.17.0" + }, + "devDependencies": { + "@kbn/babel-preset": "1.0.0", + "@kbn/dev-utils": "1.0.0", + "babel-loader": "^8.0.6", + "css-loader": "^3.4.2", + "del": "^5.1.0", + "raw-loader": "3.1.0", + "supports-color": "^7.0.0", + "typescript": "3.7.2", + "webpack": "^4.41.5", + "webpack-cli": "^3.3.10" + } +} diff --git a/packages/kbn-monaco/scripts/build.js b/packages/kbn-monaco/scripts/build.js new file mode 100644 index 0000000000000..c5540e3c956c8 --- /dev/null +++ b/packages/kbn-monaco/scripts/build.js @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const path = require('path'); +const del = require('del'); +const supportsColor = require('supports-color'); +const { run } = require('@kbn/dev-utils'); + +const TARGET_BUILD_DIR = path.resolve(__dirname, '../target'); +const ROOT_DIR = path.resolve(__dirname, '../'); +const WEBPACK_CONFIG_PATH = path.resolve(ROOT_DIR, 'webpack.config.js'); + +run( + async ({ procRunner, log, flags }) => { + log.info('Deleting old output'); + + await del(TARGET_BUILD_DIR); + + const cwd = ROOT_DIR; + const env = { ...process.env }; + if (supportsColor.stdout) { + env.FORCE_COLOR = 'true'; + } + + await procRunner.run('worker', { + cmd: 'webpack', + args: ['--config', WEBPACK_CONFIG_PATH, flags.dev ? '--env.dev' : '--env.prod'], + wait: true, + env, + cwd, + }); + + await procRunner.run('tsc ', { + cmd: 'tsc', + args: [], + wait: true, + env, + cwd, + }); + + log.success('Complete'); + }, + { + flags: { + boolean: ['dev'], + }, + } +); diff --git a/packages/kbn-monaco/src/index.ts b/packages/kbn-monaco/src/index.ts new file mode 100644 index 0000000000000..9213a1bfe1327 --- /dev/null +++ b/packages/kbn-monaco/src/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { monaco } from './monaco'; +export { XJsonLang } from './xjson'; + +/* eslint-disable-next-line @kbn/eslint/module_migration */ +import * as BarePluginApi from 'monaco-editor/esm/vs/editor/editor.api'; +export { BarePluginApi }; diff --git a/packages/kbn-ui-shared-deps/monaco.ts b/packages/kbn-monaco/src/monaco.ts similarity index 96% rename from packages/kbn-ui-shared-deps/monaco.ts rename to packages/kbn-monaco/src/monaco.ts index 42801c69a3e2c..a40b2212ef0e2 100644 --- a/packages/kbn-ui-shared-deps/monaco.ts +++ b/packages/kbn-monaco/src/monaco.ts @@ -17,6 +17,8 @@ * under the License. */ +/* eslint-disable @kbn/eslint/module_migration */ + import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import 'monaco-editor/esm/vs/base/common/worker/simpleWorker'; diff --git a/packages/kbn-monaco/src/xjson/README.md b/packages/kbn-monaco/src/xjson/README.md new file mode 100644 index 0000000000000..8652d0bd776d2 --- /dev/null +++ b/packages/kbn-monaco/src/xjson/README.md @@ -0,0 +1,28 @@ +# README + +This folder contains the language definitions for XJSON used by the Monaco editor. + +## Summary of contents + +Note: All source code. + +### ./worker + +The worker proxy and worker instantiation code used in both the main thread and the worker thread. + +### ./lexer_rules + +Contains the Monarch-specific language tokenization rules for XJSON. Each set of rules registers itself against monaco. + +### ./constants.ts + +Contains the unique language ID. + +### ./language + +Takes care of global setup steps for the language (like registering it against Monaco) and exports a way to load up +the grammar parser. + +### ./worker_proxy_service + +A stateful mechanism for holding a reference to the Monaco-provided proxy getter. diff --git a/packages/kbn-monaco/src/xjson/constants.ts b/packages/kbn-monaco/src/xjson/constants.ts new file mode 100644 index 0000000000000..dc107abff4ffe --- /dev/null +++ b/packages/kbn-monaco/src/xjson/constants.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const ID = 'xjson'; diff --git a/packages/kbn-monaco/src/xjson/grammar.ts b/packages/kbn-monaco/src/xjson/grammar.ts new file mode 100644 index 0000000000000..e95059f9ece2d --- /dev/null +++ b/packages/kbn-monaco/src/xjson/grammar.ts @@ -0,0 +1,213 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export enum AnnoTypes { + error = 'error', + warning = 'warning', +} + +/* eslint-disable */ + +export const createParser = () => { + 'use strict'; + let at: any, + annos: any[], // annotations + ch: any, + text: any, + value: any, + escapee: any = { + '"': '"', + '\\': '\\', + '/': '/', + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: ' ', + }, + error = function (m: string) { + throw { + at: at, + text: m, + message: m, + }; + }, + warning = function (m: string, idx: number) { + annos.push({ + type: AnnoTypes.warning, + at: idx, + text: m, + }); + }, + reset = function (newAt: number) { + ch = text.charAt(newAt); + at = newAt + 1; + }, + next = function (c?: string) { + return ( + c && c !== ch && error("Expected '" + c + "' instead of '" + ch + "'"), + (ch = text.charAt(at)), + (at += 1), + ch + ); + }, + nextUpTo = function (upTo: any, errorMessage: string) { + let currentAt = at, + i = text.indexOf(upTo, currentAt); + if (i < 0) { + error(errorMessage || "Expected '" + upTo + "'"); + } + reset(i + upTo.length); + return text.substring(currentAt, i); + }, + peek = function (c: string) { + return text.substr(at, c.length) === c; // nocommit - double check + }, + number = function () { + var number, + string = ''; + for ('-' === ch && ((string = '-'), next('-')); ch >= '0' && '9' >= ch; ) + (string += ch), next(); + if ('.' === ch) for (string += '.'; next() && ch >= '0' && '9' >= ch; ) string += ch; + if ('e' === ch || 'E' === ch) + for ( + string += ch, next(), ('-' === ch || '+' === ch) && ((string += ch), next()); + ch >= '0' && '9' >= ch; + + ) + (string += ch), next(); + return (number = +string), isNaN(number) ? (error('Bad number'), void 0) : number; + }, + string = function () { + let hex: any, + i: any, + uffff: any, + string = ''; + if ('"' === ch) { + if (peek('""')) { + // literal + next('"'); + next('"'); + return nextUpTo('"""', 'failed to find closing \'"""\''); + } else { + for (; next(); ) { + if ('"' === ch) return next(), string; + if ('\\' === ch) + if ((next(), 'u' === ch)) { + for ( + uffff = 0, i = 0; + 4 > i && ((hex = parseInt(next(), 16)), isFinite(hex)); + i += 1 + ) + uffff = 16 * uffff + hex; + string += String.fromCharCode(uffff); + } else { + if ('string' != typeof escapee[ch]) break; + string += escapee[ch]; + } + else string += ch; + } + } + } + error('Bad string'); + }, + white = function () { + for (; ch && ' ' >= ch; ) next(); + }, + word = function () { + switch (ch) { + case 't': + return next('t'), next('r'), next('u'), next('e'), !0; + case 'f': + return next('f'), next('a'), next('l'), next('s'), next('e'), !1; + case 'n': + return next('n'), next('u'), next('l'), next('l'), null; + } + error("Unexpected '" + ch + "'"); + }, + array = function () { + var array: any[] = []; + if ('[' === ch) { + if ((next('['), white(), ']' === ch)) return next(']'), array; + for (; ch; ) { + if ((array.push(value()), white(), ']' === ch)) return next(']'), array; + next(','), white(); + } + } + error('Bad array'); + }, + object = function () { + var key, + object: any = {}; + if ('{' === ch) { + if ((next('{'), white(), '}' === ch)) return next('}'), object; + for (; ch; ) { + let latchKeyStart = at; + if ( + ((key = string()), + white(), + next(':'), + Object.hasOwnProperty.call(object, key) && + warning('Duplicate key "' + key + '"', latchKeyStart), + (object[key] = value()), + white(), + '}' === ch) + ) + return next('}'), object; + next(','), white(); + } + } + error('Bad object'); + }; + return ( + (value = function () { + switch ((white(), ch)) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case '-': + return number(); + default: + return ch >= '0' && '9' >= ch ? number() : word(); + } + }), + function (source: string) { + annos = []; + let errored = false; + text = source; + at = 0; + ch = ' '; + white(); + + try { + value(); + } catch (e) { + errored = true; + annos.push({ type: AnnoTypes.error, at: e.at - 1, text: e.message }); + } + if (!errored && ch) { + error('Syntax error'); + } + return { annotations: annos }; + } + ); +}; diff --git a/packages/kbn-monaco/src/xjson/index.ts b/packages/kbn-monaco/src/xjson/index.ts new file mode 100644 index 0000000000000..35fd35887bc56 --- /dev/null +++ b/packages/kbn-monaco/src/xjson/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { registerGrammarChecker } from './language'; + +import { ID } from './constants'; + +export const XJsonLang = { registerGrammarChecker, ID }; diff --git a/packages/kbn-monaco/src/xjson/language.ts b/packages/kbn-monaco/src/xjson/language.ts new file mode 100644 index 0000000000000..fe505818d3c9a --- /dev/null +++ b/packages/kbn-monaco/src/xjson/language.ts @@ -0,0 +1,89 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// This file contains a lot of single setup logic for registering a language globally + +import { monaco } from '../monaco'; +import { WorkerProxyService } from './worker_proxy_service'; +import { registerLexerRules } from './lexer_rules'; +import { ID } from './constants'; +// @ts-ignore +import workerSrc from '!!raw-loader!../../target/public/xjson.editor.worker.js'; + +const wps = new WorkerProxyService(); + +// Register rules against shared monaco instance. +registerLexerRules(monaco); + +// In future we will need to make this map languages to workers using "id" and/or "label" values +// that get passed in. +// @ts-ignore +window.MonacoEnvironment = { + getWorker: (id: any, label: any) => { + // In kibana we will probably build this once and then load with raw-loader + const blob = new Blob([workerSrc], { type: 'application/javascript' }); + return new Worker(URL.createObjectURL(blob)); + }, +}; + +monaco.languages.onLanguage(ID, async () => { + return wps.setup(); +}); + +const OWNER = 'XJSON_GRAMMAR_CHECKER'; +export const registerGrammarChecker = (editor: monaco.editor.IEditor) => { + const allDisposables: monaco.IDisposable[] = []; + + const updateAnnos = async () => { + const { annotations } = await wps.getAnnos(); + const model = editor.getModel() as monaco.editor.ITextModel; + monaco.editor.setModelMarkers( + model, + OWNER, + annotations.map(({ at, text, type }) => { + const { column, lineNumber } = model.getPositionAt(at); + return { + startLineNumber: lineNumber, + startColumn: column, + endLineNumber: lineNumber, + endColumn: column, + message: text, + severity: type === 'error' ? monaco.MarkerSeverity.Error : monaco.MarkerSeverity.Warning, + }; + }) + ); + }; + + const onModelAdd = (model: monaco.editor.IModel) => { + allDisposables.push( + model.onDidChangeContent(async () => { + updateAnnos(); + }) + ); + + updateAnnos(); + }; + + allDisposables.push(monaco.editor.onDidCreateModel(onModelAdd)); + monaco.editor.getModels().forEach(onModelAdd); + return () => { + wps.stop(); + allDisposables.forEach((d) => d.dispose()); + }; +}; diff --git a/packages/kbn-monaco/src/xjson/lexer_rules/esql.ts b/packages/kbn-monaco/src/xjson/lexer_rules/esql.ts new file mode 100644 index 0000000000000..e75b1013d3727 --- /dev/null +++ b/packages/kbn-monaco/src/xjson/lexer_rules/esql.ts @@ -0,0 +1,270 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { monaco } from '../../monaco'; + +export const ID = 'esql'; + +const brackets = [ + { open: '[', close: ']', token: 'delimiter.square' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, +]; + +const keywords = [ + 'describe', + 'between', + 'in', + 'like', + 'not', + 'and', + 'or', + 'desc', + 'select', + 'from', + 'where', + 'having', + 'group', + 'by', + 'order', + 'asc', + 'desc', + 'pivot', + 'for', + 'in', + 'as', + 'show', + 'columns', + 'include', + 'frozen', + 'tables', + 'escape', + 'limit', + 'rlike', + 'all', + 'distinct', + 'is', +]; +const builtinFunctions = [ + 'avg', + 'count', + 'first', + 'first_value', + 'last', + 'last_value', + 'max', + 'min', + 'sum', + 'kurtosis', + 'mad', + 'percentile', + 'percentile_rank', + 'skewness', + 'stddev_pop', + 'sum_of_squares', + 'var_pop', + 'histogram', + 'case', + 'coalesce', + 'greatest', + 'ifnull', + 'iif', + 'isnull', + 'least', + 'nullif', + 'nvl', + 'curdate', + 'current_date', + 'current_time', + 'current_timestamp', + 'curtime', + 'dateadd', + 'datediff', + 'datepart', + 'datetrunc', + 'date_add', + 'date_diff', + 'date_part', + 'date_trunc', + 'day', + 'dayname', + 'dayofmonth', + 'dayofweek', + 'dayofyear', + 'day_name', + 'day_of_month', + 'day_of_week', + 'day_of_year', + 'dom', + 'dow', + 'doy', + 'hour', + 'hour_of_day', + 'idow', + 'isodayofweek', + 'isodow', + 'isoweek', + 'isoweekofyear', + 'iso_day_of_week', + 'iso_week_of_year', + 'iw', + 'iwoy', + 'minute', + 'minute_of_day', + 'minute_of_hour', + 'month', + 'monthname', + 'month_name', + 'month_of_year', + 'now', + 'quarter', + 'second', + 'second_of_minute', + 'timestampadd', + 'timestampdiff', + 'timestamp_add', + 'timestamp_diff', + 'today', + 'week', + 'week_of_year', + 'year', + 'abs', + 'acos', + 'asin', + 'atan', + 'atan2', + 'cbrt', + 'ceil', + 'ceiling', + 'cos', + 'cosh', + 'cot', + 'degrees', + 'e', + 'exp', + 'expm1', + 'floor', + 'log', + 'log10', + 'mod', + 'pi', + 'power', + 'radians', + 'rand', + 'random', + 'round', + 'sign', + 'signum|sin', + 'sinh', + 'sqrt', + 'tan', + 'truncate', + 'ascii', + 'bit_length', + 'char', + 'character_length', + 'char_length', + 'concat', + 'insert', + 'lcase', + 'left', + 'length', + 'locate', + 'ltrim', + 'octet_length', + 'position', + 'repeat', + 'replace', + 'right', + 'rtrim', + 'space', + 'substring', + 'ucase', + 'cast', + 'convert', + 'database', + 'user', + 'st_astext', + 'st_aswkt', + 'st_distance', + 'st_geometrytype', + 'st_geomfromtext', + 'st_wkttosql', + 'st_x', + 'st_y', + 'st_z', + 'score', +]; + +export const lexerRules = { + defaultToken: 'invalid', + ignoreCase: true, + tokenPostfix: '', + keywords, + builtinFunctions, + brackets, + tokenizer: { + root: [ + [ + /[a-zA-Z_$][a-zA-Z0-9_$]*\b/, + { + cases: { + '@keywords': 'keyword', + '@builtinFunctions': 'identifier', + '@default': 'identifier', + }, + }, + ], + [/[()]/, '@brackets'], + [/--.*$/, 'comment'], + [/\/\*/, 'comment', '@comment'], + [/\/.*$/, 'comment'], + + [/".*?"/, 'string'], + + [/'.*?'/, 'constant'], + [/`.*?`/, 'string'], + // whitespace + [/[ \t\r\n]+/, { token: '@whitespace' }], + [/[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b/, 'entity.name.function'], + [/⇐|<⇒|\*|\.|\:\:|\+|\-|\/|\/\/|%|&|\^|~|<|>|<=|=>|==|!=|<>|=/, 'keyword.operator'], + [/[\(]/, 'paren.lparen'], + [/[\)]/, 'paren.rparen'], + [/\s+/, 'text'], + ], + numbers: [ + [/0[xX][0-9a-fA-F]*/, 'number'], + [/[$][+-]*\d*(\.\d*)?/, 'number'], + [/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, 'number'], + ], + strings: [ + [/N'/, { token: 'string', next: '@string' }], + [/'/, { token: 'string', next: '@string' }], + ], + string: [ + [/[^']+/, 'string'], + [/''/, 'string'], + [/'/, { token: 'string', next: '@pop' }], + ], + comment: [ + [/[^\/*]+/, 'comment'], + [/\*\//, 'comment', '@pop'], + [/[\/*]/, 'comment'], + ], + }, +} as monaco.languages.IMonarchLanguage; diff --git a/packages/kbn-monaco/src/xjson/lexer_rules/index.ts b/packages/kbn-monaco/src/xjson/lexer_rules/index.ts new file mode 100644 index 0000000000000..515de09510a61 --- /dev/null +++ b/packages/kbn-monaco/src/xjson/lexer_rules/index.ts @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* eslint-disable-next-line @kbn/eslint/module_migration */ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import * as xJson from './xjson'; +import * as esql from './esql'; +import * as painless from './painless'; + +export const registerLexerRules = (m: typeof monaco) => { + m.languages.register({ id: xJson.ID }); + m.languages.setMonarchTokensProvider(xJson.ID, xJson.lexerRules); + m.languages.register({ id: painless.ID }); + m.languages.setMonarchTokensProvider(painless.ID, painless.lexerRules); + m.languages.register({ id: esql.ID }); + m.languages.setMonarchTokensProvider(esql.ID, esql.lexerRules); +}; diff --git a/packages/kbn-monaco/src/xjson/lexer_rules/painless.ts b/packages/kbn-monaco/src/xjson/lexer_rules/painless.ts new file mode 100644 index 0000000000000..676eb3134026a --- /dev/null +++ b/packages/kbn-monaco/src/xjson/lexer_rules/painless.ts @@ -0,0 +1,194 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { monaco } from '../../monaco'; + +export const ID = 'painless'; + +/** + * Extends the default type for a Monarch language so we can use + * attribute references (like @keywords to reference the keywords list) + * in the defined tokenizer + */ +interface Language extends monaco.languages.IMonarchLanguage { + default: string; + brackets: any; + keywords: string[]; + symbols: RegExp; + escapes: RegExp; + digits: RegExp; + primitives: string[]; + octaldigits: RegExp; + binarydigits: RegExp; + constants: string[]; + operators: string[]; +} + +export const lexerRules = { + default: 'invalid', + tokenPostfix: '', + // painless does not use < >, so we define our own + brackets: [ + ['{', '}', 'delimiter.curly'], + ['[', ']', 'delimiter.square'], + ['(', ')', 'delimiter.parenthesis'], + ], + keywords: [ + 'if', + 'in', + 'else', + 'while', + 'do', + 'for', + 'continue', + 'break', + 'return', + 'new', + 'try', + 'catch', + 'throw', + 'this', + 'instanceof', + ], + primitives: ['void', 'boolean', 'byte', 'short', 'char', 'int', 'long', 'float', 'double', 'def'], + constants: ['true', 'false'], + operators: [ + '=', + '>', + '<', + '!', + '~', + '?', + '?:', + '?.', + ':', + '==', + '===', + '<=', + '>=', + '!=', + '!==', + '&&', + '||', + '++', + '--', + '+', + '-', + '*', + '/', + '&', + '|', + '^', + '%', + '<<', + '>>', + '>>>', + '+=', + '-=', + '*=', + '/=', + '&=', + '|=', + '^=', + '%=', + '<<=', + '>>=', + '>>>=', + '->', + '::', + '=~', + '==~', + ], + symbols: /[=> { + worker.initialize((ctx: any, createData: any) => { + return new XJsonWorker(ctx); + }); +}; diff --git a/packages/kbn-monaco/src/xjson/worker/xjson_worker.ts b/packages/kbn-monaco/src/xjson/worker/xjson_worker.ts new file mode 100644 index 0000000000000..501adcacb6990 --- /dev/null +++ b/packages/kbn-monaco/src/xjson/worker/xjson_worker.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* eslint-disable-next-line @kbn/eslint/module_migration */ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { createParser } from '../grammar'; + +export class XJsonWorker { + constructor(private ctx: monaco.worker.IWorkerContext) {} + private parser: any; + + async parse() { + if (!this.parser) { + this.parser = createParser(); + } + const [model] = this.ctx.getMirrorModels(); + return this.parser(model.getValue()); + } +} diff --git a/packages/kbn-monaco/src/xjson/worker_proxy_service.ts b/packages/kbn-monaco/src/xjson/worker_proxy_service.ts new file mode 100644 index 0000000000000..17d6d56e51e59 --- /dev/null +++ b/packages/kbn-monaco/src/xjson/worker_proxy_service.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AnnoTypes } from './grammar'; +import { monaco } from '../monaco'; +import { XJsonWorker } from './worker'; +import { ID } from './constants'; + +export interface Annotation { + name?: string; + type: AnnoTypes; + text: string; + at: number; +} + +export interface AnnotationsResponse { + annotations: Annotation[]; +} + +export class WorkerProxyService { + private worker: monaco.editor.MonacoWebWorker | undefined; + + public async getAnnos(): Promise { + if (!this.worker) { + throw new Error('Worker Proxy Service has not been setup!'); + } + await this.worker.withSyncedResources(monaco.editor.getModels().map(({ uri }) => uri)); + const proxy = await this.worker.getProxy(); + return proxy.parse(); + } + + public setup() { + this.worker = monaco.editor.createWebWorker({ label: ID, moduleId: '' }); + } + + public stop() { + if (this.worker) this.worker.dispose(); + } +} diff --git a/packages/kbn-monaco/tsconfig.json b/packages/kbn-monaco/tsconfig.json new file mode 100644 index 0000000000000..95acfd32b24dd --- /dev/null +++ b/packages/kbn-monaco/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "declaration": true, + "sourceMap": true, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/kbn-monaco/webpack.config.js b/packages/kbn-monaco/webpack.config.js new file mode 100644 index 0000000000000..1a7d8c031670c --- /dev/null +++ b/packages/kbn-monaco/webpack.config.js @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const path = require('path'); + +const createLangWorkerConfig = (lang) => ({ + mode: 'production', + entry: path.resolve(__dirname, 'src', lang, 'worker', `${lang}.worker.ts`), + output: { + path: path.resolve(__dirname, 'target/public'), + filename: `${lang}.editor.worker.js`, + }, + resolve: { + modules: ['node_modules'], + extensions: ['.js', '.ts', '.tsx'], + }, + stats: 'errors-only', + module: { + rules: [ + { + test: /\.(js|ts)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + babelrc: false, + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }, + }, + }, + ], + }, +}); + +module.exports = [createLangWorkerConfig('xjson')]; diff --git a/packages/kbn-monaco/yarn.lock b/packages/kbn-monaco/yarn.lock new file mode 120000 index 0000000000000..3f82ebc9cdbae --- /dev/null +++ b/packages/kbn-monaco/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index b7c9a63897bf9..7bd7a236a43aa 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -28,6 +28,7 @@ "cpy": "^8.0.0", "css-loader": "^3.4.2", "del": "^5.1.0", + "execa": "^4.0.2", "file-loader": "^4.2.0", "istanbul-instrumenter-loader": "^3.0.1", "jest-diff": "^25.1.0", diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.scss new file mode 100644 index 0000000000000..563d20e99ce82 --- /dev/null +++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.scss @@ -0,0 +1,3 @@ +body { + color: green; +} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts index 817c4796562e8..7ddd10f4a388f 100644 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts +++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts @@ -18,6 +18,7 @@ */ import './legacy/styles.scss'; +import './index.scss'; import { fooLibFn } from '../../foo/public/index'; export * from './lib'; export { fooLibFn }; diff --git a/packages/kbn-optimizer/src/cli.ts b/packages/kbn-optimizer/src/cli.ts index a2fbe969e34d8..0916f12a7110d 100644 --- a/packages/kbn-optimizer/src/cli.ts +++ b/packages/kbn-optimizer/src/cli.ts @@ -77,8 +77,8 @@ run( const extraPluginScanDirs = ([] as string[]) .concat((flags['scan-dir'] as string | string[]) || []) - .map(p => Path.resolve(p)); - if (!extraPluginScanDirs.every(s => typeof s === 'string')) { + .map((p) => Path.resolve(p)); + if (!extraPluginScanDirs.every((s) => typeof s === 'string')) { throw createFlagError('expected --scan-dir to be a string'); } diff --git a/packages/kbn-optimizer/src/common/array_helpers.test.ts b/packages/kbn-optimizer/src/common/array_helpers.test.ts index 9d45217486ee8..ab5f4b694333e 100644 --- a/packages/kbn-optimizer/src/common/array_helpers.test.ts +++ b/packages/kbn-optimizer/src/common/array_helpers.test.ts @@ -53,11 +53,7 @@ describe('ascending/descending', () => { ].sort(() => 0.5 - Math.random()); it('sorts items using getters', () => { - expect( - Array.from(values) - .sort(ascending(a, b, c)) - .map(print) - ).toMatchInlineSnapshot(` + expect(Array.from(values).sort(ascending(a, b, c)).map(print)).toMatchInlineSnapshot(` Array [ "1/2/3", "3/2/1", @@ -81,11 +77,7 @@ describe('ascending/descending', () => { ] `); - expect( - Array.from(values) - .sort(descending(a, b, c)) - .map(print) - ).toMatchInlineSnapshot(` + expect(Array.from(values).sort(descending(a, b, c)).map(print)).toMatchInlineSnapshot(` Array [ "9/9/9", "8/foo/8", diff --git a/packages/kbn-optimizer/src/common/bundle.ts b/packages/kbn-optimizer/src/common/bundle.ts index 7581b90d60af2..9e2ad186ba40c 100644 --- a/packages/kbn-optimizer/src/common/bundle.ts +++ b/packages/kbn-optimizer/src/common/bundle.ts @@ -80,7 +80,7 @@ export class Bundle { return { spec: this.toSpec(), mtimes: entriesToObject( - files.map(p => [p, mtimes.get(p)] as const).sort(ascending(e => e[0])) + files.map((p) => [p, mtimes.get(p)] as const).sort(ascending((e) => e[0])) ), }; } diff --git a/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax.ts b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax.ts index ba19bdc9c3be7..aba4451622dcd 100644 --- a/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax.ts +++ b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax.ts @@ -130,7 +130,7 @@ export const checks: DisallowedSyntaxCheck[] = [ { name: '[es2018] object spread properties', nodeType: 'ObjectExpression', - test: (n: estree.ObjectExpression) => n.properties.some(p => p.type === 'SpreadElement'), + test: (n: estree.ObjectExpression) => n.properties.some((p) => p.type === 'SpreadElement'), }, // https://github.com/estree/estree/blob/master/es2018.md#template-literals { @@ -142,7 +142,7 @@ export const checks: DisallowedSyntaxCheck[] = [ { name: '[es2018] rest properties', nodeType: 'ObjectPattern', - test: (n: estree.ObjectPattern) => n.properties.some(p => p.type === 'RestElement'), + test: (n: estree.ObjectPattern) => n.properties.some((p) => p.type === 'RestElement'), }, /** diff --git a/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax_plugin.ts b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax_plugin.ts index 7377462eb267b..8fb7559f3e22f 100644 --- a/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax_plugin.ts +++ b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax_plugin.ts @@ -26,8 +26,8 @@ import { parseFilePath } from '../parse_path'; export class DisallowedSyntaxPlugin { apply(compiler: webpack.Compiler) { - compiler.hooks.normalModuleFactory.tap(DisallowedSyntaxPlugin.name, factory => { - factory.hooks.parser.for('javascript/auto').tap(DisallowedSyntaxPlugin.name, parser => { + compiler.hooks.normalModuleFactory.tap(DisallowedSyntaxPlugin.name, (factory) => { + factory.hooks.parser.for('javascript/auto').tap(DisallowedSyntaxPlugin.name, (parser) => { parser.hooks.program.tap(DisallowedSyntaxPlugin.name, (program: acorn.Node) => { const module = parser.state?.current; if (!module || !module.resource) { @@ -43,7 +43,7 @@ export class DisallowedSyntaxPlugin { const failedChecks = new Set(); - AcornWalk.full(program, node => { + AcornWalk.full(program, (node) => { const checks = checksByNodeType.get(node.type as any); if (!checks) { return; @@ -63,7 +63,7 @@ export class DisallowedSyntaxPlugin { // throw an error to trigger a parse failure, causing this module to be reported as invalid throw new Error( `disallowed syntax found in file ${resource}:\n - ${Array.from(failedChecks) - .map(c => c.name) + .map((c) => c.name) .join('\n - ')}` ); }); diff --git a/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts b/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts index f6f6841532799..7458fa13eccb3 100644 --- a/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts +++ b/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts @@ -233,6 +233,6 @@ it('stops an infinite stream when unsubscribed', async () => { // ensure summarizer still only called 10 times after a timeout expect(summarize).toHaveBeenCalledTimes(10); - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); expect(summarize).toHaveBeenCalledTimes(10); }); diff --git a/packages/kbn-optimizer/src/common/event_stream_helpers.ts b/packages/kbn-optimizer/src/common/event_stream_helpers.ts index d07af32f88897..d93cba5653abd 100644 --- a/packages/kbn-optimizer/src/common/event_stream_helpers.ts +++ b/packages/kbn-optimizer/src/common/event_stream_helpers.ts @@ -40,7 +40,7 @@ export const summarizeEventStream = ( initialState: State, summarize: Summarizer ) => { - return new Rx.Observable>(subscriber => { + return new Rx.Observable>((subscriber) => { const eventBuffer: Event[] = []; let processingEventBuffer = false; @@ -93,7 +93,7 @@ export const summarizeEventStream = ( subscriber.add( event$.subscribe( injectEvent, - error => { + (error) => { subscriber.error(error); }, () => { diff --git a/packages/kbn-optimizer/src/common/parse_path.test.ts b/packages/kbn-optimizer/src/common/parse_path.test.ts index 61be44348cfae..48749a08fb285 100644 --- a/packages/kbn-optimizer/src/common/parse_path.test.ts +++ b/packages/kbn-optimizer/src/common/parse_path.test.ts @@ -32,13 +32,13 @@ const FILES = [ ]; describe('parseFilePath()', () => { - it.each([...FILES, ...AMBIGUOUS])('parses %s', path => { + it.each([...FILES, ...AMBIGUOUS])('parses %s', (path) => { expect(parseFilePath(path)).toMatchSnapshot(); }); }); describe('parseDirPath()', () => { - it.each([...DIRS, ...AMBIGUOUS])('parses %s', path => { + it.each([...DIRS, ...AMBIGUOUS])('parses %s', (path) => { expect(parseDirPath(path)).toMatchSnapshot(); }); }); diff --git a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts index 72be71e6bf7ec..dda66c999b8f1 100644 --- a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts +++ b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts @@ -29,9 +29,9 @@ describe('pipeClosure()', () => { let counter = 0; const foo$ = Rx.of(1, 2, 3).pipe( - pipeClosure(source$ => { + pipeClosure((source$) => { const multiplier = ++counter; - return source$.pipe(map(i => i * multiplier)); + return source$.pipe(map((i) => i * multiplier)); }), toArray() ); @@ -71,7 +71,7 @@ describe('maybe()', () => { describe('maybeMap()', () => { it('calls map fn and filters out undefined values returned', async () => { const foo$ = Rx.of(1, 2, 3, 4, 5).pipe( - maybeMap(i => (i % 2 ? i : undefined)), + maybeMap((i) => (i % 2 ? i : undefined)), toArray() ); @@ -94,7 +94,7 @@ describe('debounceTimeBuffer()', () => { foo$ .pipe( debounceTimeBuffer(100), - map(items => items.reduce((sum, n) => sum + n)) + map((items) => items.reduce((sum, n) => sum + n)) ) .subscribe(dest); @@ -128,7 +128,7 @@ describe('debounceTimeBuffer()', () => { foo$ .pipe( debounceTimeBuffer(100), - map(items => items.reduce((sum, n) => sum + n)) + map((items) => items.reduce((sum, n) => sum + n)) ) .subscribe(dest); diff --git a/packages/kbn-optimizer/src/common/rxjs_helpers.ts b/packages/kbn-optimizer/src/common/rxjs_helpers.ts index f37bebb49efe9..c6385c22518aa 100644 --- a/packages/kbn-optimizer/src/common/rxjs_helpers.ts +++ b/packages/kbn-optimizer/src/common/rxjs_helpers.ts @@ -39,7 +39,7 @@ export const pipeClosure = (fn: Operator): Operator => { * supporting TypeScript */ export const maybe = (): Operator => { - return mergeMap(item => (item === undefined ? Rx.EMPTY : [item])); + return mergeMap((item) => (item === undefined ? Rx.EMPTY : [item])); }; /** @@ -64,7 +64,7 @@ export const debounceTimeBuffer = (ms: number) => pipeClosure((source$: Rx.Observable) => { const buffer: T[] = []; return source$.pipe( - tap(item => buffer.push(item)), + tap((item) => buffer.push(item)), debounceTime(ms), map(() => { const items = Array.from(buffer); diff --git a/packages/kbn-optimizer/src/common/worker_config.ts b/packages/kbn-optimizer/src/common/worker_config.ts index 3fb1880a73716..a1ab51ee97c23 100644 --- a/packages/kbn-optimizer/src/common/worker_config.ts +++ b/packages/kbn-optimizer/src/common/worker_config.ts @@ -31,7 +31,7 @@ export interface WorkerConfig { readonly optimizerCacheKey: unknown; } -export type CacheableWorkerConfig = Omit; +export type CacheableWorkerConfig = Omit; export function parseWorkerConfig(json: string): WorkerConfig { try { diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 8bf5eb72523ff..2814ab32017d2 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -55,8 +55,8 @@ OptimizerConfig { } `; -exports[`prepares assets for distribution: 1 async bundle 1`] = `"(window[\\"foo_bundle_jsonpfunction\\"]=window[\\"foo_bundle_jsonpfunction\\"]||[]).push([[1],[,function(module,__webpack_exports__,__webpack_require__){\\"use strict\\";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,\\"foo\\",(function(){return foo}));function foo(){}}]]);"`; +exports[`prepares assets for distribution: bar bundle 1`] = `"var __kbnBundles__=typeof __kbnBundles__===\\"object\\"?__kbnBundles__:{};__kbnBundles__[\\"plugin/bar\\"]=function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i value.split(REPO_ROOT).join('').replace(/\\/g, '/'), + test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT), +}); const log = new ToolingLog({ level: 'error', @@ -85,39 +88,39 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { } }; - const initializingStates = msgs.filter(msg => msg.state.phase === 'initializing'); + const initializingStates = msgs.filter((msg) => msg.state.phase === 'initializing'); assert('produce at least one initializing event', initializingStates.length >= 1); const bundleCacheStates = msgs.filter( - msg => + (msg) => (msg.event?.type === 'bundle cached' || msg.event?.type === 'bundle not cached') && msg.state.phase === 'initializing' ); assert('produce two bundle cache events while initializing', bundleCacheStates.length === 2); - const initializedStates = msgs.filter(msg => msg.state.phase === 'initialized'); + const initializedStates = msgs.filter((msg) => msg.state.phase === 'initialized'); assert('produce at least one initialized event', initializedStates.length >= 1); - const workerStarted = msgs.filter(msg => msg.event?.type === 'worker started'); + const workerStarted = msgs.filter((msg) => msg.event?.type === 'worker started'); assert('produce one worker started event', workerStarted.length === 1); - const runningStates = msgs.filter(msg => msg.state.phase === 'running'); + const runningStates = msgs.filter((msg) => msg.state.phase === 'running'); assert( 'produce two or three "running" states', runningStates.length === 2 || runningStates.length === 3 ); - const bundleNotCachedEvents = msgs.filter(msg => msg.event?.type === 'bundle not cached'); + const bundleNotCachedEvents = msgs.filter((msg) => msg.event?.type === 'bundle not cached'); assert('produce two "bundle not cached" events', bundleNotCachedEvents.length === 2); - const successStates = msgs.filter(msg => msg.state.phase === 'success'); + const successStates = msgs.filter((msg) => msg.state.phase === 'success'); assert( 'produce one or two "compiler success" states', successStates.length === 1 || successStates.length === 2 ); const otherStates = msgs.filter( - msg => + (msg) => msg.state.phase !== 'initializing' && msg.state.phase !== 'success' && msg.state.phase !== 'running' && @@ -126,31 +129,33 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { ); assert('produce zero unexpected states', otherStates.length === 0, otherStates); - const foo = config.bundles.find(b => b.id === 'foo')!; + const foo = config.bundles.find((b) => b.id === 'foo')!; expect(foo).toBeTruthy(); foo.cache.refresh(); - expect(foo.cache.getModuleCount()).toBe(4); + expect(foo.cache.getModuleCount()).toBe(5); expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, + /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); - const bar = config.bundles.find(b => b.id === 'bar')!; + const bar = config.bundles.find((b) => b.id === 'bar')!; expect(bar).toBeTruthy(); bar.cache.refresh(); expect(bar.cache.getModuleCount()).toBe( - // code + styles + style/css-loader runtimes - 15 + // code + styles + style/css-loader runtimes + public path updater + 21 ); expect(bar.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ /node_modules/css-loader/package.json, /node_modules/style-loader/package.json, + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/legacy/styles.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/lib.ts, @@ -159,6 +164,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/legacy/ui/public/icon.svg, + /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); }); @@ -173,7 +179,7 @@ it('uses cache on second run and exist cleanly', async () => { const msgs = await runOptimizer(config) .pipe( - tap(state => { + tap((state) => { if (state.event?.type === 'worker stdio') { // eslint-disable-next-line no-console console.log('worker', state.event.stream, state.event.chunk.toString('utf8')); @@ -183,7 +189,7 @@ it('uses cache on second run and exist cleanly', async () => { ) .toPromise(); - expect(msgs.map(m => m.state.phase)).toMatchInlineSnapshot(` + expect(msgs.map((m) => m.state.phase)).toMatchInlineSnapshot(` Array [ "initializing", "initializing", @@ -202,14 +208,12 @@ it('prepares assets for distribution', async () => { dist: true, }); - await runOptimizer(config) - .pipe(logOptimizerState(log, config), toArray()) - .toPromise(); + await runOptimizer(config).pipe(logOptimizerState(log, config), toArray()).toPromise(); expectFileMatchesSnapshotWithCompression('plugins/foo/target/public/foo.plugin.js', 'foo bundle'); expectFileMatchesSnapshotWithCompression( 'plugins/foo/target/public/1.plugin.js', - '1 async bundle' + 'foo async bundle' ); expectFileMatchesSnapshotWithCompression('plugins/bar/target/public/bar.plugin.js', 'bar bundle'); }); @@ -219,6 +223,7 @@ it('prepares assets for distribution', async () => { */ const expectFileMatchesSnapshotWithCompression = (filePath: string, snapshotLabel: string) => { const raw = Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, filePath), 'utf8'); + expect(raw).toMatchSnapshot(snapshotLabel); // Verify the brotli variant matches diff --git a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts index 1bfd8d3fd073a..39064c64062e8 100644 --- a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts @@ -35,7 +35,7 @@ const MOCK_REPO_DIR = Path.resolve(TMP_DIR, 'mock_repo'); expect.addSnapshotSerializer({ print: () => '', - test: v => v instanceof Bundle, + test: (v) => v instanceof Bundle, }); expect.addSnapshotSerializer(createAbsolutePathSerializer(MOCK_REPO_DIR)); @@ -208,8 +208,8 @@ it('emits "bundle not cached" event when optimizerCacheKey is outdated, includes "diff": "- Expected + Received - - old - + optimizerCacheKey", + - \\"old\\" + + \\"optimizerCacheKey\\"", "reason": "optimizer cache key mismatch", "type": "bundle not cached", }, @@ -291,8 +291,8 @@ it('emits "bundle not cached" event when cacheKey is outdated', async () => { "diff": "- Expected + Received - - old - + new", + - \\"old\\" + + \\"new\\"", "reason": "cache key mismatch", "type": "bundle not cached", }, diff --git a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts index c02a857883a98..91d0f308e0ef6 100644 --- a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts @@ -96,7 +96,7 @@ it('notifies of changes and completes once all bundles have changed', async () = // first we change foo and bar, and after 1 second get that change comes though if (i === 1) { expect(event.bundles).toHaveLength(2); - const [bar, foo] = event.bundles.sort(ascending(b => b.id)); + const [bar, foo] = event.bundles.sort(ascending((b) => b.id)); expect(bar).toHaveProperty('id', 'bar'); expect(foo).toHaveProperty('id', 'foo'); } @@ -110,7 +110,7 @@ it('notifies of changes and completes once all bundles have changed', async () = // finally we change box and car together if (i === 5) { expect(event.bundles).toHaveLength(2); - const [bar, foo] = event.bundles.sort(ascending(b => b.id)); + const [bar, foo] = event.bundles.sort(ascending((b) => b.id)); expect(bar).toHaveProperty('id', 'box'); expect(foo).toHaveProperty('id', 'car'); } diff --git a/packages/kbn-optimizer/src/log_optimizer_state.ts b/packages/kbn-optimizer/src/log_optimizer_state.ts index 5217581d1b413..cbec159bd27a0 100644 --- a/packages/kbn-optimizer/src/log_optimizer_state.ts +++ b/packages/kbn-optimizer/src/log_optimizer_state.ts @@ -33,7 +33,7 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { let loggedInit = false; return update$.pipe( - tap(update => { + tap((update) => { const { event, state } = update; if (event?.type === 'worker stdio') { diff --git a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts index dd4d5c294dfc8..4671276797049 100644 --- a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts +++ b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts @@ -35,7 +35,7 @@ const summarizeBundles = (w: Assignments) => const readConfigs = (workers: Assignments[]) => workers.map( - (w, i) => `worker ${i} (${summarizeBundles(w)}) => ${w.bundles.map(b => b.id).join(',')}` + (w, i) => `worker ${i} (${summarizeBundles(w)}) => ${w.bundles.map((b) => b.id).join(',')}` ); const assertReturnVal = (workers: Assignments[]) => { diff --git a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.ts b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.ts index 001783b167c7a..e1bcb22230bf9 100644 --- a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.ts +++ b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.ts @@ -70,16 +70,16 @@ export function assignBundlesToWorkers(bundles: Bundle[], maxWorkerCount: number * counts and sort them by [moduleCount, id] */ const bundlesWithCountsDesc = bundles - .filter(b => b.cache.getModuleCount() !== undefined) + .filter((b) => b.cache.getModuleCount() !== undefined) .sort( descending( - b => b.cache.getModuleCount(), - b => b.id + (b) => b.cache.getModuleCount(), + (b) => b.id ) ); const bundlesWithoutModuleCounts = bundles - .filter(b => b.cache.getModuleCount() === undefined) - .sort(descending(b => b.id)); + .filter((b) => b.cache.getModuleCount() === undefined) + .sort(descending((b) => b.id)); /** * assign largest bundles to the smallest worker until it is @@ -87,7 +87,7 @@ export function assignBundlesToWorkers(bundles: Bundle[], maxWorkerCount: number * with module counts are assigned */ while (bundlesWithCountsDesc.length) { - const [smallestWorker, nextSmallestWorker] = workers.sort(ascending(w => w.moduleCount)); + const [smallestWorker, nextSmallestWorker] = workers.sort(ascending((w) => w.moduleCount)); while (!nextSmallestWorker || smallestWorker.moduleCount <= nextSmallestWorker.moduleCount) { const bundle = bundlesWithCountsDesc.shift(); @@ -104,7 +104,7 @@ export function assignBundlesToWorkers(bundles: Bundle[], maxWorkerCount: number * assign bundles without module counts to workers round-robin * starting with the smallest workers */ - workers.sort(ascending(w => w.moduleCount)); + workers.sort(ascending((w) => w.moduleCount)); while (bundlesWithoutModuleCounts.length) { for (const worker of workers) { const bundle = bundlesWithoutModuleCounts.shift(); diff --git a/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts b/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts index 7351a3787f760..9d7f1709506f9 100644 --- a/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts +++ b/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts @@ -35,7 +35,7 @@ jest.mock('./get_changes.ts', () => ({ })); jest.mock('./get_mtimes.ts', () => ({ - getMtimes: async (paths: string[]) => new Map(paths.map(path => [path, 12345])), + getMtimes: async (paths: string[]) => new Map(paths.map((path) => [path, 12345])), })); jest.mock('execa'); @@ -100,7 +100,6 @@ describe('getOptimizerCacheKey()', () => { }, "workerConfig": Object { "browserslistEnv": "dev", - "cache": true, "dist": false, "optimizerCacheKey": "♻", "repoRoot": , @@ -134,13 +133,13 @@ describe('diffCacheKey()', () => { "- Expected + Received -  Array [ +  [  \\"1\\",  \\"2\\", -  Object { - - \\"a\\": \\"b\\", - + \\"b\\": \\"a\\", -  }, +  { + - \\"a\\": \\"b\\" + + \\"b\\": \\"a\\" +  }  ]" `); expect( @@ -158,11 +157,11 @@ describe('diffCacheKey()', () => { "- Expected + Received -  Object { +  { - \\"a\\": \\"1\\", - - \\"b\\": \\"1\\", + - \\"b\\": \\"1\\" + \\"a\\": \\"2\\", - + \\"b\\": \\"2\\", + + \\"b\\": \\"2\\"  }" `); }); diff --git a/packages/kbn-optimizer/src/optimizer/cache_keys.ts b/packages/kbn-optimizer/src/optimizer/cache_keys.ts index 11288afa28969..2766f6d63702b 100644 --- a/packages/kbn-optimizer/src/optimizer/cache_keys.ts +++ b/packages/kbn-optimizer/src/optimizer/cache_keys.ts @@ -48,11 +48,18 @@ function omit(obj: T, keys: K[]): Omit { } export function diffCacheKey(expected?: unknown, actual?: unknown) { - if (jsonStable(expected) === jsonStable(actual)) { + const expectedJson = jsonStable(expected, { + space: ' ', + }); + const actualJson = jsonStable(actual, { + space: ' ', + }); + + if (expectedJson === actualJson) { return; } - return reformatJestDiff(jestDiff(expected, actual)); + return reformatJestDiff(jestDiff(expectedJson, actualJson)); } export function reformatJestDiff(diff: string | null) { @@ -178,11 +185,11 @@ export async function getOptimizerCacheKey(config: OptimizerConfig) { bootstrap, deletedPaths, modifiedTimes: {} as Record, - workerConfig: omit(config.getWorkerConfig('♻'), ['watch', 'profileWebpack']), + workerConfig: omit(config.getWorkerConfig('♻'), ['watch', 'profileWebpack', 'cache']), }; const mtimes = await getMtimes(modifiedPaths); - for (const [path, mtime] of Array.from(mtimes.entries()).sort(ascending(e => e[0]))) { + for (const [path, mtime] of Array.from(mtimes.entries()).sort(ascending((e) => e[0]))) { if (typeof mtime === 'number') { cacheKeys.modifiedTimes[path] = mtime; } diff --git a/packages/kbn-optimizer/src/optimizer/get_mtimes.ts b/packages/kbn-optimizer/src/optimizer/get_mtimes.ts index 9ac156cb5b8de..07777c323637a 100644 --- a/packages/kbn-optimizer/src/optimizer/get_mtimes.ts +++ b/packages/kbn-optimizer/src/optimizer/get_mtimes.ts @@ -33,15 +33,15 @@ export async function getMtimes(paths: Iterable) { // map paths to [path, mtimeMs] entries with concurrency of // 100 at a time, ignoring missing paths mergeMap( - path => + (path) => stat$(path).pipe( - map(stat => [path, stat.mtimeMs] as const), + map((stat) => [path, stat.mtimeMs] as const), catchError((error: any) => (error?.code === 'ENOENT' ? Rx.EMPTY : Rx.throwError(error))) ), 100 ), toArray(), - map(entries => new Map(entries)) + map((entries) => new Map(entries)) ) .toPromise(); } diff --git a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts index 36dc0ca64c6ca..2174c488ad6cc 100644 --- a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts @@ -44,7 +44,7 @@ it('returns a bundle for core and each plugin', () => { }, ], '/repo' - ).map(b => b.toSpec()) + ).map((b) => b.toSpec()) ).toMatchInlineSnapshot(` Array [ Object { diff --git a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts index 4741cc3c30af7..b75a8a6edc264 100644 --- a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts @@ -25,9 +25,9 @@ import { KibanaPlatformPlugin } from './kibana_platform_plugins'; export function getPluginBundles(plugins: KibanaPlatformPlugin[], repoRoot: string) { return plugins - .filter(p => p.isUiPlugin) + .filter((p) => p.isUiPlugin) .map( - p => + (p) => new Bundle({ type: 'plugin', id: p.id, diff --git a/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.ts b/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.ts index b4b02649259a2..8b39b5fe8d3b6 100644 --- a/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.ts +++ b/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.ts @@ -32,7 +32,7 @@ export function handleOptimizerCompletion(config: OptimizerConfig) { return update$.pipe( tap({ - next: update => { + next: (update) => { prevState = update.state; }, complete: () => { diff --git a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts index 2165878e92ff4..992feab6cd364 100644 --- a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts +++ b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts @@ -36,15 +36,15 @@ export function findKibanaPlatformPlugins(scanDirs: string[], paths: string[]) { .sync( Array.from( new Set([ - ...scanDirs.map(dir => `${dir}/*/kibana.json`), - ...paths.map(path => `${path}/kibana.json`), + ...scanDirs.map((dir) => `${dir}/*/kibana.json`), + ...paths.map((path) => `${path}/kibana.json`), ]) ), { absolute: true, } ) - .map(path => + .map((path) => // absolute paths returned from globby are using normalize or something so the path separators are `/` even on windows, Path.resolve solves this readKibanaPlatformPlugin(Path.resolve(path)) ); diff --git a/packages/kbn-optimizer/src/optimizer/observe_worker.ts b/packages/kbn-optimizer/src/optimizer/observe_worker.ts index bfc853e5a6b75..c929cf62d1bb0 100644 --- a/packages/kbn-optimizer/src/optimizer/observe_worker.ts +++ b/packages/kbn-optimizer/src/optimizer/observe_worker.ts @@ -17,10 +17,10 @@ * under the License. */ -import { fork, ChildProcess } from 'child_process'; import { Readable } from 'stream'; import { inspect } from 'util'; +import execa from 'execa'; import * as Rx from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; @@ -42,12 +42,12 @@ export interface WorkerStarted { export type WorkerStatus = WorkerStdio | WorkerStarted; interface ProcResource extends Rx.Unsubscribable { - proc: ChildProcess; + proc: execa.ExecaChildProcess; } const isNumeric = (input: any) => String(input).match(/^[0-9]+$/); let inspectPortCounter = 9230; -const inspectFlagIndex = process.execArgv.findIndex(flag => flag.startsWith('--inspect')); +const inspectFlagIndex = process.execArgv.findIndex((flag) => flag.startsWith('--inspect')); let inspectFlag: string | undefined; if (inspectFlagIndex !== -1) { const argv = process.execArgv[inspectFlagIndex]; @@ -70,20 +70,22 @@ function usingWorkerProc( config: OptimizerConfig, workerConfig: WorkerConfig, bundles: Bundle[], - fn: (proc: ChildProcess) => Rx.Observable + fn: (proc: execa.ExecaChildProcess) => Rx.Observable ) { return Rx.using( (): ProcResource => { - const args = [JSON.stringify(workerConfig), JSON.stringify(bundles.map(b => b.toSpec()))]; + const args = [JSON.stringify(workerConfig), JSON.stringify(bundles.map((b) => b.toSpec()))]; - const proc = fork(require.resolve('../worker/run_worker'), args, { - stdio: ['ignore', 'pipe', 'pipe', 'ipc'], - execArgv: [ + const proc = execa.node(require.resolve('../worker/run_worker'), args, { + nodeOptions: [ ...(inspectFlag && config.inspectWorkers ? [`${inspectFlag}=${inspectPortCounter++}`] : []), ...(config.maxWorkerCount <= 3 ? ['--max-old-space-size=2048'] : []), ], + buffer: false, + stderr: 'pipe', + stdout: 'pipe', }); return { @@ -94,7 +96,7 @@ function usingWorkerProc( }; }, - resource => { + (resource) => { const { proc } = resource as ProcResource; return fn(proc); } @@ -107,7 +109,7 @@ function observeStdio$(stream: Readable, name: WorkerStdio['stream']) { Rx.race( Rx.fromEvent(stream, 'end'), Rx.fromEvent(stream, 'error').pipe( - map(error => { + map((error) => { throw error; }) ) @@ -134,7 +136,7 @@ export function observeWorker( workerConfig: WorkerConfig, bundles: Bundle[] ): Rx.Observable { - return usingWorkerProc(config, workerConfig, bundles, proc => { + return usingWorkerProc(config, workerConfig, bundles, (proc) => { let lastMsg: WorkerMsg; return Rx.merge( @@ -161,7 +163,7 @@ export function observeWorker( Rx.race( // throw into stream on error events Rx.fromEvent(proc, 'error').pipe( - map(error => { + map((error) => { throw new Error(`worker failed to spawn: ${error.message}`); }) ), diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index d6336cf867470..37d8a4f5eb8ae 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -106,7 +106,7 @@ export class OptimizerConfig { ...(examples ? [Path.resolve('examples'), Path.resolve('x-pack/examples')] : []), Path.resolve(repoRoot, '../kibana-extra'), ]; - if (!pluginScanDirs.every(p => Path.isAbsolute(p))) { + if (!pluginScanDirs.every((p) => Path.isAbsolute(p))) { throw new TypeError('pluginScanDirs must all be absolute paths'); } @@ -118,7 +118,7 @@ export class OptimizerConfig { } const pluginPaths = options.pluginPaths || []; - if (!pluginPaths.every(s => Path.isAbsolute(s))) { + if (!pluginPaths.every((s) => Path.isAbsolute(s))) { throw new TypeError('pluginPaths must all be absolute paths'); } @@ -152,7 +152,7 @@ export class OptimizerConfig { new Bundle({ type: 'entry', id: 'core', - entry: './public/entry_point', + entry: './public/index', sourceRoot: options.repoRoot, contextDir: Path.resolve(options.repoRoot, 'src/core'), outputDir: Path.resolve(options.repoRoot, 'src/core/target/public'), diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_state.ts b/packages/kbn-optimizer/src/optimizer/optimizer_state.ts index ac2a9b8ce1f8b..1572f459e6ee5 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_state.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_state.ts @@ -80,7 +80,7 @@ function createOptimizerState( * calculate the total state, given a set of compiler messages */ function getStatePhase(states: CompilerMsg[]) { - const types = states.map(s => s.type); + const types = states.map((s) => s.type); if (types.includes('running')) { return 'running'; @@ -90,7 +90,7 @@ function getStatePhase(states: CompilerMsg[]) { return 'issue'; } - if (types.every(s => s === 'compiler success')) { + if (types.every((s) => s === 'compiler success')) { return 'success'; } @@ -173,7 +173,7 @@ export function createOptimizerStateSummarizer( event.type === 'running' ) { const compilerStates: CompilerMsg[] = [ - ...state.compilerStates.filter(c => c.bundleId !== event.bundleId), + ...state.compilerStates.filter((c) => c.bundleId !== event.bundleId), event, ]; return createOptimizerState(state, { diff --git a/packages/kbn-optimizer/src/optimizer/run_workers.ts b/packages/kbn-optimizer/src/optimizer/run_workers.ts index e91b0d25fd72b..1f277f011004d 100644 --- a/packages/kbn-optimizer/src/optimizer/run_workers.ts +++ b/packages/kbn-optimizer/src/optimizer/run_workers.ts @@ -50,15 +50,15 @@ export function runWorkers( return Rx.concat( // first batch of bundles are based on how up-to-date the cache is bundleCache$.pipe( - maybeMap(event => (event.type === 'bundle not cached' ? event.bundle : undefined)), + maybeMap((event) => (event.type === 'bundle not cached' ? event.bundle : undefined)), toArray() ), // subsequent batches are defined by changeEvent$ - changeEvent$.pipe(maybeMap(c => (c.type === 'changes' ? c.bundles : undefined))) + changeEvent$.pipe(maybeMap((c) => (c.type === 'changes' ? c.bundles : undefined))) ).pipe( - mergeMap(bundles => + mergeMap((bundles) => Rx.from(assignBundlesToWorkers(bundles, config.maxWorkerCount)).pipe( - mergeMap(assignment => + mergeMap((assignment) => observeWorker(config, config.getWorkerConfig(optimizerCacheKey), assignment.bundles) ) ) diff --git a/packages/kbn-optimizer/src/optimizer/watch_bundles_for_changes.ts b/packages/kbn-optimizer/src/optimizer/watch_bundles_for_changes.ts index 9149c483786fc..fdac4e0204fbe 100644 --- a/packages/kbn-optimizer/src/optimizer/watch_bundles_for_changes.ts +++ b/packages/kbn-optimizer/src/optimizer/watch_bundles_for_changes.ts @@ -38,7 +38,7 @@ function recursiveGetNextChange$( return !bundles.length ? Rx.EMPTY : watcher.getNextChange$(bundles, startTime).pipe( - mergeMap(event => { + mergeMap((event) => { if (event.type === 'changes detected') { return Rx.of(event); } @@ -48,7 +48,7 @@ function recursiveGetNextChange$( recursiveGetNextChange$( watcher, - bundles.filter(b => !event.bundles.includes(b)), + bundles.filter((b) => !event.bundles.includes(b)), Date.now() ) ); @@ -74,11 +74,11 @@ export function watchBundlesForChanges$( initialStartTime: number ) { return bundleCacheEvent$.pipe( - maybeMap(event => (event.type === 'bundle cached' ? event.bundle : undefined)), + maybeMap((event) => (event.type === 'bundle cached' ? event.bundle : undefined)), toArray(), - mergeMap(bundles => + mergeMap((bundles) => bundles.length - ? Watcher.using(watcher => recursiveGetNextChange$(watcher, bundles, initialStartTime)) + ? Watcher.using((watcher) => recursiveGetNextChange$(watcher, bundles, initialStartTime)) : Rx.EMPTY ) ); diff --git a/packages/kbn-optimizer/src/optimizer/watcher.ts b/packages/kbn-optimizer/src/optimizer/watcher.ts index 343f391921383..54c548755af5c 100644 --- a/packages/kbn-optimizer/src/optimizer/watcher.ts +++ b/packages/kbn-optimizer/src/optimizer/watcher.ts @@ -43,7 +43,7 @@ export class Watcher { static using(fn: (watcher: Watcher) => Rx.Observable) { return Rx.using( () => new Watcher(), - resource => fn(resource as Watcher) + (resource) => fn(resource as Watcher) ); } @@ -69,14 +69,14 @@ export class Watcher { // debounce and bufffer change events for 1 second to create // final change notification this.change$.pipe( - map(event => event[0]), + map((event) => event[0]), debounceTimeBuffer(1000), map( (changes): Changes => ({ type: 'changes', - bundles: bundles.filter(bundle => { + bundles: bundles.filter((bundle) => { const referencedFiles = bundle.cache.getReferencedFiles(); - return changes.some(change => referencedFiles?.includes(change)); + return changes.some((change) => referencedFiles?.includes(change)); }), }) ), diff --git a/packages/kbn-optimizer/src/report_optimizer_stats.ts b/packages/kbn-optimizer/src/report_optimizer_stats.ts index 06161fb2567b9..5f3153bff5175 100644 --- a/packages/kbn-optimizer/src/report_optimizer_stats.ts +++ b/packages/kbn-optimizer/src/report_optimizer_stats.ts @@ -29,14 +29,14 @@ export function reportOptimizerStats(reporter: CiStatsReporter, config: Optimize let lastState: OptimizerState | undefined; return update$.pipe( materialize(), - mergeMap(async n => { + mergeMap(async (n) => { if (n.kind === 'N' && n.value?.state) { lastState = n.value?.state; } if (n.kind === 'C' && lastState) { await reporter.metrics( - config.bundles.map(bundle => { + config.bundles.map((bundle) => { // make the cache read from the cache file since it was likely updated by the worker bundle.cache.refresh(); diff --git a/packages/kbn-optimizer/src/worker/run_compilers.ts b/packages/kbn-optimizer/src/worker/run_compilers.ts index 0dfce4b5addba..4ab289d031d72 100644 --- a/packages/kbn-optimizer/src/worker/run_compilers.ts +++ b/packages/kbn-optimizer/src/worker/run_compilers.ts @@ -65,8 +65,8 @@ const observeCompiler = ( * Called by webpack as a single run compilation is starting */ const started$ = Rx.merge( - Rx.fromEventPattern(cb => beforeRun.tap(PLUGIN_NAME, cb)), - Rx.fromEventPattern(cb => watchRun.tap(PLUGIN_NAME, cb)) + Rx.fromEventPattern((cb) => beforeRun.tap(PLUGIN_NAME, cb)), + Rx.fromEventPattern((cb) => watchRun.tap(PLUGIN_NAME, cb)) ).pipe(mapTo(compilerMsgs.running())); /** @@ -74,8 +74,8 @@ const observeCompiler = ( * needAdditionalPass property is set then another compilation * is about to be started, so we shouldn't send complete quite yet */ - const complete$ = Rx.fromEventPattern(cb => done.tap(PLUGIN_NAME, cb)).pipe( - maybeMap(stats => { + const complete$ = Rx.fromEventPattern((cb) => done.tap(PLUGIN_NAME, cb)).pipe( + maybeMap((stats) => { // @ts-ignore not included in types, but it is real https://github.com/webpack/webpack/blob/ab4fa8ddb3f433d286653cd6af7e3aad51168649/lib/Watching.js#L58 if (stats.compilation.needAdditionalPass) { return undefined; @@ -134,7 +134,7 @@ const observeCompiler = ( ); } - const files = Array.from(referencedFiles).sort(ascending(p => p)); + const files = Array.from(referencedFiles).sort(ascending((p) => p)); const mtimes = new Map( files.map((path): [string, number | undefined] => { try { @@ -167,8 +167,10 @@ const observeCompiler = ( * prevets assets from being emitted, and prevents watching * from continuing. */ - const error$ = Rx.fromEventPattern(cb => compiler.hooks.failed.tap(PLUGIN_NAME, cb)).pipe( - map(error => { + const error$ = Rx.fromEventPattern((cb) => + compiler.hooks.failed.tap(PLUGIN_NAME, cb) + ).pipe( + map((error) => { throw compilerMsgs.error(error); }) ); @@ -184,7 +186,7 @@ const observeCompiler = ( * Run webpack compilers */ export const runCompilers = (workerConfig: WorkerConfig, bundles: Bundle[]) => { - const multiCompiler = webpack(bundles.map(def => getWebpackConfig(def, workerConfig))); + const multiCompiler = webpack(bundles.map((def) => getWebpackConfig(def, workerConfig))); return Rx.merge( /** diff --git a/packages/kbn-optimizer/src/worker/run_worker.ts b/packages/kbn-optimizer/src/worker/run_worker.ts index cbec4c3f44c7d..f83c69477f471 100644 --- a/packages/kbn-optimizer/src/worker/run_worker.ts +++ b/packages/kbn-optimizer/src/worker/run_worker.ts @@ -82,10 +82,10 @@ Rx.defer(() => { return runCompilers(workerConfig, bundles); }).subscribe( - msg => { + (msg) => { send(msg); }, - error => { + (error) => { if (isWorkerMsg(error)) { send(error); } else { diff --git a/packages/kbn-optimizer/src/worker/theme_loader.ts b/packages/kbn-optimizer/src/worker/theme_loader.ts index 6d6686a5bde1b..5d02462ef1bb8 100644 --- a/packages/kbn-optimizer/src/worker/theme_loader.ts +++ b/packages/kbn-optimizer/src/worker/theme_loader.ts @@ -21,7 +21,7 @@ import webpack from 'webpack'; import { stringifyRequest } from 'loader-utils'; // eslint-disable-next-line import/no-default-export -export default function(this: webpack.loader.LoaderContext) { +export default function (this: webpack.loader.LoaderContext) { return ` if (window.__kbnDarkMode__) { require(${stringifyRequest(this, `${this.resourcePath}?dark`)}) diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 49bcc6e7e704c..dd003af7dc5e9 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -17,6 +17,7 @@ * under the License. */ +import Fs from 'fs'; import Path from 'path'; import normalizePath from 'normalize-path'; @@ -37,11 +38,32 @@ const IS_CODE_COVERAGE = !!process.env.CODE_COVERAGE; const ISTANBUL_PRESET_PATH = require.resolve('@kbn/babel-preset/istanbul_preset'); const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset'); -const STATIC_BUNDLE_PLUGINS = [ - { id: 'data', dirname: 'data' }, - { id: 'kibanaReact', dirname: 'kibana_react' }, - { id: 'kibanaUtils', dirname: 'kibana_utils' }, - { id: 'esUiShared', dirname: 'es_ui_shared' }, +const SHARED_BUNDLES = [ + { + type: 'entry', + id: 'core', + rootRelativeDir: 'src/core/public', + }, + { + type: 'plugin', + id: 'data', + rootRelativeDir: 'src/plugins/data/public', + }, + { + type: 'plugin', + id: 'kibanaReact', + rootRelativeDir: 'src/plugins/kibana_react/public', + }, + { + type: 'plugin', + id: 'kibanaUtils', + rootRelativeDir: 'src/plugins/kibana_utils/public', + }, + { + type: 'plugin', + id: 'esUiShared', + rootRelativeDir: 'src/plugins/es_ui_shared/public', + }, ]; /** @@ -56,18 +78,8 @@ const STATIC_BUNDLE_PLUGINS = [ * @param request the request for a module from a commonjs require() call or import statement */ function dynamicExternals(bundle: Bundle, context: string, request: string) { - // ignore imports that have loaders defined - if (request.includes('!')) { - return; - } - - // ignore requests that don't include a /{dirname}/public for one of our - // "static" bundles as a cheap way to avoid doing path resolution - // for paths that couldn't possibly resolve to what we're looking for - const reqToStaticBundle = STATIC_BUNDLE_PLUGINS.some(p => - request.includes(`/${p.dirname}/public`) - ); - if (!reqToStaticBundle) { + // ignore imports that have loaders defined or are not relative seeming + if (request.includes('!') || !request.startsWith('.')) { return; } @@ -75,10 +87,15 @@ function dynamicExternals(bundle: Bundle, context: string, request: string) { const rootRelative = normalizePath( Path.relative(bundle.sourceRoot, Path.resolve(context, request)) ); - for (const { id, dirname } of STATIC_BUNDLE_PLUGINS) { - if (rootRelative === `src/plugins/${dirname}/public`) { - return `__kbnBundles__['plugin/${id}']`; + for (const sharedBundle of SHARED_BUNDLES) { + if ( + rootRelative !== sharedBundle.rootRelativeDir || + `${bundle.type}/${bundle.id}` === `${sharedBundle.type}/${sharedBundle.id}` + ) { + continue; } + + return `__kbnBundles__['${sharedBundle.type}/${sharedBundle.id}']`; } // import doesn't match a root public import @@ -86,12 +103,17 @@ function dynamicExternals(bundle: Bundle, context: string, request: string) { } export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { + const extensions = ['.js', '.ts', '.tsx', '.json']; + const entryExtension = extensions.find((ext) => + Fs.existsSync(Path.resolve(bundle.contextDir, bundle.entry) + ext) + ); + const commonConfig: webpack.Configuration = { node: { fs: 'empty' }, context: bundle.contextDir, cache: true, entry: { - [bundle.id]: bundle.entry, + [bundle.id]: `${bundle.entry}${entryExtension}`, }, devtool: worker.dist ? false : '#cheap-source-map', @@ -100,19 +122,15 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { output: { path: bundle.outputDir, filename: `[name].${bundle.type}.js`, - devtoolModuleFilenameTemplate: info => + devtoolModuleFilenameTemplate: (info) => `/${bundle.type}:${bundle.id}/${Path.relative( bundle.sourceRoot, info.absoluteResourcePath )}${info.query}`, jsonpFunction: `${bundle.id}_bundle_jsonpfunction`, - ...(bundle.type === 'plugin' - ? { - // When the entry point is loaded, assign it's exported `plugin` - // value to a key on the global `__kbnBundles__` object. - library: ['__kbnBundles__', `plugin/${bundle.id}`], - } - : {}), + // When the entry point is loaded, assign it's default export + // to a key on the global `__kbnBundles__` object. + library: ['__kbnBundles__', `${bundle.type}/${bundle.id}`], }, optimization: { @@ -121,7 +139,7 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { externals: [ UiSharedDeps.externals, - function(context, request, cb) { + function (context, request, cb) { try { cb(undefined, dynamicExternals(bundle, context, request)); } catch (error) { @@ -144,7 +162,7 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { rules: [ { - include: Path.join(bundle.contextDir, bundle.entry), + include: [`${Path.resolve(bundle.contextDir, bundle.entry)}${entryExtension}`], loader: UiSharedDeps.publicPathLoader, options: { key: bundle.id, @@ -292,7 +310,7 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { }, resolve: { - extensions: ['.js', '.ts', '.tsx', '.json'], + extensions, mainFields: ['browser', 'main'], alias: { tinymath: require.resolve('tinymath/lib/tinymath.es5.js'), diff --git a/packages/kbn-plugin-generator/index.js b/packages/kbn-plugin-generator/index.js index 5f20569886d88..e61037e42d63f 100644 --- a/packages/kbn-plugin-generator/index.js +++ b/packages/kbn-plugin-generator/index.js @@ -61,7 +61,7 @@ exports.run = function run(argv) { name, targetPath, }, - }).catch(error => { + }).catch((error) => { console.error(chalk`{red fatal error}!`); console.error(error.stack); process.exit(1); diff --git a/packages/kbn-plugin-generator/sao_template/sao.js b/packages/kbn-plugin-generator/sao_template/sao.js index 9073ce865a963..7fc29b1e6bd0a 100755 --- a/packages/kbn-plugin-generator/sao_template/sao.js +++ b/packages/kbn-plugin-generator/sao_template/sao.js @@ -59,7 +59,7 @@ async function eslintPlugin(dir) { } } -module.exports = function({ name, targetPath }) { +module.exports = function ({ name, targetPath }) { return { prompts: { customPath: { @@ -99,7 +99,7 @@ module.exports = function({ name, targetPath }) { }, generateTranslations: { type: 'confirm', - when: answers => { + when: (answers) => { // only for 3rd party plugins return !answers.customPath && answers.generateApp; }, @@ -112,7 +112,7 @@ module.exports = function({ name, targetPath }) { generateScss: { type: 'confirm', message: 'Should SCSS be used?', - when: answers => answers.generateApp, + when: (answers) => answers.generateApp, default: true, }, generateEslint: { @@ -135,7 +135,7 @@ module.exports = function({ name, targetPath }) { 'eslintrc.js': '.eslintrc.js', 'i18nrc.json': '.i18nrc.json', }, - data: answers => { + data: (answers) => { const pathToPlugin = answers.customPath ? resolve(answers.customPath, camelCase(name), 'public') : resolve(targetPath, 'public'); diff --git a/packages/kbn-plugin-helpers/src/cli.ts b/packages/kbn-plugin-helpers/src/cli.ts index ee1bca0fe3ac2..b894f854a484f 100644 --- a/packages/kbn-plugin-helpers/src/cli.ts +++ b/packages/kbn-plugin-helpers/src/cli.ts @@ -35,7 +35,7 @@ enableCollectingUnknownOptions( .description('Start kibana and have it include this plugin') .on('--help', docs('start')) .action( - createCommanderAction('start', command => ({ + createCommanderAction('start', (command) => ({ flags: command.unknownOptions, })) ) @@ -75,7 +75,7 @@ program .option('-p, --plugins ', "Manually specify which plugins' test bundles to run") .on('--help', docs('test/karma')) .action( - createCommanderAction('testKarma', command => ({ + createCommanderAction('testKarma', (command) => ({ dev: Boolean(command.dev), plugins: command.plugins, })) diff --git a/packages/kbn-plugin-helpers/src/lib/docs.ts b/packages/kbn-plugin-helpers/src/lib/docs.ts index 68c095209e817..fb05fd0c5c2ce 100644 --- a/packages/kbn-plugin-helpers/src/lib/docs.ts +++ b/packages/kbn-plugin-helpers/src/lib/docs.ts @@ -28,7 +28,7 @@ function indent(txt: string, n: number) { export function docs(name: string) { const md = readFileSync(resolve(__dirname, '../../src/tasks', name, 'README.md'), 'utf8'); - return function() { + return function () { /* eslint-disable-next-line no-console */ console.log(`\n Docs:\n\n${indent(md, 4)}\n\n`); }; diff --git a/packages/kbn-plugin-helpers/src/lib/enable_collecting_unknown_options.ts b/packages/kbn-plugin-helpers/src/lib/enable_collecting_unknown_options.ts index 77fa7f2fcae84..0d692aed06305 100644 --- a/packages/kbn-plugin-helpers/src/lib/enable_collecting_unknown_options.ts +++ b/packages/kbn-plugin-helpers/src/lib/enable_collecting_unknown_options.ts @@ -22,7 +22,7 @@ import { Command } from 'commander'; export function enableCollectingUnknownOptions(command: Command) { const origParse = command.parseOptions; command.allowUnknownOption(); - command.parseOptions = function(argv: string[]) { + command.parseOptions = function (argv: string[]) { const opts = origParse.call(this, argv); this.unknownOptions = opts.unknown; return opts; diff --git a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/build_action_test_plugin/index.js b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/build_action_test_plugin/index.js index c2d2ade568761..052d224b662e2 100644 --- a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/build_action_test_plugin/index.js +++ b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/build_action_test_plugin/index.js @@ -17,7 +17,7 @@ * under the License. */ -module.exports = kibana => +module.exports = (kibana) => new kibana.Plugin({ uiExports: { hacks: ['plugins/test_plugin/hack.js'], diff --git a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_build_test_plugin/index.js b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_build_test_plugin/index.js index c2d2ade568761..052d224b662e2 100644 --- a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_build_test_plugin/index.js +++ b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_build_test_plugin/index.js @@ -17,7 +17,7 @@ * under the License. */ -module.exports = kibana => +module.exports = (kibana) => new kibana.Plugin({ uiExports: { hacks: ['plugins/test_plugin/hack.js'], diff --git a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_package_test_plugin/index.js b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_package_test_plugin/index.js index c2d2ade568761..052d224b662e2 100644 --- a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_package_test_plugin/index.js +++ b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/__fixtures__/create_package_test_plugin/index.js @@ -17,7 +17,7 @@ * under the License. */ -module.exports = kibana => +module.exports = (kibana) => new kibana.Plugin({ uiExports: { hacks: ['plugins/test_plugin/hack.js'], diff --git a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/build_action.test.js b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/build_action.test.js index f596576fe7466..d9f20129e85f9 100644 --- a/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/build_action.test.js +++ b/packages/kbn-plugin-helpers/src/tasks/build/integration_tests/build_action.test.js @@ -91,7 +91,7 @@ describe('calling create_build', () => { expect(mockBuild.mock.calls).toHaveLength(1); const { files } = nameArgs(mockBuild.mock.calls[0]); - plugin.buildSourcePatterns.forEach(file => expect(files).toContain(file)); + plugin.buildSourcePatterns.forEach((file) => expect(files).toContain(file)); }); it('uses only files passed in', async () => { @@ -104,7 +104,7 @@ describe('calling create_build', () => { expect(mockBuild.mock.calls).toHaveLength(1); const { files } = nameArgs(mockBuild.mock.calls[0]); - options.files.forEach(file => expect(files).toContain(file)); + options.files.forEach((file) => expect(files).toContain(file)); }); it('rejects returned promise when build fails', async () => { diff --git a/packages/kbn-plugin-helpers/src/tasks/build/rewrite_package_json.ts b/packages/kbn-plugin-helpers/src/tasks/build/rewrite_package_json.ts index 255b2e6ef9992..aaecd11ad82af 100644 --- a/packages/kbn-plugin-helpers/src/tasks/build/rewrite_package_json.ts +++ b/packages/kbn-plugin-helpers/src/tasks/build/rewrite_package_json.ts @@ -26,7 +26,7 @@ export function rewritePackageJson( buildVersion: string, kibanaVersion: string ) { - return Through2Map.obj(function(file: File) { + return Through2Map.obj(function (file: File) { if (file.basename === 'package.json' && file.dirname === buildSource) { const pkg = JSON.parse(file.contents!.toString('utf8')); diff --git a/packages/kbn-plugin-helpers/src/tasks/start/start_task.ts b/packages/kbn-plugin-helpers/src/tasks/start/start_task.ts index 75affb6da8c6f..5018fd7598180 100644 --- a/packages/kbn-plugin-helpers/src/tasks/start/start_task.ts +++ b/packages/kbn-plugin-helpers/src/tasks/start/start_task.ts @@ -35,7 +35,7 @@ export function startTask({ plugin, options }: TaskContext) { let args = nodeOptions.concat([script, '--dev', '--plugin-path', plugin.root]); if (Array.isArray(plugin.includePlugins)) { - plugin.includePlugins.forEach(path => { + plugin.includePlugins.forEach((path) => { args = args.concat(['--plugin-path', path]); }); } diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 1b670eb8cd816..21fff4d85ece6 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -4859,7 +4859,7 @@ class ProcRunner { this.closing = false; this.procs = []; this.signalUnsubscribe = exit_hook_1.default(() => { - this.teardown().catch(error => { + this.teardown().catch((error) => { log.error(`ProcRunner teardown error: ${error.stack}`); }); }); @@ -4898,7 +4898,7 @@ class ProcRunner { try { if (wait instanceof RegExp) { // wait for process to log matching line - await Rx.race(proc.lines$.pipe(operators_1.filter(line => wait.test(line)), operators_1.first(), operators_1.catchError(err => { + await Rx.race(proc.lines$.pipe(operators_1.filter((line) => wait.test(line)), operators_1.first(), operators_1.catchError((err) => { if (err.name !== 'EmptyError') { throw errors_1.createCliError(`[${name}] exited without matching pattern: ${wait}`); } @@ -4943,7 +4943,7 @@ class ProcRunner { * @return {Promise} */ async waitForAllToStop() { - await Promise.all(this.procs.map(proc => proc.outcomePromise)); + await Promise.all(this.procs.map((proc) => proc.outcomePromise)); } /** * Close the ProcRunner and stop all running @@ -4959,14 +4959,14 @@ class ProcRunner { this.closing = true; this.signalUnsubscribe(); if (!signal && this.procs.length > 0) { - this.log.warning('%d processes left running, stop them with procs.stop(name):', this.procs.length, this.procs.map(proc => proc.name)); + this.log.warning('%d processes left running, stop them with procs.stop(name):', this.procs.length, this.procs.map((proc) => proc.name)); } await Promise.all(this.procs.map(async (proc) => { await proc.stop(signal === 'exit' ? 'SIGKILL' : signal); })); } getProc(name) { - return this.procs.find(proc => { + return this.procs.find((proc) => { return proc.name === name; }); } @@ -4979,14 +4979,14 @@ class ProcRunner { }; // tie into proc outcome$, remove from _procs on compete proc.outcome$.subscribe({ - next: code => { + next: (code) => { const duration = moment_1.default.duration(Date.now() - startMs); this.log.info('[%s] exited with %s after %s', name, code, duration.humanize()); }, complete: () => { remove(); }, - error: error => { + error: (error) => { if (this.closing) { this.log.error(error); } @@ -33599,8 +33599,8 @@ function startProc(name, options, log) { return code; })), // observe first error event - Rx.fromEvent(childProcess, 'error').pipe(operators_1.take(1), operators_1.mergeMap(err => Rx.throwError(err)))).pipe(operators_1.share()); - const lines$ = Rx.merge(observe_lines_1.observeLines(childProcess.stdout), observe_lines_1.observeLines(childProcess.stderr)).pipe(operators_1.tap(line => log.write(` ${chalk_1.default.gray('proc')} [${chalk_1.default.gray(name)}] ${line}`)), operators_1.share()); + Rx.fromEvent(childProcess, 'error').pipe(operators_1.take(1), operators_1.mergeMap((err) => Rx.throwError(err)))).pipe(operators_1.share()); + const lines$ = Rx.merge(observe_lines_1.observeLines(childProcess.stdout), observe_lines_1.observeLines(childProcess.stderr)).pipe(operators_1.tap((line) => log.write(` ${chalk_1.default.gray('proc')} [${chalk_1.default.gray(name)}] ${line}`)), operators_1.share()); const outcomePromise = Rx.merge(lines$.pipe(operators_1.ignoreElements()), outcome$).toPromise(); async function stop(signal) { if (stopCalled) { @@ -34812,10 +34812,11 @@ const makeError = ({ const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}); const execaMessage = `Command ${prefix}: ${command}`; - const shortMessage = error instanceof Error ? `${execaMessage}\n${error.message}` : execaMessage; + const isError = Object.prototype.toString.call(error) === '[object Error]'; + const shortMessage = isError ? `${execaMessage}\n${error.message}` : execaMessage; const message = [shortMessage, stderr, stdout].filter(Boolean).join('\n'); - if (error instanceof Error) { + if (isError) { error.originalMessage = error.message; error.message = message; } else { @@ -36263,25 +36264,24 @@ module.exports = function (/*streams...*/) { "use strict"; -const mergePromiseProperty = (spawned, promise, property) => { - // Starting the main `promise` is deferred to avoid consuming streams - const value = typeof promise === 'function' ? - (...args) => promise()[property](...args) : - promise[property].bind(promise); - Object.defineProperty(spawned, property, { - value, - writable: true, - enumerable: false, - configurable: true - }); -}; +const nativePromisePrototype = (async () => {})().constructor.prototype; +const descriptors = ['then', 'catch', 'finally'].map(property => [ + property, + Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property) +]); // The return value is a mixin of `childProcess` and `Promise` const mergePromise = (spawned, promise) => { - mergePromiseProperty(spawned, promise, 'then'); - mergePromiseProperty(spawned, promise, 'catch'); - mergePromiseProperty(spawned, promise, 'finally'); + for (const [property, descriptor] of descriptors) { + // Starting the main `promise` is deferred to avoid consuming streams + const value = typeof promise === 'function' ? + (...args) => Reflect.apply(descriptor.value, promise(), args) : + descriptor.value.bind(promise); + + Reflect.defineProperty(spawned, property, {...descriptor, value}); + } + return spawned; }; @@ -36584,7 +36584,7 @@ const operators_1 = __webpack_require__(270); * - fails on the first "error" event */ function observeReadable(readable) { - return Rx.race(Rx.fromEvent(readable, 'end').pipe(operators_1.first(), operators_1.ignoreElements()), Rx.fromEvent(readable, 'error').pipe(operators_1.first(), operators_1.mergeMap(err => Rx.throwError(err)))); + return Rx.race(Rx.fromEvent(readable, 'end').pipe(operators_1.first(), operators_1.ignoreElements()), Rx.fromEvent(readable, 'error').pipe(operators_1.first(), operators_1.mergeMap((err) => Rx.throwError(err)))); } exports.observeReadable = observeReadable; @@ -36894,7 +36894,7 @@ class ToolingLogCollectingWriter extends tooling_log_text_writer_1.ToolingLogTex super({ level: 'verbose', writeTo: { - write: msg => { + write: (msg) => { // trim trailing new line this.messages.push(msg.slice(0, -1)); }, @@ -39475,7 +39475,7 @@ async function run(fn, options = {}) { level: tooling_log_1.pickLevelFromFlags(flags), writeTo: process.stdout, }); - process.on('unhandledRejection', error => { + process.on('unhandledRejection', (error) => { log.error('UNHANDLED PROMISE REJECTION'); log.error(error instanceof Error ? error @@ -39589,7 +39589,7 @@ function combineErrors(errors) { const exitCode = errors .filter(isFailError) .reduce((acc, error) => Math.max(acc, error.exitCode), 1); - const showHelp = errors.some(error => isFailError(error) && error.showHelp); + const showHelp = errors.some((error) => isFailError(error) && error.showHelp); const message = errors.reduce((acc, error) => { if (isFailError(error)) { return acc + '\n' + error.message; @@ -40091,7 +40091,7 @@ exports.uriencode = (strings, ...values) => { return queue.reduce((acc, string, i) => `${acc}${encodeURIComponent(values[i])}${string}`, leadingString); }; const DEFAULT_MAX_ATTEMPTS = 5; -const delay = (ms) => new Promise(resolve => { +const delay = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); }); class KbnClientRequester { @@ -43984,7 +43984,7 @@ class CiStatsReporter { const reason = ((_d = (_c = error) === null || _c === void 0 ? void 0 : _c.response) === null || _d === void 0 ? void 0 : _d.status) ? `${error.response.status} response` : 'no response'; this.log.warning(`failed to reach kibana-ci-stats service [reason=${reason}], retrying in ${attempt} seconds`); - await new Promise(resolve => setTimeout(resolve, attempt * 1000)); + await new Promise((resolve) => setTimeout(resolve, attempt * 1000)); } } } diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index be56fad8aa0ce..234877e9ae626 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -54,7 +54,7 @@ "multimatch": "^4.0.0", "ncp": "^2.0.0", "ora": "^1.4.0", - "prettier": "^1.19.1", + "prettier": "^2.0.5", "read-pkg": "^5.2.0", "rxjs": "^6.5.3", "spawn-sync": "^1.0.15", diff --git a/packages/kbn-pm/src/cli.ts b/packages/kbn-pm/src/cli.ts index c2f49356957f7..94f348e1835ed 100644 --- a/packages/kbn-pm/src/cli.ts +++ b/packages/kbn-pm/src/cli.ts @@ -28,8 +28,8 @@ import { log } from './utils/log'; function help() { const availableCommands = Object.keys(commands) - .map(commandName => commands[commandName]) - .map(command => `${command.name} - ${command.description}`); + .map((commandName) => commands[commandName]) + .map((command) => `${command.name} - ${command.description}`); log.write(dedent` usage: kbn [] diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index d0aa220f25f66..6146aeab21db4 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -69,7 +69,7 @@ export const BootstrapCommand: ICommand = { log.write(chalk.bold('\nLinking executables completed, running `kbn:bootstrap` scripts\n')); const checksums = options.cache ? await getAllChecksums(kbn, log) : false; - await parallelizeBatches(batchedProjects, async project => { + await parallelizeBatches(batchedProjects, async (project) => { if (project.hasScript('kbn:bootstrap')) { const cacheFile = new BootstrapCacheFile(kbn, project, checksums); if (cacheFile.isValid()) { diff --git a/packages/kbn-pm/src/commands/run.ts b/packages/kbn-pm/src/commands/run.ts index 2f4d9e8453d09..989bfef19c380 100644 --- a/packages/kbn-pm/src/commands/run.ts +++ b/packages/kbn-pm/src/commands/run.ts @@ -43,7 +43,7 @@ export const RunCommand: ICommand = { chalk.bold(`\nRunning script [${chalk.green(scriptName)}] in batched topological order\n`) ); - await parallelizeBatches(batchedProjects, async pkg => { + await parallelizeBatches(batchedProjects, async (pkg) => { if (pkg.hasScript(scriptName)) { await pkg.runScriptStreaming(scriptName, scriptArgs); } diff --git a/packages/kbn-pm/src/commands/watch.ts b/packages/kbn-pm/src/commands/watch.ts index b5c493372b04f..2e18b02a1c860 100644 --- a/packages/kbn-pm/src/commands/watch.ts +++ b/packages/kbn-pm/src/commands/watch.ts @@ -83,7 +83,7 @@ export const WatchCommand: ICommand = { batchedProjects.push([projects.get(kibanaProjectName)!]); } - await parallelizeBatches(batchedProjects, async pkg => { + await parallelizeBatches(batchedProjects, async (pkg) => { const completionHint = await waitUntilWatchIsReady( pkg.runScriptStreaming(watchScriptName).stdout ); diff --git a/packages/kbn-pm/src/production/build_production_projects.ts b/packages/kbn-pm/src/production/build_production_projects.ts index 0d4be8b016077..689bf51cd7bdf 100644 --- a/packages/kbn-pm/src/production/build_production_projects.ts +++ b/packages/kbn-pm/src/production/build_production_projects.ts @@ -46,7 +46,7 @@ export async function buildProductionProjects({ const projectGraph = buildProjectGraph(projects); const batchedProjects = topologicallyBatchProjects(projects, projectGraph); - const projectNames = [...projects.values()].map(project => project.name); + const projectNames = [...projects.values()].map((project) => project.name); log.write(`Preparing production build for [${projectNames.join(', ')}]`); for (const batch of batchedProjects) { @@ -82,7 +82,7 @@ async function getProductionProjects(rootPath: string, onlyOSS?: boolean) { productionProjects.delete('kibana'); if (onlyOSS) { - productionProjects.forEach(project => { + productionProjects.forEach((project) => { if (project.getBuildConfig().oss === false) { productionProjects.delete(project.json.name); } diff --git a/packages/kbn-pm/src/run.ts b/packages/kbn-pm/src/run.ts index 44bf5a91ee1b1..c3879c701d785 100644 --- a/packages/kbn-pm/src/run.ts +++ b/packages/kbn-pm/src/run.ts @@ -71,7 +71,7 @@ export async function runCommand(command: ICommand, config: Omit 0) { - const metaOutput = keys.map(key => { + const metaOutput = keys.map((key) => { const value = e.meta[key]; return `${key}: ${value}`; }); diff --git a/packages/kbn-pm/src/utils/bootstrap_cache_file.ts b/packages/kbn-pm/src/utils/bootstrap_cache_file.ts index 7d87179f34605..282483e10ccf2 100644 --- a/packages/kbn-pm/src/utils/bootstrap_cache_file.ts +++ b/packages/kbn-pm/src/utils/bootstrap_cache_file.ts @@ -39,7 +39,7 @@ export class BootstrapCacheFile { // sort deps by name so that the key is stable .sort((a, b) => a.name.localeCompare(b.name)) // get the cacheKey for each project, return undefined if the cache key couldn't be determined - .map(p => { + .map((p) => { const cacheKey = checksums.get(p.name); if (cacheKey) { return `${p.name}:${cacheKey}`; @@ -47,7 +47,7 @@ export class BootstrapCacheFile { }); // if any of the relevant cache keys are undefined then the projectCacheKey must be too - this.expectedValue = projectAndDepCacheKeys.some(k => !k) + this.expectedValue = projectAndDepCacheKeys.some((k) => !k) ? undefined : [ `# this is only human readable for debugging, please don't try to parse this`, diff --git a/packages/kbn-pm/src/utils/fs.ts b/packages/kbn-pm/src/utils/fs.ts index 9484c3a61e608..44fc59bdeba96 100644 --- a/packages/kbn-pm/src/utils/fs.ts +++ b/packages/kbn-pm/src/utils/fs.ts @@ -49,7 +49,7 @@ async function statTest(path: string, block: (stats: fs.Stats) => boolean) { * @param path */ export async function isSymlink(path: string) { - return await statTest(path, stats => stats.isSymbolicLink()); + return await statTest(path, (stats) => stats.isSymbolicLink()); } /** @@ -57,7 +57,7 @@ export async function isSymlink(path: string) { * @param path */ export async function isDirectory(path: string) { - return await statTest(path, stats => stats.isDirectory()); + return await statTest(path, (stats) => stats.isDirectory()); } /** @@ -65,7 +65,7 @@ export async function isDirectory(path: string) { * @param path */ export async function isFile(path: string) { - return await statTest(path, stats => stats.isFile()); + return await statTest(path, (stats) => stats.isFile()); } /** diff --git a/packages/kbn-pm/src/utils/kibana.ts b/packages/kbn-pm/src/utils/kibana.ts index 58af98b2a92db..7fca4bd01822b 100644 --- a/packages/kbn-pm/src/utils/kibana.ts +++ b/packages/kbn-pm/src/utils/kibana.ts @@ -103,11 +103,11 @@ export class Kibana { const allProjects = this.getAllProjects(); const filteredProjects: ProjectMap = new Map(); - const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); + const pkgJsonPaths = Array.from(allProjects.values()).map((p) => p.packageJsonLocation); const filteredPkgJsonGlobs = getProjectPaths({ ...options, rootPath: this.kibanaProject.path, - }).map(g => Path.resolve(g, 'package.json')); + }).map((g) => Path.resolve(g, 'package.json')); const matchingPkgJsonPaths = multimatch(pkgJsonPaths, filteredPkgJsonGlobs); for (const project of allProjects.values()) { diff --git a/packages/kbn-pm/src/utils/link_project_executables.test.ts b/packages/kbn-pm/src/utils/link_project_executables.test.ts index a6334ec850860..a19e1fd66f334 100644 --- a/packages/kbn-pm/src/utils/link_project_executables.test.ts +++ b/packages/kbn-pm/src/utils/link_project_executables.test.ts @@ -70,7 +70,7 @@ const projectGraph = buildProjectGraph(projectsByName); function getFsMockCalls() { const fs = require('./fs'); const fsMockCalls: { [key: string]: any[][] } = {}; - Object.keys(fs).map(key => { + Object.keys(fs).map((key) => { if (jest.isMockFunction(fs[key])) { fsMockCalls[key] = fs[key].mock.calls; } diff --git a/packages/kbn-pm/src/utils/link_project_executables.ts b/packages/kbn-pm/src/utils/link_project_executables.ts index 25fb11f17f782..b403dfb2ecf2e 100644 --- a/packages/kbn-pm/src/utils/link_project_executables.ts +++ b/packages/kbn-pm/src/utils/link_project_executables.ts @@ -55,9 +55,7 @@ export async function linkProjectExecutables( const dest = resolve(binsDir, name); // Get relative project path with normalized path separators. - const projectRelativePath = relative(project.path, srcPath) - .split(sep) - .join('/'); + const projectRelativePath = relative(project.path, srcPath).split(sep).join('/'); log.write(chalk`{dim [${project.name}]} ${name} -> {dim ${projectRelativePath}}`); diff --git a/packages/kbn-pm/src/utils/parallelize.test.ts b/packages/kbn-pm/src/utils/parallelize.test.ts index fa23ecbb8c1e7..e85b40e0c67d8 100644 --- a/packages/kbn-pm/src/utils/parallelize.test.ts +++ b/packages/kbn-pm/src/utils/parallelize.test.ts @@ -22,7 +22,7 @@ import { parallelizeBatches } from './parallelize'; // As promises resolve async, we use this helper to wait for all promises that // have been resolved to complete (aka call `then`). const tick = () => - new Promise(resolve => { + new Promise((resolve) => { setTimeout(resolve, 0); }); @@ -32,7 +32,7 @@ test('parallelizes batches', async () => { const baz = createPromiseWithResolve(); const batches = [[foo, bar], [baz]]; - const parallelize = parallelizeBatches(batches, async obj => { + const parallelize = parallelizeBatches(batches, async (obj) => { obj.called = true; await obj.promise; }); @@ -82,7 +82,7 @@ test('schedules at most 4 calls at the same time (concurrency)', async () => { const foobar = createPromiseWithResolve(); const batches = [[foo, bar, baz, quux, foobar]]; - const parallelize = parallelizeBatches(batches, async obj => { + const parallelize = parallelizeBatches(batches, async (obj) => { obj.called = true; await obj.promise; }); @@ -113,7 +113,7 @@ test('rejects if any promise rejects', async () => { const baz = createPromiseWithResolve(); const batches = [[foo, bar], [baz]]; - const parallelize = parallelizeBatches(batches, async obj => { + const parallelize = parallelizeBatches(batches, async (obj) => { await obj.promise; }); diff --git a/packages/kbn-pm/src/utils/project.ts b/packages/kbn-pm/src/utils/project.ts index 7b0bbed5c3f46..91a3a5365b60e 100644 --- a/packages/kbn-pm/src/utils/project.ts +++ b/packages/kbn-pm/src/utils/project.ts @@ -229,10 +229,10 @@ export class Project { // check for any cross-project dependency for (const name of Object.keys(workspacesInfo)) { const workspace = workspacesInfo[name]; - workspace.workspaceDependencies.forEach(w => unusedWorkspaces.delete(w)); + workspace.workspaceDependencies.forEach((w) => unusedWorkspaces.delete(w)); } - unusedWorkspaces.forEach(name => { + unusedWorkspaces.forEach((name) => { const { dependencies, devDependencies } = this.json; const nodeModulesPath = Path.resolve(this.nodeModulesLocation, name); const isDependency = dependencies && dependencies.hasOwnProperty(name); diff --git a/packages/kbn-pm/src/utils/project_checksums.ts b/packages/kbn-pm/src/utils/project_checksums.ts index 7d939e715d411..46dde1b32c158 100644 --- a/packages/kbn-pm/src/utils/project_checksums.ts +++ b/packages/kbn-pm/src/utils/project_checksums.ts @@ -49,8 +49,8 @@ async function getChangesForProjects(projects: ProjectMap, kbn: Kibana, log: Too '--exclude-standard', '--', ...Array.from(projects.values()) - .filter(p => kbn.isPartOfRepo(p)) - .map(p => p.path), + .filter((p) => kbn.isPartOfRepo(p)) + .map((p) => p.path), ], { cwd: kbn.getAbsolute(), @@ -265,7 +265,7 @@ export async function getAllChecksums(kbn: Kibana, log: ToolingLog) { const cacheKeys: ChecksumMap = new Map(); await Promise.all( - Array.from(projects.values()).map(async project => { + Array.from(projects.values()).map(async (project) => { cacheKeys.set( project.name, await getChecksum(project, changesByProject.get(project), yarnLock, kbn, log) diff --git a/packages/kbn-pm/src/utils/projects.test.ts b/packages/kbn-pm/src/utils/projects.test.ts index ba093b9d5eba1..068c72286872a 100644 --- a/packages/kbn-pm/src/utils/projects.test.ts +++ b/packages/kbn-pm/src/utils/projects.test.ts @@ -208,7 +208,7 @@ describe('#topologicallyBatchProjects', () => { test('batches projects topologically based on their project dependencies', async () => { const batches = topologicallyBatchProjects(projects, graph); - const expectedBatches = batches.map(batch => batch.map(project => project.name)); + const expectedBatches = batches.map((batch) => batch.map((project) => project.name)); expect(expectedBatches).toMatchSnapshot(); }); @@ -219,7 +219,7 @@ describe('#topologicallyBatchProjects', () => { const batches = topologicallyBatchProjects(projects, graph); - const expectedBatches = batches.map(batch => batch.map(project => project.name)); + const expectedBatches = batches.map((batch) => batch.map((project) => project.name)); expect(expectedBatches).toMatchSnapshot(); }); @@ -228,7 +228,7 @@ describe('#topologicallyBatchProjects', () => { test('batches projects topologically based on their project dependencies and workspaces', async () => { const batches = topologicallyBatchProjects(projects, graph, { batchByWorkspace: true }); - const expectedBatches = batches.map(batch => batch.map(project => project.name)); + const expectedBatches = batches.map((batch) => batch.map((project) => project.name)); expect(expectedBatches).toEqual([['kibana'], ['bar', 'foo'], ['baz', 'zorge'], ['quux']]); }); diff --git a/packages/kbn-pm/src/utils/projects.ts b/packages/kbn-pm/src/utils/projects.ts index a6f174b1fc5a1..1c3bf0fa3091a 100644 --- a/packages/kbn-pm/src/utils/projects.ts +++ b/packages/kbn-pm/src/utils/projects.ts @@ -137,7 +137,9 @@ export function topologicallyBatchProjects( const batches = []; if (batchByWorkspace) { - const workspaceRootProject = Array.from(projectsToBatch.values()).find(p => p.isWorkspaceRoot); + const workspaceRootProject = Array.from(projectsToBatch.values()).find( + (p) => p.isWorkspaceRoot + ); if (!workspaceRootProject) { throw new CliError(`There was no yarn workspace root found.`); @@ -167,7 +169,7 @@ export function topologicallyBatchProjects( const batch = []; for (const projectName of projectsLeftToBatch) { const projectDeps = projectGraph.get(projectName)!; - const needsDependenciesBatched = projectDeps.some(dep => projectsLeftToBatch.has(dep.name)); + const needsDependenciesBatched = projectDeps.some((dep) => projectsLeftToBatch.has(dep.name)); if (!needsDependenciesBatched) { batch.push(projectsToBatch.get(projectName)!); @@ -188,7 +190,7 @@ export function topologicallyBatchProjects( batches.push(batch); - batch.forEach(project => projectsLeftToBatch.delete(project.name)); + batch.forEach((project) => projectsLeftToBatch.delete(project.name)); } return batches; @@ -211,7 +213,7 @@ export function includeTransitiveProjects( ? project.productionDependencies : project.allDependencies; - Object.keys(dependencies).forEach(dep => { + Object.keys(dependencies).forEach((dep) => { if (allProjects.has(dep)) { toProcess.push(allProjects.get(dep)!); } diff --git a/packages/kbn-pm/src/utils/watch.ts b/packages/kbn-pm/src/utils/watch.ts index 0ec8b50d83905..1998c5199fb73 100644 --- a/packages/kbn-pm/src/utils/watch.ts +++ b/packages/kbn-pm/src/utils/watch.ts @@ -56,20 +56,20 @@ function getWatchHandlers( }: IWatchOptions ) { const typescriptHandler = buildOutput$.pipe( - first(data => data.includes('$ tsc')), + first((data) => data.includes('$ tsc')), map(() => buildOutput$.pipe( - first(data => data.includes('Compilation complete.')), + first((data) => data.includes('Compilation complete.')), mapTo('tsc') ) ) ); const webpackHandler = buildOutput$.pipe( - first(data => data.includes('$ webpack')), + first((data) => data.includes('$ webpack')), map(() => buildOutput$.pipe( - first(data => data.includes('Chunk Names')), + first((data) => data.includes('Chunk Names')), mapTo('webpack') ) ) @@ -100,7 +100,7 @@ export function waitUntilWatchIsReady(stream: NodeJS.EventEmitter, opts: IWatchO return Rx.race(getWatchHandlers(buildOutput$, opts)) .pipe( - mergeMap(whenReady => whenReady), + mergeMap((whenReady) => whenReady), finalize(() => { stream.removeListener('data', onDataListener); stream.removeListener('end', onEndListener); diff --git a/packages/kbn-pm/src/utils/workspaces.ts b/packages/kbn-pm/src/utils/workspaces.ts index 22fa8636aea90..830a713e84ad4 100644 --- a/packages/kbn-pm/src/utils/workspaces.ts +++ b/packages/kbn-pm/src/utils/workspaces.ts @@ -48,7 +48,7 @@ export async function workspacePackagePaths(rootPath: string): Promise for (const pattern of workspacesPathsPatterns) { if (pattern.startsWith('!')) { const pathToRemove = path.join(rootPath, pattern.slice(1), 'package.json'); - workspaceProjectsPaths = workspaceProjectsPaths.filter(p => p !== pathToRemove); + workspaceProjectsPaths = workspaceProjectsPaths.filter((p) => p !== pathToRemove); } } diff --git a/packages/kbn-spec-to-console/bin/spec_to_console.js b/packages/kbn-spec-to-console/bin/spec_to_console.js index 20b42c67f3b89..432890a9cb903 100644 --- a/packages/kbn-spec-to-console/bin/spec_to_console.js +++ b/packages/kbn-spec-to-console/bin/spec_to_console.js @@ -46,7 +46,7 @@ console.log(); console.log(files); console.log(); -files.forEach(file => { +files.forEach((file) => { const spec = JSON.parse(fs.readFileSync(file)); const convertedSpec = convert(spec); if (!Object.keys(convertedSpec).length) { diff --git a/packages/kbn-spec-to-console/lib/convert.js b/packages/kbn-spec-to-console/lib/convert.js index 9648ef0b85a4f..bd0dbb429cff3 100644 --- a/packages/kbn-spec-to-console/lib/convert.js +++ b/packages/kbn-spec-to-console/lib/convert.js @@ -22,7 +22,7 @@ const convertMethods = require('./convert/methods'); const convertPaths = require('./convert/paths'); const convertParts = require('./convert/parts'); -module.exports = spec => { +module.exports = (spec) => { const result = {}; /** * TODO: @@ -34,7 +34,7 @@ module.exports = spec => { * from being used in autocompletion. It would be really nice if we could use this information * instead of just not including it. */ - Object.keys(spec).forEach(api => { + Object.keys(spec).forEach((api) => { const source = spec[api]; if (!source.url) { @@ -42,7 +42,7 @@ module.exports = spec => { } if (source.url.path) { - if (source.url.paths.every(path => Boolean(path.deprecated))) { + if (source.url.paths.every((path) => Boolean(path.deprecated))) { return; } } @@ -61,10 +61,10 @@ module.exports = spec => { if (source.url.paths) { // We filter out all deprecated url patterns here. - const paths = source.url.paths.filter(path => !path.deprecated); + const paths = source.url.paths.filter((path) => !path.deprecated); patterns = convertPaths(paths); - paths.forEach(pathsObject => { - pathsObject.methods.forEach(method => methodSet.add(method)); + paths.forEach((pathsObject) => { + pathsObject.methods.forEach((method) => methodSet.add(method)); if (pathsObject.parts) { for (const partName of Object.keys(pathsObject.parts)) { urlComponents[partName] = pathsObject.parts[partName]; @@ -79,7 +79,7 @@ module.exports = spec => { if (Object.keys(urlComponents).length) { const components = convertParts(urlComponents); const hasComponents = - Object.keys(components).filter(c => { + Object.keys(components).filter((c) => { return Boolean(components[c]); }).length > 0; if (hasComponents) { diff --git a/packages/kbn-spec-to-console/lib/convert/methods.js b/packages/kbn-spec-to-console/lib/convert/methods.js index b4ab8f467ae1f..89d193dd38071 100644 --- a/packages/kbn-spec-to-console/lib/convert/methods.js +++ b/packages/kbn-spec-to-console/lib/convert/methods.js @@ -17,6 +17,6 @@ * under the License. */ -module.exports = methods => { +module.exports = (methods) => { return methods; }; diff --git a/packages/kbn-spec-to-console/lib/convert/params.js b/packages/kbn-spec-to-console/lib/convert/params.js index 0d1747ae4f685..00169b12322ed 100644 --- a/packages/kbn-spec-to-console/lib/convert/params.js +++ b/packages/kbn-spec-to-console/lib/convert/params.js @@ -17,9 +17,9 @@ * under the License. */ -module.exports = params => { +module.exports = (params) => { const result = {}; - Object.keys(params).forEach(param => { + Object.keys(params).forEach((param) => { const { type, description = '', options = [] } = params[param]; const [, defaultValue] = description.match(/\(default: (.*)\)/) || []; switch (type) { @@ -35,7 +35,7 @@ module.exports = params => { case 'enum': // This is to clean up entries like: "d (Days)". We only want the "d" part. if (param === 'time') { - result[param] = options.map(option => option.split(' ')[0]); + result[param] = options.map((option) => option.split(' ')[0]); } else { result[param] = options; } diff --git a/packages/kbn-spec-to-console/lib/convert/parts.js b/packages/kbn-spec-to-console/lib/convert/parts.js index 040d04a0c1dc4..96cd3c94e796d 100644 --- a/packages/kbn-spec-to-console/lib/convert/parts.js +++ b/packages/kbn-spec-to-console/lib/convert/parts.js @@ -19,9 +19,9 @@ const replacePattern = require('../replace_pattern'); -module.exports = parts => { +module.exports = (parts) => { const result = {}; - Object.keys(parts).forEach(part => { + Object.keys(parts).forEach((part) => { const key = replacePattern(part, { exact: true }); const options = parts[part].options; if (options && options.length) { diff --git a/packages/kbn-spec-to-console/lib/convert/paths.js b/packages/kbn-spec-to-console/lib/convert/paths.js index 6c65bf48b9b06..af8897c2782f2 100644 --- a/packages/kbn-spec-to-console/lib/convert/paths.js +++ b/packages/kbn-spec-to-console/lib/convert/paths.js @@ -19,8 +19,8 @@ const replacePattern = require('../replace_pattern'); -module.exports = patterns => { - return patterns.map(patternObject => { +module.exports = (patterns) => { + return patterns.map((patternObject) => { return replacePattern(patternObject.path, { brackets: true }); }); }; diff --git a/packages/kbn-spec-to-console/lib/replace_pattern.js b/packages/kbn-spec-to-console/lib/replace_pattern.js index 29d16be3cc70f..4da75db78086d 100644 --- a/packages/kbn-spec-to-console/lib/replace_pattern.js +++ b/packages/kbn-spec-to-console/lib/replace_pattern.js @@ -21,7 +21,7 @@ const map = require('./static/map_interpolation'); module.exports = (pattern, { brackets, exact } = {}) => { let newPattern = pattern; - Object.keys(map).forEach(key => { + Object.keys(map).forEach((key) => { const replaceFrom = brackets ? `{${key}}` : key; const replaceTo = brackets ? `{${map[key]}}` : map[key]; if (exact) { diff --git a/packages/kbn-spec-to-console/package.json b/packages/kbn-spec-to-console/package.json index a6b3e8f96f7db..ebbf244eb2e10 100644 --- a/packages/kbn-spec-to-console/package.json +++ b/packages/kbn-spec-to-console/package.json @@ -18,7 +18,7 @@ "homepage": "https://github.com/jbudz/spec-to-console#readme", "devDependencies": { "jest": "^24.9.0", - "prettier": "^1.19.1" + "prettier": "^2.0.5" }, "dependencies": { "commander": "^3.0.0", diff --git a/packages/kbn-storybook/index.js b/packages/kbn-storybook/index.js index b595de8ea1c07..c7dae20902f1a 100644 --- a/packages/kbn-storybook/index.js +++ b/packages/kbn-storybook/index.js @@ -27,7 +27,7 @@ const { generateStorybookEntry } = require('./lib/storybook_entry'); const { REPO_ROOT, ASSET_DIR, CURRENT_CONFIG } = require('./lib/constants'); const { buildDll } = require('./lib/dll'); -exports.runStorybookCli = config => { +exports.runStorybookCli = (config) => { const { name, storyGlobs } = config; run( async ({ flags, log, procRunner }) => { diff --git a/packages/kbn-storybook/lib/storybook_entry.js b/packages/kbn-storybook/lib/storybook_entry.js index dececef47f40e..9eb1b0a458c6a 100644 --- a/packages/kbn-storybook/lib/storybook_entry.js +++ b/packages/kbn-storybook/lib/storybook_entry.js @@ -37,7 +37,7 @@ const STORE_ENTRY_DIR = dirname(STORY_ENTRY_PATH); exports.generateStorybookEntry = ({ log, storyGlobs }) => { const globs = ['built_assets/css/**/*.light.css', ...storyGlobs]; log.info('Storybook globs:\n', globs); - const norm = p => normalize(relative(STORE_ENTRY_DIR, p)); + const norm = (p) => normalize(relative(STORE_ENTRY_DIR, p)); return Rx.defer(() => glob(globs, { @@ -46,20 +46,20 @@ exports.generateStorybookEntry = ({ log, storyGlobs }) => { onlyFiles: true, }) ).pipe( - map(paths => { + map((paths) => { log.info('Discovered Storybook entry points:\n', paths); return new Set(paths.map(norm)); }), mergeMap( - paths => - new Rx.Observable(observer => { + (paths) => + new Rx.Observable((observer) => { observer.next(paths); const chokidar = watch(globs, { cwd: REPO_ROOT }) - .on('add', path => { + .on('add', (path) => { observer.next(paths.add(norm(resolve(REPO_ROOT, path)))); }) - .on('unlink', path => { + .on('unlink', (path) => { observer.next(paths.delete(norm(resolve(REPO_ROOT, path)))); }); diff --git a/packages/kbn-storybook/storybook_config/middleware.js b/packages/kbn-storybook/storybook_config/middleware.js index 046758948b2cf..9410bb66030d9 100644 --- a/packages/kbn-storybook/storybook_config/middleware.js +++ b/packages/kbn-storybook/storybook_config/middleware.js @@ -21,6 +21,6 @@ const serve = require('serve-static'); const path = require('path'); // Extend the Storybook Middleware to include a route to access Legacy UI assets -module.exports = function(router) { +module.exports = function (router) { router.get('/ui', serve(path.resolve(__dirname, '../../../src/core/server/core_app/assets'))); }; diff --git a/packages/kbn-storybook/storybook_config/mocks/noop.js b/packages/kbn-storybook/storybook_config/mocks/noop.js index aaddfb2ed8ac3..e78d222eaa560 100755 --- a/packages/kbn-storybook/storybook_config/mocks/noop.js +++ b/packages/kbn-storybook/storybook_config/mocks/noop.js @@ -17,4 +17,4 @@ * under the License. */ -export default function() {} +export default function () {} diff --git a/packages/kbn-storybook/storybook_config/webpack.config.js b/packages/kbn-storybook/storybook_config/webpack.config.js index 779d8a4153644..2dd051882bb4b 100644 --- a/packages/kbn-storybook/storybook_config/webpack.config.js +++ b/packages/kbn-storybook/storybook_config/webpack.config.js @@ -29,7 +29,7 @@ const { currentConfig } = require('../../../built_assets/storybook/current.confi module.exports = async ({ config }) => { // Find and alter the CSS rule to replace the Kibana public path string with a path // to the route we've added in middleware.js - const cssRule = config.module.rules.find(rule => rule.test.source.includes('.css$')); + const cssRule = config.module.rules.find((rule) => rule.test.source.includes('.css$')); cssRule.use.push({ loader: 'string-replace-loader', options: { diff --git a/packages/kbn-test-subj-selector/__tests__/index.js b/packages/kbn-test-subj-selector/__tests__/index.js index e18405b99ae52..23165cefec94a 100755 --- a/packages/kbn-test-subj-selector/__tests__/index.js +++ b/packages/kbn-test-subj-selector/__tests__/index.js @@ -20,8 +20,8 @@ const testSubjSelector = require('../'); const expect = require('@kbn/expect'); -describe('testSubjSelector()', function() { - it('converts subjectSelectors to cssSelectors', function() { +describe('testSubjSelector()', function () { + it('converts subjectSelectors to cssSelectors', function () { expect(testSubjSelector('foo bar')).to.eql('[data-test-subj="foo bar"]'); expect(testSubjSelector('foo > bar')).to.eql('[data-test-subj="foo"] [data-test-subj="bar"]'); expect(testSubjSelector('foo > bar baz')).to.eql( diff --git a/packages/kbn-test-subj-selector/index.js b/packages/kbn-test-subj-selector/index.js index 3984c15c00fef..2be59d78dc5ef 100755 --- a/packages/kbn-test-subj-selector/index.js +++ b/packages/kbn-test-subj-selector/index.js @@ -42,12 +42,7 @@ module.exports = function testSubjSelector(selector) { while (terms.length) { const term = terms.shift(); // split each term by joins/& and map to css selectors - cssSelectors.push( - term - .split('&') - .map(termToCssSelector) - .join('') - ); + cssSelectors.push(term.split('&').map(termToCssSelector).join('')); } return cssSelectors.join(' '); diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts index 0c824754b1237..7cbeb18a5ebd4 100644 --- a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts @@ -25,12 +25,8 @@ import { createPatch } from 'diff'; // turns out Jest can't encode xml diffs in their JUnit reports... expect.addSnapshotSerializer({ - test: v => typeof v === 'string' && (v.includes('<') || v.includes('>')), - print: v => - v - .replace(//g, '›') - .replace(/^\s+$/gm, ''), + test: (v) => typeof v === 'string' && (v.includes('<') || v.includes('>')), + print: (v) => v.replace(//g, '›').replace(/^\s+$/gm, ''), }); jest.mock('fs', () => { diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts index 32ea5fa0f9033..6bc7556db8a47 100644 --- a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts +++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts @@ -49,7 +49,7 @@ export async function addMessagesToReport(options: { for (const testCase of makeFailedTestCaseIter(report)) { const { classname, name } = testCase.$; const messageList = messages - .filter(u => u.classname === classname && u.name === name) + .filter((u) => u.classname === classname && u.name === name) .reduce((acc, u) => `${acc}\n - ${u.message}`, ''); if (!messageList) { @@ -76,7 +76,7 @@ export async function addMessagesToReport(options: { const xml = builder .buildObject(report) .split('\n') - .map(line => (line.trim() === '' ? '' : line)) + .map((line) => (line.trim() === '' ? '' : line)) .join('\n'); if (dryRun) { diff --git a/packages/kbn-test/src/failed_tests_reporter/github_api.ts b/packages/kbn-test/src/failed_tests_reporter/github_api.ts index 7da79b5b67e63..a0e3bcafdf196 100644 --- a/packages/kbn-test/src/failed_tests_reporter/github_api.ts +++ b/packages/kbn-test/src/failed_tests_reporter/github_api.ts @@ -233,7 +233,7 @@ export class GithubApi { this.log.error(`Unable to reach github, waiting ${waitMs}ms to retry`); } - await new Promise(resolve => setTimeout(resolve, waitMs)); + await new Promise((resolve) => setTimeout(resolve, waitMs)); return await this.request( { ...options, diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts index 9324f9eb42aa5..3bcea44cf73b6 100644 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -49,8 +49,7 @@ export function runFailedTestsReporterCli() { } const isPr = !!process.env.ghprbPullId; - const isMasterOrVersion = - branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/); + const isMasterOrVersion = branch === 'master' || branch.match(/^\d+\.(x|\d+)$/); if (!isMasterOrVersion || isPr) { log.info('Failure issues only created on master/version branch jobs'); updateGithub = false; @@ -100,7 +99,7 @@ export function runFailedTestsReporterCli() { } let existingIssue: GithubIssueMini | undefined = await githubApi.findFailedTestIssue( - i => + (i) => getIssueMetadata(i.body, 'test.class') === failure.classname && getIssueMetadata(i.body, 'test.name') === failure.name ); diff --git a/packages/kbn-test/src/functional_test_runner/__tests__/integration/basic.js b/packages/kbn-test/src/functional_test_runner/__tests__/integration/basic.js index 3c8daf4154236..133f4d2feb53e 100644 --- a/packages/kbn-test/src/functional_test_runner/__tests__/integration/basic.js +++ b/packages/kbn-test/src/functional_test_runner/__tests__/integration/basic.js @@ -26,7 +26,7 @@ import { REPO_ROOT } from '@kbn/dev-utils'; const SCRIPT = resolve(REPO_ROOT, 'scripts/functional_test_runner.js'); const BASIC_CONFIG = require.resolve('../fixtures/simple_project/config.js'); -describe('basic config file with a single app and test', function() { +describe('basic config file with a single app and test', function () { this.timeout(60 * 1000); it('runs and prints expected output', () => { diff --git a/packages/kbn-test/src/functional_test_runner/__tests__/integration/failure_hooks.js b/packages/kbn-test/src/functional_test_runner/__tests__/integration/failure_hooks.js index d6e7b1ac58aa4..12e28d2702c5a 100644 --- a/packages/kbn-test/src/functional_test_runner/__tests__/integration/failure_hooks.js +++ b/packages/kbn-test/src/functional_test_runner/__tests__/integration/failure_hooks.js @@ -27,7 +27,7 @@ import { REPO_ROOT } from '@kbn/dev-utils'; const SCRIPT = resolve(REPO_ROOT, 'scripts/functional_test_runner.js'); const FAILURE_HOOKS_CONFIG = require.resolve('../fixtures/failure_hooks/config.js'); -describe('failure hooks', function() { +describe('failure hooks', function () { this.timeout(60 * 1000); it('runs and prints expected output', () => { diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts index 276a51c3a6a99..fd5ee5ad3ae44 100644 --- a/packages/kbn-test/src/functional_test_runner/cli.ts +++ b/packages/kbn-test/src/functional_test_runner/cli.ts @@ -87,7 +87,7 @@ export function runFtrCli() { } }; - process.on('unhandledRejection', err => + process.on('unhandledRejection', (err) => teardown( err instanceof Error ? err : new Error(`non-Error type rejection value: ${inspect(err)}`) ) diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index 3a66ba22ccf3d..03d4d7643607f 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -89,7 +89,7 @@ export class FunctionalTestRunner { // promise-like objects which never resolve, essentially disabling them // allowing us to load the test files and populate the mocha suites const readStubbedProviderSpec = (type: string, providers: any) => - readProviderSpec(type, providers).map(p => ({ + readProviderSpec(type, providers).map((p) => ({ ...p, fn: () => ({ then: () => {}, diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts index ad9247523797a..e38520f00e45b 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts @@ -114,7 +114,7 @@ export class Config { throw new Error(`Unknown config key "${key}"`); } - return cloneDeep(get(this[$values], key, defaultValue), v => { + return cloneDeep(get(this[$values], key, defaultValue), (v) => { if (typeof v === 'function') { return v; } @@ -122,7 +122,7 @@ export class Config { } public getAll() { - return cloneDeep(this[$values], v => { + return cloneDeep(this[$values], (v) => { if (typeof v === 'function') { return v; } diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index f4b91d154cbb8..29ec28175a851 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -30,12 +30,8 @@ const INSPECTING = const urlPartsSchema = () => Joi.object() .keys({ - protocol: Joi.string() - .valid('http', 'https') - .default('http'), - hostname: Joi.string() - .hostname() - .default('localhost'), + protocol: Joi.string().valid('http', 'https').default('http'), + hostname: Joi.string().hostname().default('localhost'), port: Joi.number(), auth: Joi.string().regex(/^[^:]+:.+$/, 'username and password separated by a colon'), username: Joi.string(), @@ -66,33 +62,21 @@ export const schema = Joi.object() suiteFiles: Joi.object() .keys({ - include: Joi.array() - .items(Joi.string()) - .default([]), - exclude: Joi.array() - .items(Joi.string()) - .default([]), + include: Joi.array().items(Joi.string()).default([]), + exclude: Joi.array().items(Joi.string()).default([]), }) .default(), suiteTags: Joi.object() .keys({ - include: Joi.array() - .items(Joi.string()) - .default([]), - exclude: Joi.array() - .items(Joi.string()) - .default([]), + include: Joi.array().items(Joi.string()).default([]), + exclude: Joi.array().items(Joi.string()).default([]), }) .default(), - services: Joi.object() - .pattern(ID_PATTERN, Joi.func().required()) - .default(), + services: Joi.object().pattern(ID_PATTERN, Joi.func().required()).default(), - pageObjects: Joi.object() - .pattern(ID_PATTERN, Joi.func().required()) - .default(), + pageObjects: Joi.object().pattern(ID_PATTERN, Joi.func().required()).default(), timeouts: Joi.object() .keys({ @@ -135,9 +119,7 @@ export const schema = Joi.object() browser: Joi.object() .keys({ - type: Joi.string() - .valid('chrome', 'firefox', 'ie', 'msedge') - .default('chrome'), + type: Joi.string().valid('chrome', 'firefox', 'ie', 'msedge').default('chrome'), logPollingMs: Joi.number().default(100), }) @@ -210,9 +192,7 @@ export const schema = Joi.object() .default(), // definition of apps that work with `common.navigateToApp()` - apps: Joi.object() - .pattern(ID_PATTERN, appUrlPartsSchema()) - .default(), + apps: Joi.object().pattern(ID_PATTERN, appUrlPartsSchema()).default(), // settings for the esArchiver module esArchiver: Joi.object() diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts index be033e063fb9d..fdf8b3c0ddfa8 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts @@ -39,7 +39,7 @@ export class FailureMetadata { ); } - lifecycle.beforeEachRunnable.add(runnable => { + lifecycle.beforeEachRunnable.add((runnable) => { this.currentRunnable = runnable; }); } @@ -57,7 +57,7 @@ export class FailureMetadata { } addMessages(messages: string[]) { - this.add(current => ({ + this.add((current) => ({ messages: [...(Array.isArray(current.messages) ? current.messages : []), ...messages], })); } @@ -76,7 +76,7 @@ export class FailureMetadata { const slash = prefix.endsWith('/') ? '' : '/'; const urlPath = Path.relative(REPO_ROOT, repoPath) .split(Path.sep) - .map(c => encodeURIComponent(c)) + .map((c) => encodeURIComponent(c)) .join('/'); if (urlPath.startsWith('..')) { @@ -91,7 +91,7 @@ export class FailureMetadata { url, }; - this.add(current => ({ + this.add((current) => ({ screenshots: [...(Array.isArray(current.screenshots) ? current.screenshots : []), screenshot], })); diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_event.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_event.ts index 22b7363454361..ce242d44009f2 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_event.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_event.ts @@ -57,7 +57,7 @@ export class LifecycleEvent { } try { - await Promise.all(this.handlers.map(async fn => await fn(...args))); + await Promise.all(this.handlers.map(async (fn) => await fn(...args))); } finally { this.afterSubj.next(undefined); if (this.options.singular) { diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts index 94dd76884f2ca..d17c5503c42f8 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts @@ -104,7 +104,7 @@ describe('without randomness', () => { const handler = jest.fn(async () => { order.push('handler start'); - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); order.push('handler done'); }); phase.add(handler); @@ -124,10 +124,10 @@ describe('without randomness', () => { const phase = new LifecyclePhase({ singular: true }); const beforeNotifs: Array> = []; - phase.before$.pipe(materialize()).subscribe(n => beforeNotifs.push(n)); + phase.before$.pipe(materialize()).subscribe((n) => beforeNotifs.push(n)); const afterNotifs: Array> = []; - phase.after$.pipe(materialize()).subscribe(n => afterNotifs.push(n)); + phase.after$.pipe(materialize()).subscribe((n) => afterNotifs.push(n)); await phase.trigger(); expect(beforeNotifs).toMatchInlineSnapshot(` diff --git a/packages/kbn-test/src/functional_test_runner/lib/load_tracer.ts b/packages/kbn-test/src/functional_test_runner/lib/load_tracer.ts index 26a0e9617a7c7..588e32b5d274c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/load_tracer.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/load_tracer.ts @@ -22,7 +22,7 @@ const globalLoadPath: Array<{ ident: string; description: string }> = []; function getPath(startAt = 0) { return globalLoadPath .slice(startAt) - .map(step => step.description) + .map((step) => step.description) .join(' -> '); } @@ -49,7 +49,7 @@ function addPathToMessage(message: string, startAt?: number) { * @return {Any} the value produced by load() */ export function loadTracer(ident: any, description: string, load: () => Promise | void) { - const isCircular = globalLoadPath.find(step => step.ident === ident); + const isCircular = globalLoadPath.find((step) => step.ident === ident); if (isCircular) { throw new Error(addPathToMessage(`Circular reference to "${description}"`)); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/assignment_proxy.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/assignment_proxy.js index 5c08d566d3d73..ecf8f7af87ed8 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/assignment_proxy.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/assignment_proxy.js @@ -31,7 +31,7 @@ export function createAssignmentProxy(object, interceptor) { get(target, property) { if (property === 'revertProxiedAssignments') { - return function() { + return function () { for (const [property, value] of originalValues) { object[property] = value; } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js index 1cac852a7e713..5d3d8fe7d759b 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js @@ -57,12 +57,12 @@ export function decorateMochaUi(lifecycle, context) { throw new Error(`Unexpected arguments to ${name}(${argumentsList.join(', ')})`); } - argumentsList[1] = function() { + argumentsList[1] = function () { before(async () => { await lifecycle.beforeTestSuite.trigger(this); }); - this.tags = tags => { + this.tags = (tags) => { this._tags = [].concat(this._tags || [], tags); }; diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js index 302d43fac3e61..f7aaabd5a4495 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js @@ -30,7 +30,7 @@ export function filterSuitesByTags({ log, mocha, include, exclude }) { mocha.excludedTests = []; // collect all the tests from some suite, including it's children - const collectTests = suite => + const collectTests = (suite) => suite.suites.reduce((acc, s) => acc.concat(collectTests(s)), suite.tests); // if include tags were provided, filter the tree once to @@ -38,8 +38,10 @@ export function filterSuitesByTags({ log, mocha, include, exclude }) { if (include.length) { log.info('Only running suites (and their sub-suites) if they include the tag(s):', include); - const isIncluded = suite => (!suite._tags ? false : suite._tags.some(t => include.includes(t))); - const isChildIncluded = suite => suite.suites.some(s => isIncluded(s) || isChildIncluded(s)); + const isIncluded = (suite) => + !suite._tags ? false : suite._tags.some((t) => include.includes(t)); + const isChildIncluded = (suite) => + suite.suites.some((s) => isIncluded(s) || isChildIncluded(s)); (function recurse(parentSuite) { const children = parentSuite.suites; @@ -73,7 +75,7 @@ export function filterSuitesByTags({ log, mocha, include, exclude }) { if (exclude.length) { log.info('Filtering out any suites that include the tag(s):', exclude); - const isNotExcluded = suite => !suite._tags || !suite._tags.some(t => exclude.includes(t)); + const isNotExcluded = (suite) => !suite._tags || !suite._tags.some((t) => exclude.includes(t)); (function recurse(parentSuite) { const children = parentSuite.suites; diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js index 9901f62ae71cf..6ecfadfd25d6d 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js @@ -26,21 +26,21 @@ import Test from 'mocha/lib/test'; import { filterSuitesByTags } from './filter_suites_by_tags'; function setup({ include, exclude }) { - return new Promise(resolve => { + return new Promise((resolve) => { const history = []; const mocha = new Mocha({ reporter: class { constructor(runner) { - runner.on('hook', hook => { + runner.on('hook', (hook) => { history.push(`hook: ${hook.fullTitle()}`); }); - runner.on('pass', test => { + runner.on('pass', (test) => { history.push(`test: ${test.fullTitle()}`); }); - runner.on('suite', suite => { + runner.on('suite', (suite) => { history.push(`suite: ${suite.fullTitle()}`); }); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js index 6ee65b1b7e394..5c23be6361866 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js @@ -32,7 +32,7 @@ import { decorateMochaUi } from './decorate_mocha_ui'; * @return {undefined} - mutates mocha, no return value */ export const loadTestFiles = ({ mocha, log, lifecycle, providers, paths, updateBaselines }) => { - const innerLoadTestFile = path => { + const innerLoadTestFile = (path) => { if (typeof path !== 'string' || !isAbsolute(path)) { throw new TypeError('loadTestFile() only accepts absolute paths'); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js index 0e8c1bc121e15..90bea1c3aa293 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js @@ -54,7 +54,7 @@ export function MochaReporterProvider({ getService }) { if (config.get('junit.enabled') && config.get('junit.reportName')) { setupJUnitReportGeneration(runner, { reportName: config.get('junit.reportName'), - getTestMetadata: t => failureMetadata.get(t), + getTestMetadata: (t) => failureMetadata.get(t), }); } } @@ -76,7 +76,7 @@ export function MochaReporterProvider({ getService }) { new ToolingLogTextWriter({ level: 'debug', writeTo: { - write: line => { + write: (line) => { // if the current runnable is a beforeEach hook then // `runner.suite` is set to the suite that defined the // hook, rather than the suite executing, so instead we @@ -104,7 +104,7 @@ export function MochaReporterProvider({ getService }) { log.write(''); }; - onHookStart = hook => { + onHookStart = (hook) => { log.write(`-> ${colors.suite(hook.title)}`); log.indent(2); }; @@ -113,7 +113,7 @@ export function MochaReporterProvider({ getService }) { log.indent(-2); }; - onSuiteStart = suite => { + onSuiteStart = (suite) => { if (!suite.root) { log.write('-: ' + colors.suite(suite.title)); } @@ -127,28 +127,28 @@ export function MochaReporterProvider({ getService }) { } }; - onTestStart = test => { + onTestStart = (test) => { log.write(`-> ${test.title}`); log.indent(2); }; - onTestEnd = test => { + onTestEnd = (test) => { snapshotLogsForRunnable(test); log.indent(-2); }; - onPending = test => { + onPending = (test) => { log.write('-> ' + colors.pending(test.title)); log.indent(2); }; - onPass = test => { + onPass = (test) => { const time = colors.speed(test.speed, ` (${ms(test.duration)})`); const pass = colors.pass(`${symbols.ok} pass`); log.write(`- ${pass} ${time} "${test.fullTitle()}"`); }; - onFail = runnable => { + onFail = (runnable) => { // NOTE: this is super gross // // - I started by trying to extract the Base.list() logic from mocha @@ -173,8 +173,8 @@ export function MochaReporterProvider({ getService }) { // drop the first two lines, (empty + test title) .slice(2) // move leading colors behind leading spaces - .map(line => line.replace(/^((?:\[.+m)+)(\s+)/, '$2$1')) - .map(line => ` ${line}`) + .map((line) => line.replace(/^((?:\[.+m)+)(\s+)/, '$2$1')) + .map((line) => ` ${line}`) .join('\n') ); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts index 654f588fda858..a23a5fb1407a0 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts @@ -39,7 +39,7 @@ export async function runTests(lifecycle: Lifecycle, mocha: Mocha) { if (!runComplete) runner.abort(); }); - return new Promise(resolve => { + return new Promise((resolve) => { const respond = () => resolve(runner.failures); // if there are no tests, mocha.run() is sync diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js index 61851cece0e8f..3ac7a50cd28ea 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js @@ -42,7 +42,7 @@ export async function setupMocha(lifecycle, log, config, providers) { }); // global beforeEach hook in root suite triggers before all others - mocha.suite.beforeEach('global before each', async function() { + mocha.suite.beforeEach('global before each', async function () { await lifecycle.beforeEachTest.trigger(this.currentTest); }); @@ -62,15 +62,15 @@ export async function setupMocha(lifecycle, log, config, providers) { filterSuitesByTags({ log, mocha, - include: config.get('suiteFiles.include').map(file => relative(REPO_ROOT, file)), - exclude: config.get('suiteFiles.exclude').map(file => relative(REPO_ROOT, file)), + include: config.get('suiteFiles.include').map((file) => relative(REPO_ROOT, file)), + exclude: config.get('suiteFiles.exclude').map((file) => relative(REPO_ROOT, file)), }); filterSuitesByTags({ log, mocha, - include: config.get('suiteTags.include').map(tag => tag.replace(/-\d+$/, '')), - exclude: config.get('suiteTags.exclude').map(tag => tag.replace(/-\d+$/, '')), + include: config.get('suiteTags.include').map((tag) => tag.replace(/-\d+$/, '')), + exclude: config.get('suiteTags.exclude').map((tag) => tag.replace(/-\d+$/, '')), }); return mocha; diff --git a/packages/kbn-test/src/functional_test_runner/lib/providers/async_instance.ts b/packages/kbn-test/src/functional_test_runner/lib/providers/async_instance.ts index 7bb1b2bc153c1..2d5644fbad290 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/providers/async_instance.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/providers/async_instance.ts @@ -34,7 +34,7 @@ export const createAsyncInstance = ( ): AsyncInstance => { let instance: T | symbol = INITIALIZING; - const initPromise = promiseForValue.then(v => (instance = v)); + const initPromise = promiseForValue.then((v) => (instance = v)); const loadingTarget = { init() { return initPromise; diff --git a/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts b/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts index f9ad86be634fc..c58747e07dcf4 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts @@ -37,7 +37,7 @@ export class ProviderCollection { public getPageObjects = (names: string[]) => { const pageObjects: Record = {}; - names.forEach(name => (pageObjects[name] = this.getPageObject(name))); + names.forEach((name) => (pageObjects[name] = this.getPageObject(name))); return pageObjects; }; @@ -78,7 +78,7 @@ export class ProviderCollection { } private findProvider(type: string, name: string) { - return this.providers.find(p => p.type === type && p.name === name); + return this.providers.find((p) => p.type === type && p.name === name); } private getProvider(type: string, name: string) { diff --git a/packages/kbn-test/src/functional_test_runner/lib/providers/read_provider_spec.ts b/packages/kbn-test/src/functional_test_runner/lib/providers/read_provider_spec.ts index be8e25f102b09..a29b220bc603b 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/providers/read_provider_spec.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/providers/read_provider_spec.ts @@ -21,7 +21,7 @@ export type Providers = ReturnType; export type Provider = Providers extends Array ? X : unknown; export function readProviderSpec(type: string, providers: Record any>) { - return Object.keys(providers).map(name => { + return Object.keys(providers).map((name) => { return { type, name, diff --git a/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts b/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts index 93a87f3496b54..1967e98306d42 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts @@ -23,7 +23,7 @@ import { ToolingLog } from '@kbn/dev-utils'; function printArgs(args: any[]): string { return args - .map(arg => { + .map((arg) => { if (typeof arg === 'string' || typeof arg === 'number' || arg instanceof Date) { return inspect(arg); } @@ -42,7 +42,7 @@ export function createVerboseInstance( name: string, instance: { [k: string]: any; [i: number]: any } ) { - if (!log.getWriters().some(l => (l as any).level.flags.verbose)) { + if (!log.getWriters().some((l) => (l as any).level.flags.verbose)) { return instance; } @@ -54,7 +54,7 @@ export function createVerboseInstance( return value; } - return function(this: any, ...args: any[]) { + return function (this: any, ...args: any[]) { log.verbose(`${name}.${prop}(${printArgs(args)})`); log.indent(2); diff --git a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts index b6c2c0a6d511d..f879408bf2beb 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts @@ -145,8 +145,8 @@ describe('SuiteTracker', () => { const { suiteTracker } = await runLifecycleWithMocks([root, parent, withTests]); const suites = suiteTracker.getAllFinishedSuites(); - const finishedRoot = suites.find(s => s.title === 'root'); - const finishedWithTests = suites.find(s => s.title !== 'root'); + const finishedRoot = suites.find((s) => s.title === 'root'); + const finishedWithTests = suites.find((s) => s.title !== 'root'); expect(finishedRoot).toBeTruthy(); expect(finishedRoot?.hasTests).toBeFalsy(); diff --git a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.ts b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.ts index 8967251ea78de..b346be2d58dad 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.ts @@ -70,7 +70,7 @@ export class SuiteTracker { const config = relative(REPO_ROOT, configPathAbsolute); - lifecycle.beforeTestSuite.add(suite => { + lifecycle.beforeTestSuite.add((suite) => { const tracked = this.getTracked(suite); tracked.startTime = new Date(); }); @@ -92,7 +92,7 @@ export class SuiteTracker { lifecycle.testFailure.add(handleFailure); lifecycle.testHookFailure.add(handleFailure); - lifecycle.afterTestSuite.add(suite => { + lifecycle.afterTestSuite.add((suite) => { const tracked = this.getTracked(suite); tracked.endTime = new Date(); diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js index 7d2414305de8e..94d510915d8e5 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js @@ -73,8 +73,8 @@ const options = { export function displayHelp() { const helpOptions = Object.keys(options) - .filter(name => name !== '_') - .map(name => { + .filter((name) => name !== '_') + .map((name) => { const option = options[name]; return { ...option, @@ -82,7 +82,7 @@ export function displayHelp() { default: option.defaultHelp || '', }; }) - .map(option => { + .map((option) => { return `--${option.usage.padEnd(28)} ${option.desc} ${option.default}`; }) .join(`\n `); @@ -149,7 +149,7 @@ export function processOptions(userOptions, defaultConfigPaths) { return { ...userOptions, - configs: configs.map(c => resolve(c)), + configs: configs.map((c) => resolve(c)), createLogger, extraKbnOpts: userOptions._, }; diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.js b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.js index 27335739d290e..cf49fc77e479f 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.js @@ -30,7 +30,7 @@ import { processOptions, displayHelp } from './args'; * if no config option is passed */ export async function runTestsCli(defaultConfigPaths) { - await runCli(displayHelp, async userOptions => { + await runCli(displayHelp, async (userOptions) => { const options = processOptions(userOptions, defaultConfigPaths); await runTests(options); }); diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/args.js b/packages/kbn-test/src/functional_tests/cli/start_servers/args.js index c221ad42fcad1..e604e86de8b3a 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/args.js +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/args.js @@ -45,8 +45,8 @@ const options = { export function displayHelp() { const helpOptions = Object.keys(options) - .filter(name => name !== '_') - .map(name => { + .filter((name) => name !== '_') + .map((name) => { const option = options[name]; return { ...option, @@ -54,7 +54,7 @@ export function displayHelp() { default: option.defaultHelp || '', }; }) - .map(option => { + .map((option) => { return `--${option.usage.padEnd(30)} ${option.desc} ${option.default}`; }) .join(`\n `); diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.js b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.js index 5716441798aa4..d4499ee76e313 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.js +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.js @@ -27,7 +27,7 @@ import { processOptions, displayHelp } from './args'; * if no config option is passed */ export async function startServersCli(defaultConfigPath) { - await runCli(displayHelp, async userOptions => { + await runCli(displayHelp, async (userOptions) => { const options = processOptions(userOptions, defaultConfigPath); await startServers(options); }); diff --git a/packages/kbn-test/src/functional_tests/lib/run_cli.js b/packages/kbn-test/src/functional_tests/lib/run_cli.js index 56f6f36f5388f..51a970e1a305d 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_cli.js +++ b/packages/kbn-test/src/functional_tests/lib/run_cli.js @@ -53,12 +53,7 @@ export async function runCli(getHelpText, run) { if (!(error instanceof CliError)) { // first line in the stack trace is the message, skip it as we log it directly and color it red if (error.stack) { - console.log( - error.stack - .split('\n') - .slice(1) - .join('\n') - ); + console.log(error.stack.split('\n').slice(1).join('\n')); } else { console.log(' (no stack trace)'); } diff --git a/packages/kbn-test/src/functional_tests/lib/run_cli.test.js b/packages/kbn-test/src/functional_tests/lib/run_cli.test.js index 235f50f0d9dd7..959f965917530 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_cli.test.js +++ b/packages/kbn-test/src/functional_tests/lib/run_cli.test.js @@ -25,7 +25,7 @@ const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); const actualProcessArgv = process.argv; -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); beforeEach(() => { process.argv = actualProcessArgv.slice(0, 2); @@ -72,7 +72,7 @@ it('waits for promise returned from run function to resolve before resolving', a let resolveMockRun; const mockRun = jest.fn().mockImplementation( () => - new Promise(resolve => { + new Promise((resolve) => { resolveMockRun = resolve; }) ); diff --git a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js index 5f58190078f0d..3d174791fffc1 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js +++ b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js @@ -63,7 +63,7 @@ export async function runElasticsearch({ config, options }) { function getRelativeCertificateAuthorityPath(esConfig = []) { const caConfig = esConfig.find( - config => config.indexOf('--elasticsearch.ssl.certificateAuthorities') === 0 + (config) => config.indexOf('--elasticsearch.ssl.certificateAuthorities') === 0 ); return caConfig ? caConfig.split('=')[1] : undefined; } diff --git a/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js b/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js index a5744d6498801..fb9f8f7a52408 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js +++ b/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js @@ -58,9 +58,9 @@ function collectCliArgs(config, { installDir, extraKbnOpts }) { return pipe( serverArgs, - args => (installDir ? args.filter(a => a !== '--oss') : args), - args => (installDir ? [...buildArgs, ...args] : [KIBANA_EXEC_PATH, ...sourceArgs, ...args]), - args => args.concat(extraKbnOpts || []) + (args) => (installDir ? args.filter((a) => a !== '--oss') : args), + (args) => (installDir ? [...buildArgs, ...args] : [KIBANA_EXEC_PATH, ...sourceArgs, ...args]), + (args) => args.concat(extraKbnOpts || []) ); } @@ -79,7 +79,7 @@ function filterCliArgs(args) { // the current val. If so, skip this val. if ( !allowsDuplicate(val) && - findIndexFrom(args, ++ind, opt => opt.split('=')[0] === val.split('=')[0]) > -1 + findIndexFrom(args, ++ind, (opt) => opt.split('=')[0] === val.split('=')[0]) > -1 ) { return acc; } @@ -112,7 +112,7 @@ function isBasePathSettingOverridden(args, val, ind) { const basePathKeys = ['--no-base-path', '--server.basePath']; if (basePathKeys.includes(key)) { - if (findIndexFrom(args, ++ind, opt => basePathKeys.includes(opt.split('=')[0])) > -1) { + if (findIndexFrom(args, ++ind, (opt) => basePathKeys.includes(opt.split('=')[0])) > -1) { return true; } } diff --git a/packages/kbn-test/src/functional_tests/tasks.js b/packages/kbn-test/src/functional_tests/tasks.js index 8645923a13d30..7d4fc84d47bda 100644 --- a/packages/kbn-test/src/functional_tests/tasks.js +++ b/packages/kbn-test/src/functional_tests/tasks.js @@ -34,7 +34,7 @@ import { import { readConfigFile } from '../functional_test_runner/lib'; -const makeSuccessMessage = options => { +const makeSuccessMessage = (options) => { const installDirFlag = options.installDir ? ` --kibana-install-dir=${options.installDir}` : ''; return ( @@ -92,7 +92,7 @@ export async function runTests(options) { continue; } - await withProcRunner(log, async procs => { + await withProcRunner(log, async (procs) => { const config = await readConfigFile(log, configPath); let es; @@ -128,7 +128,7 @@ export async function startServers(options) { log, }; - await withProcRunner(log, async procs => { + await withProcRunner(log, async (procs) => { const config = await readConfigFile(log, options.config); const es = await runElasticsearch({ config, options: opts }); diff --git a/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js b/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js index 6edd0a551ebd0..00a11432dd9e8 100644 --- a/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js @@ -49,8 +49,8 @@ describe('dev/mocha/junit report generation', () => { }); mocha.addFile(resolve(PROJECT_DIR, 'test.js')); - await new Promise(resolve => mocha.run(resolve)); - const report = await fcb(cb => + await new Promise((resolve) => mocha.run(resolve)); + const report = await fcb((cb) => parseString(readFileSync(makeJunitReportPath(PROJECT_DIR, 'test')), cb) ); diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index b56741b48d367..7e39c32ee4db8 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -39,26 +39,26 @@ export function setupJUnitReportGeneration(runner, options = {}) { const stats = {}; const results = []; - const getDuration = node => + const getDuration = (node) => node.startTime && node.endTime ? ((node.endTime - node.startTime) / 1000).toFixed(3) : null; - const findAllTests = suite => + const findAllTests = (suite) => suite.suites.reduce((acc, suite) => acc.concat(findAllTests(suite)), suite.tests); - const setStartTime = node => { + const setStartTime = (node) => { node.startTime = dateNow(); }; - const setEndTime = node => { + const setEndTime = (node) => { node.endTime = dateNow(); }; - const getFullTitle = node => { + const getFullTitle = (node) => { const parentTitle = node.parent && getFullTitle(node.parent); return parentTitle ? `${parentTitle} ${node.title}` : node.title; }; - const getPath = node => { + const getPath = (node) => { if (node.file) { return relative(rootDirectory, node.file); } @@ -75,7 +75,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { runner.on('hook', setStartTime); runner.on('hook end', setEndTime); runner.on('test', setStartTime); - runner.on('pass', node => results.push({ node })); + runner.on('pass', (node) => results.push({ node })); runner.on('pass', setEndTime); runner.on('fail', (node, error) => results.push({ failed: true, error, node })); runner.on('fail', setEndTime); @@ -89,16 +89,16 @@ export function setupJUnitReportGeneration(runner, options = {}) { } // filter out just the failures - const failures = results.filter(result => result.failed); + const failures = results.filter((result) => result.failed); // any failure that isn't for a test is for a hook - const failedHooks = failures.filter(result => !allTests.includes(result.node)); + const failedHooks = failures.filter((result) => !allTests.includes(result.node)); // mocha doesn't emit 'pass' or 'fail' when it skips a test // or a test is pending, so we find them ourselves const skippedResults = allTests - .filter(node => node.pending || !results.find(result => result.node === node)) - .map(node => ({ skipped: true, node })); + .filter((node) => node.pending || !results.find((result) => result.node === node)) + .map((node) => ({ skipped: true, node })); const builder = xmlBuilder.create( 'testsuites', @@ -124,7 +124,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { }); } - [...results, ...skippedResults].forEach(result => { + [...results, ...skippedResults].forEach((result) => { const el = addTestcaseEl(result.node); if (result.failed) { diff --git a/packages/kbn-test/src/mocha/run_mocha_cli.js b/packages/kbn-test/src/mocha/run_mocha_cli.js index 77f40aded1d7f..3c77fef963a76 100644 --- a/packages/kbn-test/src/mocha/run_mocha_cli.js +++ b/packages/kbn-test/src/mocha/run_mocha_cli.js @@ -85,7 +85,7 @@ export function runMochaCli() { ], } ) - .forEach(file => { + .forEach((file) => { process.argv.push(file); }); } diff --git a/packages/kbn-ui-framework/Gruntfile.js b/packages/kbn-ui-framework/Gruntfile.js index cf0c1643055eb..177fd1f153155 100644 --- a/packages/kbn-ui-framework/Gruntfile.js +++ b/packages/kbn-ui-framework/Gruntfile.js @@ -26,7 +26,7 @@ const debounce = require('lodash/function/debounce'); const platform = require('os').platform(); const isPlatformWindows = /^win/.test(platform); -module.exports = function(grunt) { +module.exports = function (grunt) { grunt.initConfig({ clean: { target: ['target'], @@ -62,7 +62,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-copy'); grunt.registerTask('prodBuild', ['clean:target', 'copy:makeProdBuild', 'babel:prodBuild']); - grunt.registerTask('docSiteBuild', function() { + grunt.registerTask('docSiteBuild', function () { const done = this.async(); const serverCmd = { @@ -94,17 +94,17 @@ module.exports = function(grunt) { uiFrameworkServerBuild.then(done); }); - grunt.registerTask('docSiteStart', function() { + grunt.registerTask('docSiteStart', function () { const done = this.async(); Promise.all([uiFrameworkWatch(), uiFrameworkServerStart()]).then(done); }); - grunt.registerTask('compileCssLight', function() { + grunt.registerTask('compileCssLight', function () { const done = this.async(); uiFrameworkCompileLight().then(done); }); - grunt.registerTask('compileCssDark', function() { + grunt.registerTask('compileCssDark', function () { const done = this.async(); uiFrameworkCompileDark().then(done); }); @@ -146,19 +146,19 @@ module.exports = function(grunt) { const src = 'src/kui_light.scss'; const dest = 'dist/kui_light.css'; - return new Promise(resolve => { + return new Promise((resolve) => { sass.render( { file: src, }, - function(error, result) { + function (error, result) { if (error) { grunt.log.error(error); } postcss([postcssConfig]) .process(result.css, { from: src, to: dest }) - .then(result => { + .then((result) => { grunt.file.write(dest, result.css); if (result.map) { @@ -176,19 +176,19 @@ module.exports = function(grunt) { const src = 'src/kui_dark.scss'; const dest = 'dist/kui_dark.css'; - return new Promise(resolve => { + return new Promise((resolve) => { sass.render( { file: src, }, - function(error, result) { + function (error, result) { if (error) { grunt.log.error(error); } postcss([postcssConfig]) .process(result.css, { from: src, to: dest }) - .then(result => { + .then((result) => { grunt.file.write(dest, result.css); if (result.map) { diff --git a/packages/kbn-ui-framework/doc_site/src/actions/example_nav_actions.js b/packages/kbn-ui-framework/doc_site/src/actions/example_nav_actions.js index 205be7920aa60..1836d00af4ab3 100644 --- a/packages/kbn-ui-framework/doc_site/src/actions/example_nav_actions.js +++ b/packages/kbn-ui-framework/doc_site/src/actions/example_nav_actions.js @@ -25,7 +25,7 @@ export const registerSection = (id, name) => ({ name, }); -export const unregisterSection = id => ({ +export const unregisterSection = (id) => ({ type: ActionTypes.UNREGISTER_SECTION, id, }); diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_code/guide_code.js b/packages/kbn-ui-framework/doc_site/src/components/guide_code/guide_code.js index f5845becbe77f..41aeb74d24b6b 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_code/guide_code.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_code/guide_code.js @@ -19,4 +19,4 @@ import React from 'react'; -export const GuideCode = props => {props.children}; +export const GuideCode = (props) => {props.children}; diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_code_viewer/guide_code_viewer.js b/packages/kbn-ui-framework/doc_site/src/components/guide_code_viewer/guide_code_viewer.js index b1d2f8e031f7c..b387a89b7072c 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_code_viewer/guide_code_viewer.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_code_viewer/guide_code_viewer.js @@ -65,7 +65,7 @@ export class GuideCodeViewer extends Component { 'is-code-viewer-open': this.props.isOpen, }); - const codeSections = this.props.source.map(sourceObject => + const codeSections = this.props.source.map((sourceObject) => this.renderSection(sourceObject.type, sourceObject.code) ); diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_demo/guide_demo.js b/packages/kbn-ui-framework/doc_site/src/components/guide_demo/guide_demo.js index 93470ac6de128..d968e014370f8 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_demo/guide_demo.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_demo/guide_demo.js @@ -68,7 +68,7 @@ export class GuideDemo extends Component { }); return ( -

(this.content = c)} {...rest}> +
(this.content = c)} {...rest}> {children}
); diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_link/guide_link.js b/packages/kbn-ui-framework/doc_site/src/components/guide_link/guide_link.js index b49880e0e3bfa..62d1e3ac8bedd 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_link/guide_link.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_link/guide_link.js @@ -19,7 +19,7 @@ import React from 'react'; -export const GuideLink = props => ( +export const GuideLink = (props) => ( {props.children} diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js b/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js index f31a3b10eef4e..49225c96ba5e5 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js @@ -120,7 +120,7 @@ export class GuideNav extends Component { }); const componentNavItems = this.props.components - .filter(item => item.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1) + .filter((item) => item.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1) .map((item, index) => { const icon = item.hasReact ?
: undefined; return ( @@ -135,7 +135,7 @@ export class GuideNav extends Component { }); const sandboxNavItems = this.props.sandboxes - .filter(item => item.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1) + .filter((item) => item.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1) .map((item, index) => { const icon = item.hasReact ?
: undefined; return ( diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_page/guide_page_container.js b/packages/kbn-ui-framework/doc_site/src/components/guide_page/guide_page_container.js index 5d8e6993abe4e..0a7442fce4723 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_page/guide_page_container.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_page/guide_page_container.js @@ -21,7 +21,7 @@ import { connect } from 'react-redux'; import { getSections } from '../../store'; import { GuidePage } from './guide_page'; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ sections: getSections(state), }); diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_page_side_nav/guide_page_side_nav.js b/packages/kbn-ui-framework/doc_site/src/components/guide_page_side_nav/guide_page_side_nav.js index bd7dc0705c6d9..9aeca1b30e03d 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_page_side_nav/guide_page_side_nav.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_page_side_nav/guide_page_side_nav.js @@ -20,7 +20,7 @@ import PropTypes from 'prop-types'; import React from 'react'; -export const GuidePageSideNav = props => { +export const GuidePageSideNav = (props) => { return (
{props.title}
diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_text/guide_text.js b/packages/kbn-ui-framework/doc_site/src/components/guide_text/guide_text.js index 26c68dfe87951..820e4728da86d 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_text/guide_text.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_text/guide_text.js @@ -19,4 +19,4 @@ import React from 'react'; -export const GuideText = props =>
{props.children}
; +export const GuideText = (props) =>
{props.children}
; diff --git a/packages/kbn-ui-framework/doc_site/src/index.js b/packages/kbn-ui-framework/doc_site/src/index.js index 5473024ae93c9..f7f1df059a041 100644 --- a/packages/kbn-ui-framework/doc_site/src/index.js +++ b/packages/kbn-ui-framework/doc_site/src/index.js @@ -58,16 +58,16 @@ const routes = [ ]; // Update document title with route name. -const onRouteEnter = route => { +const onRouteEnter = (route) => { const leafRoute = route.routes[route.routes.length - 1]; document.title = leafRoute.name ? `Kibana UI Framework - ${leafRoute.name}` : 'Kibana UI Framework'; }; -const syncTitleWithRoutes = routesList => { +const syncTitleWithRoutes = (routesList) => { if (!routesList) return; - routesList.forEach(route => { + routesList.forEach((route) => { route.onEnter = onRouteEnter; // eslint-disable-line no-param-reassign if (route.indexRoute) { // Index routes have a weird relationship with their "parent" routes, diff --git a/packages/kbn-ui-framework/doc_site/src/services/routes/routes.js b/packages/kbn-ui-framework/doc_site/src/services/routes/routes.js index 32912d5eb9c86..510a7fea7a026 100644 --- a/packages/kbn-ui-framework/doc_site/src/services/routes/routes.js +++ b/packages/kbn-ui-framework/doc_site/src/services/routes/routes.js @@ -159,14 +159,14 @@ export default { return allRoutes; }, getPreviousRoute: function getPreviousRoute(routeName) { - const index = allRoutes.findIndex(item => { + const index = allRoutes.findIndex((item) => { return item.name === routeName; }); return index >= 0 ? allRoutes[index - 1] : undefined; }, getNextRoute: function getNextRoute(routeName) { - const index = allRoutes.findIndex(item => { + const index = allRoutes.findIndex((item) => { return item.name === routeName; }); diff --git a/packages/kbn-ui-framework/doc_site/src/services/string/slugify.js b/packages/kbn-ui-framework/doc_site/src/services/string/slugify.js index f016857ff4414..2e0678f569774 100644 --- a/packages/kbn-ui-framework/doc_site/src/services/string/slugify.js +++ b/packages/kbn-ui-framework/doc_site/src/services/string/slugify.js @@ -32,7 +32,7 @@ function one(str) { } function each(items, src, dest) { - return items.map(item => { + return items.map((item) => { const _item = item; _item[dest] = one(_item[src]); return _item; diff --git a/packages/kbn-ui-framework/doc_site/src/store/reducers/sections_reducer.js b/packages/kbn-ui-framework/doc_site/src/store/reducers/sections_reducer.js index 6b61c009c5186..a86580903de68 100644 --- a/packages/kbn-ui-framework/doc_site/src/store/reducers/sections_reducer.js +++ b/packages/kbn-ui-framework/doc_site/src/store/reducers/sections_reducer.js @@ -40,7 +40,7 @@ export default function sectionsReducer(state = defaultState, action) { case ActionTypes.UNREGISTER_SECTION: { const sections = state.sections.slice(); - const index = sections.findIndex(section => section.id === action.id); + const index = sections.findIndex((section) => section.id === action.id); sections.splice(index, 1); return { diff --git a/packages/kbn-ui-framework/doc_site/src/views/bar/bar_example.js b/packages/kbn-ui-framework/doc_site/src/views/bar/bar_example.js index a71b2ff28ee65..9f2f1dec56055 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/bar/bar_example.js +++ b/packages/kbn-ui-framework/doc_site/src/views/bar/bar_example.js @@ -36,7 +36,7 @@ import BarThreeSections from './bar_three_sections'; import barThreeSectionsSource from '!!raw-loader!./bar_three_sections'; const barThreeSectionsHtml = renderToHtml(BarThreeSections); -export default props => ( +export default (props) => ( (
{ + onSubmit={(e) => { e.preventDefault(); window.alert('Submit'); }} @@ -40,7 +40,7 @@ export default () => (
{ + onSubmit={(e) => { e.preventDefault(); window.alert('Submit'); }} diff --git a/packages/kbn-ui-framework/doc_site/src/views/button/button_example.js b/packages/kbn-ui-framework/doc_site/src/views/button/button_example.js index daea2978aeffb..4943748ab1830 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/button/button_example.js +++ b/packages/kbn-ui-framework/doc_site/src/views/button/button_example.js @@ -71,7 +71,7 @@ const elementsHtml = renderToHtml(Elements); import sizesHtml from './button_sizes.html'; -export default props => ( +export default (props) => ( { + onToggleContent = (ev) => { ev.preventDefault(); - this.setState(state => ({ + this.setState((state) => ({ isExpanded: !state.isExpanded, })); }; diff --git a/packages/kbn-ui-framework/doc_site/src/views/collapse_button/collapse_button_example.js b/packages/kbn-ui-framework/doc_site/src/views/collapse_button/collapse_button_example.js index d008a0d5f23f7..52e8a91b17aa9 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/collapse_button/collapse_button_example.js +++ b/packages/kbn-ui-framework/doc_site/src/views/collapse_button/collapse_button_example.js @@ -32,7 +32,7 @@ import CollapseButtonAria from './collapse_button_aria'; import collapseButtonAriaSource from '!!raw-loader!./collapse_button_aria'; const collapseButtonAriaHtml = renderToHtml(CollapseButtonAria); -export default props => ( +export default (props) => ( ( +export default (props) => ( this.handleChange(event, 'value1')} + onChange={(event) => this.handleChange(event, 'value1')} />
this.handleChange(event, 'value2')} + onChange={(event) => this.handleChange(event, 'value2')} />
this.handleChange(event, 'value3')} + onChange={(event) => this.handleChange(event, 'value3')} isDisabled />
this.handleChange(event, 'value4')} + onChange={(event) => this.handleChange(event, 'value4')} />
); diff --git a/packages/kbn-ui-framework/doc_site/src/views/form/form_example.js b/packages/kbn-ui-framework/doc_site/src/views/form/form_example.js index 0717459151584..88edfc5242564 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/form/form_example.js +++ b/packages/kbn-ui-framework/doc_site/src/views/form/form_example.js @@ -59,7 +59,7 @@ import CheckBox from './check_box'; import checkBoxSource from '!!raw-loader!./check_box'; const checkBoxHtml = renderToHtml(CheckBox); -export default props => ( +export default (props) => ( - this.handleChange(event, 'value1')}> + this.handleChange(event, 'value1')} + > @@ -44,7 +47,7 @@ class KuiSelectExample extends Component {
this.handleChange(event, 'value2')} + onChange={(event) => this.handleChange(event, 'value2')} isDisabled > @@ -52,7 +55,7 @@ class KuiSelectExample extends Component {
this.handleChange(event, 'value3')} + onChange={(event) => this.handleChange(event, 'value3')} isInvalid > @@ -60,7 +63,7 @@ class KuiSelectExample extends Component {
this.handleChange(event, 'value4')} + onChange={(event) => this.handleChange(event, 'value4')} size="small" > @@ -68,7 +71,7 @@ class KuiSelectExample extends Component {
this.handleChange(event, 'value5')} + onChange={(event) => this.handleChange(event, 'value5')} size="large" > diff --git a/packages/kbn-ui-framework/doc_site/src/views/form/text_area.js b/packages/kbn-ui-framework/doc_site/src/views/form/text_area.js index b56051071a6da..0d4e876d996a2 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/form/text_area.js +++ b/packages/kbn-ui-framework/doc_site/src/views/form/text_area.js @@ -40,38 +40,38 @@ class KuiTextAreaExample extends Component { this.handleChange(event, 'value1')} + onChange={(event) => this.handleChange(event, 'value1')} />
this.handleChange(event, 'value2')} + onChange={(event) => this.handleChange(event, 'value2')} />
this.handleChange(event, 'value3')} + onChange={(event) => this.handleChange(event, 'value3')} />
this.handleChange(event, 'value4')} + onChange={(event) => this.handleChange(event, 'value4')} />
this.handleChange(event, 'value5')} + onChange={(event) => this.handleChange(event, 'value5')} />
this.handleChange(event, 'value6')} + onChange={(event) => this.handleChange(event, 'value6')} />
); diff --git a/packages/kbn-ui-framework/doc_site/src/views/form/text_area_non_resizable.js b/packages/kbn-ui-framework/doc_site/src/views/form/text_area_non_resizable.js index aba16bae1269b..65c7fa765a359 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/form/text_area_non_resizable.js +++ b/packages/kbn-ui-framework/doc_site/src/views/form/text_area_non_resizable.js @@ -33,7 +33,7 @@ class KuiTextAreaNonResizableExample extends Component { return ( this.handleChange(event, 'value1')} + onChange={(event) => this.handleChange(event, 'value1')} isNonResizable /> ); diff --git a/packages/kbn-ui-framework/doc_site/src/views/form/text_input.js b/packages/kbn-ui-framework/doc_site/src/views/form/text_input.js index a3cebcb07bf2b..5bb3fabe22fa5 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/form/text_input.js +++ b/packages/kbn-ui-framework/doc_site/src/views/form/text_input.js @@ -40,39 +40,39 @@ class KuiTextInputExample extends Component { this.handleChange(event, 'value1')} + onChange={(event) => this.handleChange(event, 'value1')} />
this.handleChange(event, 'value2')} + onChange={(event) => this.handleChange(event, 'value2')} />
this.handleChange(event, 'value3')} + onChange={(event) => this.handleChange(event, 'value3')} />
this.handleChange(event, 'value4')} + onChange={(event) => this.handleChange(event, 'value4')} />
this.handleChange(event, 'value5')} + onChange={(event) => this.handleChange(event, 'value5')} />
this.handleChange(event, 'value6')} + onChange={(event) => this.handleChange(event, 'value6')} />
); diff --git a/packages/kbn-ui-framework/doc_site/src/views/form_layout/form_layout_example.js b/packages/kbn-ui-framework/doc_site/src/views/form_layout/form_layout_example.js index 9c76a8b3a1d3a..7b1a5d2785cac 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/form_layout/form_layout_example.js +++ b/packages/kbn-ui-framework/doc_site/src/views/form_layout/form_layout_example.js @@ -28,7 +28,7 @@ import FieldGroup from './field_group'; import fieldGroupSource from '!!raw-loader!./field_group'; const fieldGroupHtml = renderToHtml(FieldGroup); -export default props => ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( item.title.toLowerCase(), + getValue: (item) => item.title.toLowerCase(), isAscending: true, }, { name: 'description', - getValue: item => item.description.toLowerCase(), + getValue: (item) => item.description.toLowerCase(), isAscending: true, }, ], @@ -70,7 +70,7 @@ export class FluidTable extends Component { ); } - onSort = prop => { + onSort = (prop) => { this.sortableProperties.sortOn(prop); this.setState({ @@ -79,7 +79,7 @@ export class FluidTable extends Component { }; renderRows() { - return this.items.map(item => ( + return this.items.map((item) => ( {item.title} diff --git a/packages/kbn-ui-framework/doc_site/src/views/table/listing_table.js b/packages/kbn-ui-framework/doc_site/src/views/table/listing_table.js index fece33c16980a..38eaa7b396ef5 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/table/listing_table.js +++ b/packages/kbn-ui-framework/doc_site/src/views/table/listing_table.js @@ -137,7 +137,7 @@ export class ListingTable extends Component { ]; } - onItemSelectionChanged = selectedRowIds => { + onItemSelectionChanged = (selectedRowIds) => { this.setState({ selectedRowIds }); }; diff --git a/packages/kbn-ui-framework/doc_site/src/views/table/table.js b/packages/kbn-ui-framework/doc_site/src/views/table/table.js index 45f6e389e7234..0c55d1dc5ed51 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/table/table.js +++ b/packages/kbn-ui-framework/doc_site/src/views/table/table.js @@ -85,12 +85,12 @@ export class Table extends Component { [ { name: 'title', - getValue: item => item.title.toLowerCase(), + getValue: (item) => item.title.toLowerCase(), isAscending: true, }, { name: 'status', - getValue: item => item.status.toLowerCase(), + getValue: (item) => item.status.toLowerCase(), isAscending: true, }, ], @@ -98,7 +98,7 @@ export class Table extends Component { ); } - onSort = prop => { + onSort = (prop) => { this.sortableProperties.sortOn(prop); this.setState({ @@ -106,35 +106,35 @@ export class Table extends Component { }); }; - toggleItem = item => { - this.setState(previousState => { + toggleItem = (item) => { + this.setState((previousState) => { const rowToSelectedStateMap = new Map(previousState.rowToSelectedStateMap); rowToSelectedStateMap.set(item, !rowToSelectedStateMap.get(item)); return { rowToSelectedStateMap }; }); }; - isItemChecked = item => { + isItemChecked = (item) => { return this.state.rowToSelectedStateMap.get(item); }; - togglePopover = item => { - this.setState(previousState => { + togglePopover = (item) => { + this.setState((previousState) => { const rowToOpenActionsPopoverMap = new Map(previousState.rowToOpenActionsPopoverMap); rowToOpenActionsPopoverMap.set(item, !rowToOpenActionsPopoverMap.get(item)); return { rowToOpenActionsPopoverMap }; }); }; - closePopover = item => { - this.setState(previousState => { + closePopover = (item) => { + this.setState((previousState) => { const rowToOpenActionsPopoverMap = new Map(previousState.rowToOpenActionsPopoverMap); rowToOpenActionsPopoverMap.set(item, false); return { rowToOpenActionsPopoverMap }; }); }; - isPopoverOpen = item => { + isPopoverOpen = (item) => { return this.state.rowToOpenActionsPopoverMap.get(item); }; @@ -146,7 +146,7 @@ export class Table extends Component { renderRows() { const rows = []; - this.items.forEach(item => { + this.items.forEach((item) => { const classes = classNames({ 'kuiTableRowCell--wrap': item.isWrapped, }); diff --git a/packages/kbn-ui-framework/doc_site/src/views/table/table_example.js b/packages/kbn-ui-framework/doc_site/src/views/table/table_example.js index 9ed449ea9767b..07e328c4e5e83 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/table/table_example.js +++ b/packages/kbn-ui-framework/doc_site/src/views/table/table_example.js @@ -52,7 +52,7 @@ import { ListingTableLoadingItems } from './listing_table_loading_items'; import listingTableLoadingItemsSource from '!!raw-loader!./listing_table_loading_items'; // eslint-disable-line import/default const listingTableLoadingItemsHtml = renderToHtml(ListingTableLoadingItems); -export default props => ( +export default (props) => ( { + onSelectedTabChanged = (id) => { this.setState({ selectedTabId: id, }); diff --git a/packages/kbn-ui-framework/doc_site/src/views/tabs/tabs_example.js b/packages/kbn-ui-framework/doc_site/src/views/tabs/tabs_example.js index 0d3663167520e..125fd0fb53ae3 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/tabs/tabs_example.js +++ b/packages/kbn-ui-framework/doc_site/src/views/tabs/tabs_example.js @@ -35,7 +35,7 @@ import Tabs from './tabs'; import tabsSource from '!!raw-loader!./tabs'; const tabsHtml = renderToHtml(Tabs); -export default props => ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( ( +export default (props) => ( diff --git a/packages/kbn-ui-framework/generator-kui/app/component.js b/packages/kbn-ui-framework/generator-kui/app/component.js index 1cf03b89d54f7..bcb561f6fa729 100644 --- a/packages/kbn-ui-framework/generator-kui/app/component.js +++ b/packages/kbn-ui-framework/generator-kui/app/component.js @@ -39,7 +39,7 @@ module.exports = class extends Generator { }, ], }, - ]).then(answers => { + ]).then((answers) => { this.config = answers; }); } diff --git a/packages/kbn-ui-framework/generator-kui/app/documentation.js b/packages/kbn-ui-framework/generator-kui/app/documentation.js index c2735f1ce978b..3cbc0263789c6 100644 --- a/packages/kbn-ui-framework/generator-kui/app/documentation.js +++ b/packages/kbn-ui-framework/generator-kui/app/documentation.js @@ -43,7 +43,7 @@ module.exports = class extends Generator { }, ], }, - ]).then(answers => { + ]).then((answers) => { this.config = answers; }); } diff --git a/packages/kbn-ui-framework/generator-kui/component/index.js b/packages/kbn-ui-framework/generator-kui/component/index.js index 3abf84b7a2073..56c49fe6fa471 100644 --- a/packages/kbn-ui-framework/generator-kui/component/index.js +++ b/packages/kbn-ui-framework/generator-kui/component/index.js @@ -49,7 +49,7 @@ module.exports = class extends Generator { type: 'confirm', default: true, }, - ]).then(answers => { + ]).then((answers) => { this.config = answers; if (!answers.name || !answers.name.trim()) { @@ -62,7 +62,7 @@ module.exports = class extends Generator { writing() { const config = this.config; - const writeComponent = isStatelessFunction => { + const writeComponent = (isStatelessFunction) => { const componentName = utils.makeComponentName(config.name); const cssClassName = utils.lowerCaseFirstLetter(componentName); const fileName = config.name; diff --git a/packages/kbn-ui-framework/generator-kui/documentation/index.js b/packages/kbn-ui-framework/generator-kui/documentation/index.js index daaaf32c8fd22..03f8d5813b251 100644 --- a/packages/kbn-ui-framework/generator-kui/documentation/index.js +++ b/packages/kbn-ui-framework/generator-kui/documentation/index.js @@ -47,7 +47,7 @@ module.exports = class extends Generator { name: 'folderName', type: 'input', store: true, - default: answers => answers.name, + default: (answers) => answers.name, }); prompts.push({ @@ -58,7 +58,7 @@ module.exports = class extends Generator { }); } - return this.prompt(prompts).then(answers => { + return this.prompt(prompts).then((answers) => { this.config = answers; }); } diff --git a/packages/kbn-ui-framework/generator-kui/utils.js b/packages/kbn-ui-framework/generator-kui/utils.js index c51393121777e..0f7b910451767 100644 --- a/packages/kbn-ui-framework/generator-kui/utils.js +++ b/packages/kbn-ui-framework/generator-kui/utils.js @@ -21,7 +21,7 @@ function makeComponentName(str, usePrefix = true) { const words = str.split('_'); const componentName = words - .map(function(word) { + .map(function (word) { return upperCaseFirstLetter(word); }) .join(''); @@ -30,13 +30,13 @@ function makeComponentName(str, usePrefix = true) { } function lowerCaseFirstLetter(str) { - return str.replace(/\w\S*/g, function(txt) { + return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toLowerCase() + txt.substr(1); }); } function upperCaseFirstLetter(str) { - return str.replace(/\w\S*/g, function(txt) { + return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1); }); } diff --git a/packages/kbn-ui-framework/src/components/button/button.js b/packages/kbn-ui-framework/src/components/button/button.js index b18ca87a27b1e..e95b9209343ae 100644 --- a/packages/kbn-ui-framework/src/components/button/button.js +++ b/packages/kbn-ui-framework/src/components/button/button.js @@ -133,7 +133,7 @@ const KuiLinkButton = ({ children, ...rest }) => { - const onClick = e => { + const onClick = (e) => { if (disabled) { e.preventDefault(); } diff --git a/packages/kbn-ui-framework/src/components/button/button.test.js b/packages/kbn-ui-framework/src/components/button/button.test.js index d664ce85f9a37..db74308d9de8d 100644 --- a/packages/kbn-ui-framework/src/components/button/button.test.js +++ b/packages/kbn-ui-framework/src/components/button/button.test.js @@ -48,7 +48,7 @@ describe('KuiButton', () => { describe('Props', () => { describe('buttonType', () => { - BUTTON_TYPES.forEach(buttonType => { + BUTTON_TYPES.forEach((buttonType) => { describe(`${buttonType}`, () => { test(`renders the ${buttonType} class`, () => { const $button = render(); diff --git a/packages/kbn-ui-framework/src/components/button/button_group/button_group.js b/packages/kbn-ui-framework/src/components/button/button_group/button_group.js index 49eaff03efd40..630d5598cab9c 100644 --- a/packages/kbn-ui-framework/src/components/button/button_group/button_group.js +++ b/packages/kbn-ui-framework/src/components/button/button_group/button_group.js @@ -22,7 +22,7 @@ import React from 'react'; import classNames from 'classnames'; -const KuiButtonGroup = props => { +const KuiButtonGroup = (props) => { const classes = classNames('kuiButtonGroup', { 'kuiButtonGroup--united': props.isUnited, }); diff --git a/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.js b/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.js index 736e349396b7e..256d7c4a1786e 100644 --- a/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.js +++ b/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.js @@ -24,7 +24,7 @@ import classNames from 'classnames'; const ICON_TYPES = ['create', 'delete', 'previous', 'next', 'loading', 'settings', 'menu']; -const KuiButtonIcon = props => { +const KuiButtonIcon = (props) => { const typeToClassNameMap = { create: 'fa-plus', delete: 'fa-trash', diff --git a/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.test.js b/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.test.js index e270f26644393..553fef1432487 100644 --- a/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.test.js +++ b/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.test.js @@ -33,7 +33,7 @@ describe('KuiButtonIcon', () => { describe('Props', () => { describe('type', () => { - ICON_TYPES.forEach(type => { + ICON_TYPES.forEach((type) => { describe(`${type}`, () => { test(`renders the ${type} class`, () => { const $buttonIcon = render(); diff --git a/packages/kbn-ui-framework/src/components/button/link_button.test.js b/packages/kbn-ui-framework/src/components/button/link_button.test.js index 4f77af3febf54..4489dc1a46d2a 100644 --- a/packages/kbn-ui-framework/src/components/button/link_button.test.js +++ b/packages/kbn-ui-framework/src/components/button/link_button.test.js @@ -49,7 +49,7 @@ describe('KuiLinkButton', () => { describe('Props', () => { describe('buttonType', () => { - BUTTON_TYPES.forEach(buttonType => { + BUTTON_TYPES.forEach((buttonType) => { describe(`${buttonType}`, () => { test(`renders the ${buttonType} class`, () => { const $button = render( diff --git a/packages/kbn-ui-framework/src/components/button/submit_button.test.js b/packages/kbn-ui-framework/src/components/button/submit_button.test.js index fc30523649c12..77ad9eb40c55f 100644 --- a/packages/kbn-ui-framework/src/components/button/submit_button.test.js +++ b/packages/kbn-ui-framework/src/components/button/submit_button.test.js @@ -47,7 +47,7 @@ describe('KuiSubmitButton', () => { describe('Props', () => { describe('buttonType', () => { - BUTTON_TYPES.forEach(buttonType => { + BUTTON_TYPES.forEach((buttonType) => { describe(`${buttonType}`, () => { test(`renders the ${buttonType} class`, () => { const $button = render(); diff --git a/packages/kbn-ui-framework/src/components/collapse_button/collapse_button.test.js b/packages/kbn-ui-framework/src/components/collapse_button/collapse_button.test.js index f6245ef654bb9..8ce225038b8b7 100644 --- a/packages/kbn-ui-framework/src/components/collapse_button/collapse_button.test.js +++ b/packages/kbn-ui-framework/src/components/collapse_button/collapse_button.test.js @@ -27,7 +27,7 @@ import { DIRECTIONS, KuiCollapseButton } from './collapse_button'; describe('KuiCollapseButton', () => { describe('Props', () => { describe('direction', () => { - DIRECTIONS.forEach(direction => { + DIRECTIONS.forEach((direction) => { describe(`${direction}`, () => { test(`renders the ${direction} class`, () => { const component = ; diff --git a/packages/kbn-ui-framework/src/components/form/select/select.test.js b/packages/kbn-ui-framework/src/components/form/select/select.test.js index 81c1e4d9a0ff2..056bc88016d51 100644 --- a/packages/kbn-ui-framework/src/components/form/select/select.test.js +++ b/packages/kbn-ui-framework/src/components/form/select/select.test.js @@ -77,7 +77,7 @@ describe('KuiSelect', () => { }); describe('size', () => { - SELECT_SIZE.forEach(size => { + SELECT_SIZE.forEach((size) => { test(`renders ${size}`, () => { const component = {}} />; diff --git a/packages/kbn-ui-framework/src/components/form/text_area/text_area.test.js b/packages/kbn-ui-framework/src/components/form/text_area/text_area.test.js index eddb655094088..d87b7b76789de 100644 --- a/packages/kbn-ui-framework/src/components/form/text_area/text_area.test.js +++ b/packages/kbn-ui-framework/src/components/form/text_area/text_area.test.js @@ -87,7 +87,7 @@ describe('KuiTextArea', () => { }); describe('size', () => { - TEXTAREA_SIZE.forEach(size => { + TEXTAREA_SIZE.forEach((size) => { test(`renders ${size}`, () => { const component = {}} />; diff --git a/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js b/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js index 9ef3c420bba68..41d3726582fb5 100644 --- a/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js +++ b/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js @@ -89,7 +89,7 @@ describe('KuiTextInput', () => { }); describe('size', () => { - TEXTINPUT_SIZE.forEach(size => { + TEXTINPUT_SIZE.forEach((size) => { test(`renders ${size}`, () => { const component = {}} />; diff --git a/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.js b/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.js index 3478c159cbbb9..82b2afba7bc40 100644 --- a/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.js +++ b/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.js @@ -57,7 +57,7 @@ export function KuiListingTable({ if (areAllRowsSelected()) { onItemSelectionChanged([]); } else { - onItemSelectionChanged(rows.map(row => row.id)); + onItemSelectionChanged(rows.map((row) => row.id)); } } diff --git a/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.test.js b/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.test.js index b47a1275d1565..2607caeba0dbb 100644 --- a/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.test.js +++ b/packages/kbn-ui-framework/src/components/table/listing_table/listing_table.test.js @@ -22,7 +22,7 @@ import { mount, shallow } from 'enzyme'; import { requiredProps, takeMountedSnapshot } from '../../../test'; import { KuiListingTable } from './listing_table'; -const getProps = customProps => { +const getProps = (customProps) => { const defaultProps = { header: ['Breed', 'Description'], rows: [ @@ -59,20 +59,14 @@ test('renders KuiListingTable', () => { test('selecting a row calls onItemSelectionChanged', () => { const props = getProps(); const component = shallow(); - component - .find('KuiListingTableRow') - .at(1) - .prop('onSelectionChanged')('1'); + component.find('KuiListingTableRow').at(1).prop('onSelectionChanged')('1'); expect(props.onItemSelectionChanged).toHaveBeenCalledWith(['1']); }); test('selectedRowIds is preserved when onItemSelectionChanged is called', () => { const props = getProps({ selectedRowIds: ['3'] }); const component = shallow(); - component - .find('KuiListingTableRow') - .at(0) - .prop('onSelectionChanged')('1'); + component.find('KuiListingTableRow').at(0).prop('onSelectionChanged')('1'); expect(props.onItemSelectionChanged).toHaveBeenCalledWith(expect.arrayContaining(['1', '3'])); }); diff --git a/packages/kbn-ui-framework/src/components/typography/typography.test.js b/packages/kbn-ui-framework/src/components/typography/typography.test.js index 7155483354e48..b341a2c442fb2 100644 --- a/packages/kbn-ui-framework/src/components/typography/typography.test.js +++ b/packages/kbn-ui-framework/src/components/typography/typography.test.js @@ -35,7 +35,7 @@ describe('KuiTitle', () => { }); describe('renders size', () => { - SIZES.forEach(size => { + SIZES.forEach((size) => { test(size, () => { const component = render( diff --git a/packages/kbn-ui-framework/src/services/accessibility/html_id_generator.js b/packages/kbn-ui-framework/src/services/accessibility/html_id_generator.js index 6537f0d157997..164910341f027 100644 --- a/packages/kbn-ui-framework/src/services/accessibility/html_id_generator.js +++ b/packages/kbn-ui-framework/src/services/accessibility/html_id_generator.js @@ -27,5 +27,5 @@ import uuid from 'uuid'; */ export function htmlIdGenerator(idPrefix) { const prefix = idPrefix || uuid.v1(); - return suffix => `${prefix}_${suffix || uuid.v1()}`; + return (suffix) => `${prefix}_${suffix || uuid.v1()}`; } diff --git a/packages/kbn-ui-framework/src/services/sort/sortable_properties.js b/packages/kbn-ui-framework/src/services/sort/sortable_properties.js index 4b08b20b68cbf..6c445bf8b747b 100644 --- a/packages/kbn-ui-framework/src/services/sort/sortable_properties.js +++ b/packages/kbn-ui-framework/src/services/sort/sortable_properties.js @@ -73,7 +73,7 @@ export class SortableProperties { * @returns {SortableProperty|undefined} */ getSortablePropertyByName(propertyName) { - return this.sortableProperties.find(property => property.name === propertyName); + return this.sortableProperties.find((property) => property.name === propertyName); } /** diff --git a/packages/kbn-ui-framework/src/services/sort/sortable_properties.test.js b/packages/kbn-ui-framework/src/services/sort/sortable_properties.test.js index 223724edac8b8..0037787830ac2 100644 --- a/packages/kbn-ui-framework/src/services/sort/sortable_properties.test.js +++ b/packages/kbn-ui-framework/src/services/sort/sortable_properties.test.js @@ -22,19 +22,19 @@ import { SortableProperties } from './sortable_properties'; describe('SortProperties', () => { const name = { name: 'name', - getValue: bird => bird.name, + getValue: (bird) => bird.name, isAscending: true, }; const size = { name: 'size', - getValue: bird => bird.size, + getValue: (bird) => bird.size, isAscending: false, }; const color = { name: 'color', - getValue: bird => bird.color, + getValue: (bird) => bird.color, isAscending: true, }; diff --git a/packages/kbn-ui-framework/src/test/take_mounted_snapshot.js b/packages/kbn-ui-framework/src/test/take_mounted_snapshot.js index 4d87930d434b5..d4567ebb800fe 100644 --- a/packages/kbn-ui-framework/src/test/take_mounted_snapshot.js +++ b/packages/kbn-ui-framework/src/test/take_mounted_snapshot.js @@ -23,7 +23,7 @@ * containing both React components and HTML elements. This function removes the React components, * leaving only HTML elements in the snapshot. */ -export const takeMountedSnapshot = mountedComponent => { +export const takeMountedSnapshot = (mountedComponent) => { const html = mountedComponent.html(); const template = document.createElement('template'); template.innerHTML = html; // eslint-disable-line no-unsanitized/property diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 26efd174f4e39..88e84fc87ae53 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -30,8 +30,8 @@ export const KbnI18nReact = require('@kbn/i18n/react'); export const Angular = require('angular'); export const Moment = require('moment'); export const MomentTimezone = require('moment-timezone/moment-timezone'); -export const Monaco = require('./monaco.ts'); -export const MonacoBare = require('monaco-editor/esm/vs/editor/editor.api'); +export const KbnMonaco = require('@kbn/monaco'); +export const MonacoBarePluginApi = require('@kbn/monaco').BarePluginApi; export const React = require('react'); export const ReactDom = require('react-dom'); export const ReactDomServer = require('react-dom/server'); @@ -44,6 +44,7 @@ Moment.tz.load(require('moment-timezone/data/packed/latest.json')); // big deps which are locked to a single version export const Rxjs = require('rxjs'); export const RxjsOperators = require('rxjs/operators'); +export const ElasticNumeral = require('@elastic/numeral'); export const ElasticCharts = require('@elastic/charts'); export const ElasticEui = require('@elastic/eui'); export const ElasticEuiLibServices = require('@elastic/eui/lib/services'); diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 9aec3ab359924..301d176555847 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -42,15 +42,17 @@ exports.externals = { 'react-intl': '__kbnSharedDeps__.ReactIntl', 'react-router': '__kbnSharedDeps__.ReactRouter', 'react-router-dom': '__kbnSharedDeps__.ReactRouterDom', - '@kbn/ui-shared-deps/monaco': '__kbnSharedDeps__.Monaco', + '@kbn/monaco': '__kbnSharedDeps__.KbnMonaco', // this is how plugins/consumers from npm load monaco - 'monaco-editor/esm/vs/editor/editor.api': '__kbnSharedDeps__.MonacoBare', + 'monaco-editor/esm/vs/editor/editor.api': '__kbnSharedDeps__.MonacoBarePluginApi', /** * big deps which are locked to a single version */ rxjs: '__kbnSharedDeps__.Rxjs', 'rxjs/operators': '__kbnSharedDeps__.RxjsOperators', + numeral: '__kbnSharedDeps__.ElasticNumeral', + '@elastic/numeral': '__kbnSharedDeps__.ElasticNumeral', '@elastic/charts': '__kbnSharedDeps__.ElasticCharts', '@elastic/eui': '__kbnSharedDeps__.ElasticEui', '@elastic/eui/lib/services': '__kbnSharedDeps__.ElasticEuiLibServices', diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index d34fe3624ab26..744a656c54a7f 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -11,7 +11,9 @@ "dependencies": { "@elastic/charts": "19.2.0", "@elastic/eui": "23.3.1", + "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", + "@kbn/monaco": "1.0.0", "abortcontroller-polyfill": "^1.4.0", "angular": "^1.7.9", "compression-webpack-plugin": "^3.1.0", @@ -21,7 +23,6 @@ "jquery": "^3.5.0", "moment": "^2.24.0", "moment-timezone": "^0.5.27", - "monaco-editor": "~0.17.0", "react": "^16.12.0", "react-dom": "^16.12.0", "react-intl": "^2.8.0", @@ -35,6 +36,8 @@ "devDependencies": { "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", + "loader-utils": "^1.2.3", + "val-loader": "^1.1.1", "css-loader": "^3.4.2", "del": "^5.1.0", "webpack": "^4.41.5" diff --git a/packages/kbn-ui-shared-deps/public_path_loader.js b/packages/kbn-ui-shared-deps/public_path_loader.js index 6b7a27c9ca52b..cdebdcb4f0422 100644 --- a/packages/kbn-ui-shared-deps/public_path_loader.js +++ b/packages/kbn-ui-shared-deps/public_path_loader.js @@ -17,7 +17,15 @@ * under the License. */ -module.exports = function(source) { +const Qs = require('querystring'); +const { stringifyRequest } = require('loader-utils'); + +const VAL_LOADER = require.resolve('val-loader'); +const MODULE_CREATOR = require.resolve('./public_path_module_creator'); + +module.exports = function (source) { const options = this.query; - return `__webpack_public_path__ = window.__kbnPublicPath__['${options.key}'];${source}`; + const valOpts = Qs.stringify({ key: options.key }); + const req = `${VAL_LOADER}?${valOpts}!${MODULE_CREATOR}`; + return `import ${stringifyRequest(this, req)};${source}`; }; diff --git a/packages/kbn-ui-shared-deps/public_path_module_creator.js b/packages/kbn-ui-shared-deps/public_path_module_creator.js new file mode 100644 index 0000000000000..1cb9989432178 --- /dev/null +++ b/packages/kbn-ui-shared-deps/public_path_module_creator.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = function ({ key }) { + return { + code: `__webpack_public_path__ = window.__kbnPublicPath__['${key}']`, + }; +}; diff --git a/packages/kbn-ui-shared-deps/scripts/build.js b/packages/kbn-ui-shared-deps/scripts/build.js index e45b3dbed1748..af4e3481e624d 100644 --- a/packages/kbn-ui-shared-deps/scripts/build.js +++ b/packages/kbn-ui-shared-deps/scripts/build.js @@ -38,7 +38,7 @@ run( ); /** @param {webpack.Stats} stats */ - const onCompilationComplete = stats => { + const onCompilationComplete = (stats) => { const took = Math.round((stats.endTime - stats.startTime) / 1000); if (!stats.hasErrors() && !stats.hasWarnings()) { @@ -55,7 +55,7 @@ run( }; if (flags.watch) { - compiler.hooks.done.tap('report on stats', stats => { + compiler.hooks.done.tap('report on stats', (stats) => { try { onCompilationComplete(stats); } catch (error) { @@ -72,7 +72,7 @@ run( log.info('Running webpack compilation...'); }); - compiler.watch({}, error => { + compiler.watch({}, (error) => { if (error) { log.error('Fatal webpack error'); log.error(error); diff --git a/packages/kbn-ui-shared-deps/tsconfig.json b/packages/kbn-ui-shared-deps/tsconfig.json index 5d981c73f1d21..5aa0f45e4100d 100644 --- a/packages/kbn-ui-shared-deps/tsconfig.json +++ b/packages/kbn-ui-shared-deps/tsconfig.json @@ -1,7 +1,4 @@ { "extends": "../../tsconfig.json", - "include": [ - "index.d.ts", - "monaco.ts" - ] + "include": ["index.d.ts", "./monaco"] } diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index 02987b8a19201..523927bd64a20 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -55,7 +55,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ path: UiSharedDeps.distDir, filename: '[name].js', sourceMapFilename: '[file].map', - devtoolModuleFilenameTemplate: info => + devtoolModuleFilenameTemplate: (info) => `kbn-ui-shared-deps/${Path.relative(REPO_ROOT, info.absoluteResourcePath)}`, library: '__kbnSharedDeps__', }, @@ -78,17 +78,6 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], }, - { - include: [require.resolve('./monaco.ts')], - use: [ - { - loader: 'babel-loader', - options: { - presets: [require.resolve('@kbn/babel-preset/webpack_preset')], - }, - }, - ], - }, ], }, @@ -96,6 +85,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ alias: { moment: MOMENT_SRC, }, + extensions: ['.js', '.ts'], }, optimization: { @@ -104,7 +94,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ cacheGroups: { 'kbn-ui-shared-deps.@elastic': { name: 'kbn-ui-shared-deps.@elastic', - test: m => m.resource && m.resource.includes('@elastic'), + test: (m) => m.resource && m.resource.includes('@elastic'), chunks: 'all', enforce: true, }, diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 657d9f547de66..9a8a81460f410 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -82,9 +82,9 @@ export type Values = T extends any[] ? T[number] : T extends object ? T[keyof * type. This is necessary in the case of distinguishing one collection from * another. */ -export type UnionToIntersection = (U extends any -? (k: U) => void -: never) extends (k: infer I) => void +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I +) => void ? I : never; diff --git a/renovate.json5 b/renovate.json5 index f5bb39a16fe46..674c4e0df7904 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -25,7 +25,7 @@ 'Team:Operations', 'renovate', 'v8.0.0', - 'v7.7.0', + 'v7.9.0', ], major: { labels: [ @@ -33,7 +33,7 @@ 'Team:Operations', 'renovate', 'v8.0.0', - 'v7.7.0', + 'v7.9.0', 'renovate:major', ], }, @@ -246,7 +246,7 @@ 'Team:Operations', 'renovate', 'v8.0.0', - 'v7.7.0', + 'v7.9.0', ':ml', ], }, @@ -715,14 +715,6 @@ '@types/normalize-path', ], }, - { - groupSlug: 'numeral', - groupName: 'numeral related packages', - packageNames: [ - 'numeral', - '@types/numeral', - ], - }, { groupSlug: 'object-hash', groupName: 'object-hash related packages', diff --git a/rfcs/text/0003_handler_interface.md b/rfcs/text/0003_handler_interface.md index 51e78cf7c9f54..197f2a2012376 100644 --- a/rfcs/text/0003_handler_interface.md +++ b/rfcs/text/0003_handler_interface.md @@ -65,7 +65,7 @@ application.registerApp({ }); // Alerting -alerting.registerType({ +alerts.registerType({ id: 'myAlert', async execute(context, params, state) { const indexPatterns = await context.core.savedObjects.find('indexPattern'); diff --git a/rfcs/text/0011_global_search.md b/rfcs/text/0011_global_search.md index 5ec368a1c2f02..3b2120283d06a 100644 --- a/rfcs/text/0011_global_search.md +++ b/rfcs/text/0011_global_search.md @@ -194,7 +194,7 @@ Notes: ### Plugin API -#### server API +#### Common types ```ts /** @@ -208,6 +208,21 @@ type GlobalSearchResult = Omit & { url: string; }; + +/** + * Response returned from the {@link GlobalSearchServiceStart | global search service}'s `find` API + */ +type GlobalSearchBatchedResults = { + /** + * Results for this batch + */ + results: GlobalSearchResult[]; +}; +``` + +#### server API + +```ts /** * Options for the server-side {@link GlobalSearchServiceStart.find | find API} */ @@ -226,16 +241,6 @@ interface GlobalSearchFindOptions { aborted$?: Observable; } -/** - * Response returned from the server-side {@link GlobalSearchServiceStart | global search service}'s `find` API - */ -type GlobalSearchBatchedResults = { - /** - * Results for this batch - */ - results: GlobalSearchResult[]; -}; - /** @public */ interface GlobalSearchPluginSetup { registerResultProvider(provider: GlobalSearchResultProvider); @@ -265,28 +270,6 @@ interface GlobalSearchFindOptions { aborted$?: Observable; } -/** - * Enhanced {@link GlobalSearchResult | result type} for the client-side, - * to allow navigating to a given result. - */ -interface NavigableGlobalSearchResult extends GlobalSearchResult { - /** - * Navigate to this result's associated url. If the result is on this kibana instance, user will be redirected to it - * in a SPA friendly way using `application.navigateToApp`, else, a full page refresh will be performed. - */ - navigate: () => Promise; -} - -/** - * Response returned from the client-side {@link GlobalSearchServiceStart | global search service}'s `find` API - */ -type GlobalSearchBatchedResults = { - /** - * Results for this batch - */ - results: NavigableGlobalSearchResult[]; -}; - /** @public */ interface GlobalSearchPluginSetup { registerResultProvider(provider: GlobalSearchResultProvider); @@ -304,9 +287,6 @@ Notes: - The `registerResultProvider` setup APIs share the same signature, however the input `GlobalSearchResultProvider` types are different on the client and server. - The `find` start API signature got a `KibanaRequest` for `server`, when this parameter is not present for `public`. -- The `find` API returns a observable of `NavigableGlobalSearchResult` instead of plain `GlobalSearchResult`. This type - is here to enhance results with a `navigate` method to let the `GlobalSearch` plugin handle the navigation logic, which is - non-trivial. See the [Redirecting to a result](#redirecting-to-a-result) section for more info. #### http API @@ -395,14 +375,11 @@ In current specification, the only conversion step is to transform the `result.u #### redirecting to a result -Parsing a relative or absolute result url to perform SPA navigation can be non trivial, and should remains the responsibility -of the GlobalSearch plugin API. - -This is why `NavigableGlobalSearchResult.navigate` has been introduced on the client-side version of the `find` API +Parsing a relative or absolute result url to perform SPA navigation can be non trivial. This is why `ApplicationService.navigateToUrl` has been introduced on the client-side core API -When using `navigate` from a result instance, the following logic will be executed: +When using `navigateToUrl` with the url of a result instance, the following logic will be executed: -If all these criteria are true for `result.url`: +If all these criteria are true for `url`: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space) diff --git a/scripts/es.js b/scripts/es.js index 4f15cc11801e3..93f1d69350bac 100644 --- a/scripts/es.js +++ b/scripts/es.js @@ -32,7 +32,7 @@ kbnEs 'base-path': resolve(__dirname, '../.es'), ssl: false, }) - .catch(function(e) { + .catch(function (e) { console.error(e); process.exitCode = 1; }); diff --git a/scripts/prettier_on_changed.js b/scripts/prettier_on_changed.js deleted file mode 100644 index f9598110f91fd..0000000000000 --- a/scripts/prettier_on_changed.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -require('../src/setup_node_env/babel_register'); -require('../src/dev/run_prettier_on_changed'); diff --git a/scripts/test_hardening.js b/scripts/test_hardening.js index c0a20a9ff6cb4..0bc0d1c045ac0 100644 --- a/scripts/test_hardening.js +++ b/scripts/test_hardening.js @@ -28,10 +28,10 @@ program .description( 'Run the tests in test/harden directory. If no files are provided, all files within the directory will be run.' ) - .action(function(globs) { + .action(function (globs) { if (globs.length === 0) globs.push(path.join('test', 'harden', '*')); - globs.forEach(function(glob) { - syncGlob(glob).forEach(function(filename) { + globs.forEach(function (glob) { + syncGlob(glob).forEach(function (filename) { if (path.basename(filename)[0] === '_') return; console.log(process.argv[0], filename); execFileSync(process.argv[0], [filename], { stdio: 'inherit' }); diff --git a/src/apm.js b/src/apm.js index e3f4d84d9b523..6c10539c6b7d3 100644 --- a/src/apm.js +++ b/src/apm.js @@ -85,7 +85,7 @@ function getConfig(serviceName) { */ const isKibanaDistributable = Boolean(build && build.distributable === true); -module.exports = function(serviceName = name) { +module.exports = function (serviceName = name) { if (process.env.kbnWorkerType === 'optmzr') return; const conf = getConfig(serviceName); diff --git a/src/cli/cli.js b/src/cli/cli.js index b6598520352a6..50a8d4c7f7f01 100644 --- a/src/cli/cli.js +++ b/src/cli/cli.js @@ -40,13 +40,13 @@ serveCommand(program); program .command('help ') .description('Get the help for a specific command') - .action(function(cmdName) { + .action(function (cmdName) { const cmd = _.find(program.commands, { _name: cmdName }); if (!cmd) return program.error(`unknown command ${cmdName}`); cmd.help(); }); -program.command('*', null, { noHelp: true }).action(function(cmd) { +program.command('*', null, { noHelp: true }).action(function (cmd) { program.error(`unknown command ${cmd}`); }); diff --git a/src/cli/cluster/cluster_manager.test.ts b/src/cli/cluster/cluster_manager.test.ts index 707778861fb59..66f68f815edac 100644 --- a/src/cli/cluster/cluster_manager.test.ts +++ b/src/cli/cluster/cluster_manager.test.ts @@ -153,7 +153,7 @@ describe('CLI cluster manager', () => { const events: Array = []; delayUntil().subscribe( () => events.push('next'), - error => events.push(error), + (error) => events.push(error), () => events.push('complete') ); diff --git a/src/cli/cluster/cluster_manager.ts b/src/cli/cluster/cluster_manager.ts index 3b3e4d78320d2..09f9bb2333dbe 100644 --- a/src/cli/cluster/cluster_manager.ts +++ b/src/cli/cluster/cluster_manager.ts @@ -37,7 +37,7 @@ process.env.kbnWorkerType = 'managr'; const firstAllTrue = (...sources: Array>) => Rx.combineLatest(...sources).pipe( - filter(values => values.every(v => v === true)), + filter((values) => values.every((v) => v === true)), take(1), mapTo(undefined) ); @@ -75,7 +75,7 @@ export class ClusterManager { .pipe( map(({ state }) => state.phase === 'success' || state.phase === 'issue'), tap({ - error: error => { + error: (error) => { this.log.bad('New platform optimizer error', error.stack); process.exit(1); }, @@ -139,9 +139,9 @@ export class ClusterManager { .subscribe(this.optimizerReady$); // broker messages between workers - this.workers.forEach(worker => { - worker.on('broadcast', msg => { - this.workers.forEach(to => { + this.workers.forEach((worker) => { + worker.on('broadcast', (msg) => { + this.workers.forEach((to) => { if (to !== worker && to.online) { to.fork!.send(msg); } @@ -156,7 +156,7 @@ export class ClusterManager { this.server.on('reloadLoggingConfigFromServerWorker', () => { process.emit('message' as any, { reloadLoggingConfig: true } as any); - this.workers.forEach(worker => { + this.workers.forEach((worker) => { worker.fork!.send({ reloadLoggingConfig: true }); }); }); @@ -182,7 +182,7 @@ export class ClusterManager { const extraPaths = [...pluginPaths, ...scanDirs]; const pluginInternalDirsIgnore = scanDirs - .map(scanDir => resolve(scanDir, '*')) + .map((scanDir) => resolve(scanDir, '*')) .concat(pluginPaths) .reduce( (acc, path) => @@ -212,10 +212,7 @@ export class ClusterManager { shouldRedirectFromOldBasePath: (path: string) => { // strip `s/{id}` prefix when checking for need to redirect if (path.startsWith('s/')) { - path = path - .split('/') - .slice(2) - .join('/'); + path = path.split('/').slice(2).join('/'); } const isApp = path.startsWith('app/'); @@ -253,7 +250,7 @@ export class ClusterManager { fromRoot('x-pack/legacy/server'), fromRoot('config'), ...extraPaths, - ].map(path => resolve(path)) + ].map((path) => resolve(path)) ) ); @@ -264,8 +261,8 @@ export class ClusterManager { /debug\.log$/, ...pluginInternalDirsIgnore, fromRoot('src/legacy/server/sass/__tmp__'), - fromRoot('x-pack/legacy/plugins/reporting/.chromium'), - fromRoot('x-pack/plugins/siem/cypress'), + fromRoot('x-pack/plugins/reporting/.chromium'), + fromRoot('x-pack/plugins/security_solution/cypress'), fromRoot('x-pack/plugins/apm/e2e'), fromRoot('x-pack/plugins/apm/scripts'), fromRoot('x-pack/plugins/canvas/canvas_plugin_src'), // prevents server from restarting twice for Canvas plugin changes, diff --git a/src/cli/cluster/worker.ts b/src/cli/cluster/worker.ts index c73d3edbf7df7..dc6e6d5676651 100644 --- a/src/cli/cluster/worker.ts +++ b/src/cli/cluster/worker.ts @@ -136,7 +136,7 @@ export class Worker extends EventEmitter { this.processBinder.destroy(); // wait until the cluster reports this fork has exited, then resolve - await new Promise(resolve => this.once('fork:exit', resolve)); + await new Promise((resolve) => this.once('fork:exit', resolve)); } } @@ -179,7 +179,7 @@ export class Worker extends EventEmitter { flushChangeBuffer() { const files = _.unique(this.changes.splice(0)); const prefix = files.length > 1 ? '\n - ' : ''; - return files.reduce(function(list, file) { + return files.reduce(function (list, file) { return `${list || ''}${prefix}"${file}"`; }, ''); } @@ -188,7 +188,7 @@ export class Worker extends EventEmitter { if (this.fork) { // once "exit" event is received with 0 status, start() is called again this.shutdown(); - await new Promise(cb => this.once('online', cb)); + await new Promise((cb) => this.once('online', cb)); return; } @@ -214,6 +214,6 @@ export class Worker extends EventEmitter { this.processBinder.on('exit', () => this.shutdown()); // wait for the fork to report it is online before resolving - await new Promise(cb => this.once('fork:online', cb)); + await new Promise((cb) => this.once('fork:online', cb)); } } diff --git a/src/cli/command.js b/src/cli/command.js index 6f083bb2a1fa2..f4781fcab1e20 100644 --- a/src/cli/command.js +++ b/src/cli/command.js @@ -23,7 +23,7 @@ import Chalk from 'chalk'; import help from './help'; import { Command } from 'commander'; -Command.prototype.error = function(err) { +Command.prototype.error = function (err) { if (err && err.message) err = err.message; console.log( @@ -37,7 +37,7 @@ ${help(this, ' ')} process.exit(64); // eslint-disable-line no-process-exit }; -Command.prototype.defaultHelp = function() { +Command.prototype.defaultHelp = function () { console.log( ` ${help(this, ' ')} @@ -48,7 +48,7 @@ ${help(this, ' ')} process.exit(64); // eslint-disable-line no-process-exit }; -Command.prototype.unknownArgv = function(argv) { +Command.prototype.unknownArgv = function (argv) { if (argv) this.__unknownArgv = argv; return this.__unknownArgv ? this.__unknownArgv.slice(0) : []; }; @@ -57,11 +57,11 @@ Command.prototype.unknownArgv = function(argv) { * setup the command to accept arbitrary configuration via the cli * @return {[type]} [description] */ -Command.prototype.collectUnknownOptions = function() { +Command.prototype.collectUnknownOptions = function () { const title = `Extra ${this._name} options`; this.allowUnknownOption(); - this.getUnknownOptions = function() { + this.getUnknownOptions = function () { const opts = {}; const unknowns = this.unknownArgv(); @@ -95,17 +95,17 @@ Command.prototype.collectUnknownOptions = function() { return this; }; -Command.prototype.parseOptions = _.wrap(Command.prototype.parseOptions, function(parse, argv) { +Command.prototype.parseOptions = _.wrap(Command.prototype.parseOptions, function (parse, argv) { const opts = parse.call(this, argv); this.unknownArgv(opts.unknown); return opts; }); -Command.prototype.action = _.wrap(Command.prototype.action, function(action, fn) { - return action.call(this, function(...args) { +Command.prototype.action = _.wrap(Command.prototype.action, function (action, fn) { + return action.call(this, function (...args) { const ret = fn.apply(this, args); if (ret && typeof ret.then === 'function') { - ret.then(null, function(e) { + ret.then(null, function (e) { console.log('FATAL CLI ERROR', e.stack); process.exit(1); }); diff --git a/src/cli/help.js b/src/cli/help.js index a2dc638d2b6ee..656944d85b254 100644 --- a/src/cli/help.js +++ b/src/cli/help.js @@ -24,7 +24,7 @@ export default function help(command, spaces) { return command.outputHelp(); } - const defCmd = _.find(command.commands, function(cmd) { + const defCmd = _.find(command.commands, function (cmd) { return cmd._name === 'serve'; }); @@ -53,12 +53,12 @@ function indent(str, n) { function commandsSummary(program) { const cmds = _.compact( - program.commands.map(function(cmd) { + program.commands.map(function (cmd) { const name = cmd._name; if (name === '*') return; const opts = cmd.options.length ? ' [options]' : ''; const args = cmd._args - .map(function(arg) { + .map(function (arg) { return humanReadableArgName(arg); }) .join(' '); @@ -67,11 +67,11 @@ function commandsSummary(program) { }) ); - const cmdLColWidth = cmds.reduce(function(width, cmd) { + const cmdLColWidth = cmds.reduce(function (width, cmd) { return Math.max(width, cmd[0].length); }, 0); - return cmds.reduce(function(help, cmd) { + return cmds.reduce(function (help, cmd) { return `${help || ''}${_.padRight(cmd[0], cmdLColWidth)} ${cmd[1] || ''}\n`; }, ''); } diff --git a/src/cli/repl/index.js b/src/cli/repl/index.js index 37a51b4d1ec9e..0b27fafcef84e 100644 --- a/src/cli/repl/index.js +++ b/src/cli/repl/index.js @@ -72,7 +72,7 @@ function prettyPrint(text, o, depth) { // This lets us handle promises more gracefully than the default REPL, // which doesn't show the results. function promiseFriendlyWriter({ displayPrompt, getPrintDepth }) { - return result => promisePrint(result, displayPrompt, getPrintDepth); + return (result) => promisePrint(result, displayPrompt, getPrintDepth); } function promisePrint(result, displayPrompt, getPrintDepth) { @@ -83,8 +83,8 @@ function promisePrint(result, displayPrompt, getPrintDepth) { Promise.resolve() .then(() => console.log('Waiting for promise...')) .then(() => result) - .then(o => prettyPrint('Promise Resolved: \n', o, depth)) - .catch(err => prettyPrint('Promise Rejected: \n', err, depth)) + .then((o) => prettyPrint('Promise Resolved: \n', o, depth)) + .catch((err) => prettyPrint('Promise Rejected: \n', err, depth)) .then(displayPrompt); return ''; } diff --git a/src/cli/repl/repl.test.js b/src/cli/repl/repl.test.js index 9abb43c338290..3a032d415e5f2 100644 --- a/src/cli/repl/repl.test.js +++ b/src/cli/repl/repl.test.js @@ -17,7 +17,7 @@ * under the License. */ -jest.mock('repl', () => ({ start: opts => ({ opts, context: {} }) }), { virtual: true }); +jest.mock('repl', () => ({ start: (opts) => ({ opts, context: {} }) }), { virtual: true }); describe('repl', () => { const originalConsoleLog = console.log; @@ -25,7 +25,7 @@ describe('repl', () => { beforeEach(() => { global.console.log = jest.fn(); - require('repl').start = opts => { + require('repl').start = (opts) => { let resetHandler; const replServer = { opts, @@ -188,7 +188,7 @@ describe('repl', () => { async function waitForPrompt(replServer, fn) { let resolveDone; - const done = new Promise(resolve => (resolveDone = resolve)); + const done = new Promise((resolve) => (resolveDone = resolve)); replServer.displayPrompt = () => { resolveDone(); }; diff --git a/src/cli/serve/integration_tests/invalid_config.test.ts b/src/cli/serve/integration_tests/invalid_config.test.ts index da6684fae8cef..fd6fa1bf192fc 100644 --- a/src/cli/serve/integration_tests/invalid_config.test.ts +++ b/src/cli/serve/integration_tests/invalid_config.test.ts @@ -29,10 +29,10 @@ interface LogEntry { type: string; } -describe('cli invalid config support', function() { +describe('cli invalid config support', function () { it( 'exits with statusCode 64 and logs a single line when config is invalid', - function() { + function () { // Unused keys only throw once LegacyService starts, so disable migrations so that Core // will finish the start lifecycle without a running Elasticsearch instance. const { error, status, stdout } = spawnSync( @@ -47,9 +47,9 @@ describe('cli invalid config support', function() { .toString('utf8') .split('\n') .filter(Boolean) - .map(line => JSON.parse(line) as LogEntry) - .filter(line => line.tags.includes('fatal')) - .map(obj => ({ + .map((line) => JSON.parse(line) as LogEntry) + .filter((line) => line.tags.includes('fatal')) + .map((obj) => ({ ...obj, pid: '## PID ##', '@timestamp': '## @timestamp ##', diff --git a/src/cli/serve/integration_tests/reload_logging_config.test.ts b/src/cli/serve/integration_tests/reload_logging_config.test.ts index 9ad8438c312a1..35391b9b58ecc 100644 --- a/src/cli/serve/integration_tests/reload_logging_config.test.ts +++ b/src/cli/serve/integration_tests/reload_logging_config.test.ts @@ -70,7 +70,7 @@ function watchFileUntil(path: string, matcher: RegExp, timeout: number) { } function containsJsonOnly(content: string[]) { - return content.every(line => line.startsWith('{')); + return content.every((line) => line.startsWith('{')); } function createConfigManager(configPath: string) { @@ -83,7 +83,7 @@ function createConfigManager(configPath: string) { }; } -describe('Server logging configuration', function() { +describe('Server logging configuration', function () { let child: undefined | Child.ChildProcess; beforeEach(() => { @@ -92,7 +92,7 @@ describe('Server logging configuration', function() { afterEach(async () => { if (child !== undefined) { - const exitPromise = new Promise(resolve => child?.once('exit', resolve)); + const exitPromise = new Promise((resolve) => child?.once('exit', resolve)); child.kill('SIGKILL'); await exitPromise; } @@ -110,7 +110,7 @@ describe('Server logging configuration', function() { describe('legacy logging', () => { it( 'should be reloadable via SIGHUP process signaling', - async function() { + async function () { const configFilePath = Path.resolve(tempDir, 'kibana.yml'); Fs.copyFileSync(legacyConfig, configFilePath); @@ -123,17 +123,13 @@ describe('Server logging configuration', function() { ]); const message$ = Rx.fromEvent(child.stdout, 'data').pipe( - map(messages => - String(messages) - .split('\n') - .filter(Boolean) - ) + map((messages) => String(messages).split('\n').filter(Boolean)) ); await message$ .pipe( // We know the sighup handler will be registered before this message logged - filter(messages => messages.some(m => m.includes('setting up root'))), + filter((messages) => messages.some((m) => m.includes('setting up root'))), take(1) ) .toPromise(); @@ -141,7 +137,7 @@ describe('Server logging configuration', function() { const lastMessage = await message$.pipe(take(1)).toPromise(); expect(containsJsonOnly(lastMessage)).toBe(true); - createConfigManager(configFilePath).modify(oldConfig => { + createConfigManager(configFilePath).modify((oldConfig) => { oldConfig.logging.json = false; return oldConfig; }); @@ -150,7 +146,7 @@ describe('Server logging configuration', function() { await message$ .pipe( - filter(messages => !containsJsonOnly(messages)), + filter((messages) => !containsJsonOnly(messages)), take(1) ) .toPromise(); @@ -160,7 +156,7 @@ describe('Server logging configuration', function() { it( 'should recreate file handle on SIGHUP', - async function() { + async function () { const logPath = Path.resolve(tempDir, 'kibana.log'); const logPathArchived = Path.resolve(tempDir, 'kibana_archive.log'); @@ -188,24 +184,20 @@ describe('Server logging configuration', function() { describe('platform logging', () => { it( 'should be reloadable via SIGHUP process signaling', - async function() { + async function () { const configFilePath = Path.resolve(tempDir, 'kibana.yml'); Fs.copyFileSync(configFileLogConsole, configFilePath); child = Child.spawn(process.execPath, [kibanaPath, '--oss', '--config', configFilePath]); const message$ = Rx.fromEvent(child.stdout, 'data').pipe( - map(messages => - String(messages) - .split('\n') - .filter(Boolean) - ) + map((messages) => String(messages).split('\n').filter(Boolean)) ); await message$ .pipe( // We know the sighup handler will be registered before this message logged - filter(messages => messages.some(m => m.includes('setting up root'))), + filter((messages) => messages.some((m) => m.includes('setting up root'))), take(1) ) .toPromise(); @@ -213,7 +205,7 @@ describe('Server logging configuration', function() { const lastMessage = await message$.pipe(take(1)).toPromise(); expect(containsJsonOnly(lastMessage)).toBe(true); - createConfigManager(configFilePath).modify(oldConfig => { + createConfigManager(configFilePath).modify((oldConfig) => { oldConfig.logging.appenders.console.layout.kind = 'pattern'; return oldConfig; }); @@ -221,7 +213,7 @@ describe('Server logging configuration', function() { await message$ .pipe( - filter(messages => !containsJsonOnly(messages)), + filter((messages) => !containsJsonOnly(messages)), take(1) ) .toPromise(); @@ -230,14 +222,14 @@ describe('Server logging configuration', function() { ); it( 'should recreate file handle on SIGHUP', - async function() { + async function () { const configFilePath = Path.resolve(tempDir, 'kibana.yml'); Fs.copyFileSync(configFileLogFile, configFilePath); const logPath = Path.resolve(tempDir, 'kibana.log'); const logPathArchived = Path.resolve(tempDir, 'kibana_archive.log'); - createConfigManager(configFilePath).modify(oldConfig => { + createConfigManager(configFilePath).modify((oldConfig) => { oldConfig.logging.appenders.file.path = logPath; return oldConfig; }); diff --git a/src/cli/serve/read_keystore.js b/src/cli/serve/read_keystore.js index c17091a11f5c1..cfe02735630f2 100644 --- a/src/cli/serve/read_keystore.js +++ b/src/cli/serve/read_keystore.js @@ -30,7 +30,7 @@ export function readKeystore(dataPath = getDataPath()) { const keys = Object.keys(keystore.data); const data = {}; - keys.forEach(key => { + keys.forEach((key) => { set(data, key, keystore.data[key]); }); diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index ff6c51e215c3c..8bc65f3da7111 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -52,9 +52,9 @@ const CAN_REPL = canRequire(REPL_PATH); const XPACK_DIR = resolve(__dirname, '../../../x-pack'); const XPACK_INSTALLED = canRequire(XPACK_DIR); -const pathCollector = function() { +const pathCollector = function () { const paths = []; - return function(path) { + return function (path) { paths.push(resolve(process.cwd(), path)); return paths; }; @@ -109,7 +109,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { (customElasticsearchHosts.length > 0 && customElasticsearchHosts) || [ 'https://localhost:9200', ] - ).map(hostUrl => { + ).map((hostUrl) => { const parsedUrl = url.parse(hostUrl); if (parsedUrl.hostname !== 'localhost') { throw new Error( @@ -159,7 +159,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { return rawConfig; } -export default function(program) { +export default function (program) { const command = program.command('serve'); command @@ -223,7 +223,7 @@ export default function(program) { .option('--no-dev-config', 'Prevents loading the kibana.dev.yml file in --dev mode'); } - command.action(async function(opts) { + command.action(async function (opts) { if (opts.dev && opts.devConfig !== false) { try { const kbnDevConfig = fromRoot('config/kibana.dev.yml'); @@ -262,7 +262,7 @@ export default function(program) { isClusterModeSupported: CAN_CLUSTER, isReplModeSupported: CAN_REPL, }, - applyConfigOverrides: rawConfig => applyConfigOverrides(rawConfig, opts, unknownOptions), + applyConfigOverrides: (rawConfig) => applyConfigOverrides(rawConfig, opts, unknownOptions), }); }); } diff --git a/src/cli_keystore/add.test.js b/src/cli_keystore/add.test.js index 72f15a5439ef0..320581b470c2b 100644 --- a/src/cli_keystore/add.test.js +++ b/src/cli_keystore/add.test.js @@ -23,14 +23,14 @@ const mockKeystoreData = 'Ry21UcAJki2qFUTj4TYuvhta3LId+RM5UX/dJ2468hQ=='; jest.mock('fs', () => ({ - readFileSync: jest.fn().mockImplementation(path => { + readFileSync: jest.fn().mockImplementation((path) => { if (!path.includes('nonexistent')) { return JSON.stringify(mockKeystoreData); } throw { code: 'ENOENT' }; }), - existsSync: jest.fn().mockImplementation(path => { + existsSync: jest.fn().mockImplementation((path) => { return !path.includes('nonexistent'); }), writeFileSync: jest.fn(), diff --git a/src/cli_keystore/cli_keystore.js b/src/cli_keystore/cli_keystore.js index 7c90d88f7b0cd..e1561b343ef39 100644 --- a/src/cli_keystore/cli_keystore.js +++ b/src/cli_keystore/cli_keystore.js @@ -50,13 +50,13 @@ removeCli(program, keystore); program .command('help ') .description('get the help for a specific command') - .action(function(cmdName) { + .action(function (cmdName) { const cmd = _.find(program.commands, { _name: cmdName }); if (!cmd) return program.error(`unknown command ${cmdName}`); cmd.help(); }); -program.command('*', null, { noHelp: true }).action(function(cmd) { +program.command('*', null, { noHelp: true }).action(function (cmd) { program.error(`unknown command ${cmd}`); }); diff --git a/src/cli_keystore/create.test.js b/src/cli_keystore/create.test.js index 01355f51a0c55..33b5aa4bd07d8 100644 --- a/src/cli_keystore/create.test.js +++ b/src/cli_keystore/create.test.js @@ -23,14 +23,14 @@ const mockKeystoreData = 'Ry21UcAJki2qFUTj4TYuvhta3LId+RM5UX/dJ2468hQ=='; jest.mock('fs', () => ({ - readFileSync: jest.fn().mockImplementation(path => { + readFileSync: jest.fn().mockImplementation((path) => { if (!path.includes('foo')) { return JSON.stringify(mockKeystoreData); } throw { code: 'ENOENT' }; }), - existsSync: jest.fn().mockImplementation(path => { + existsSync: jest.fn().mockImplementation((path) => { return !path.includes('foo'); }), writeFileSync: jest.fn(), diff --git a/src/cli_keystore/list.test.js b/src/cli_keystore/list.test.js index 3fb5014820865..857991b5ae3b9 100644 --- a/src/cli_keystore/list.test.js +++ b/src/cli_keystore/list.test.js @@ -23,14 +23,14 @@ const mockKeystoreData = 'Ry21UcAJki2qFUTj4TYuvhta3LId+RM5UX/dJ2468hQ=='; jest.mock('fs', () => ({ - readFileSync: jest.fn().mockImplementation(path => { + readFileSync: jest.fn().mockImplementation((path) => { if (!path.includes('nonexistent')) { return JSON.stringify(mockKeystoreData); } throw { code: 'ENOENT' }; }), - existsSync: jest.fn().mockImplementation(path => { + existsSync: jest.fn().mockImplementation((path) => { return !path.includes('nonexistent'); }), })); diff --git a/src/cli_plugin/cli.js b/src/cli_plugin/cli.js index d1cdf983c0da4..da1068b54b4b5 100644 --- a/src/cli_plugin/cli.js +++ b/src/cli_plugin/cli.js @@ -43,13 +43,13 @@ removeCommand(program); program .command('help ') .description('get the help for a specific command') - .action(function(cmdName) { + .action(function (cmdName) { const cmd = _.find(program.commands, { _name: cmdName }); if (!cmd) return program.error(`unknown command ${cmdName}`); cmd.help(); }); -program.command('*', null, { noHelp: true }).action(function(cmd) { +program.command('*', null, { noHelp: true }).action(function (cmd) { program.error(`unknown command ${cmd}`); }); diff --git a/src/cli_plugin/install/cleanup.js b/src/cli_plugin/install/cleanup.js index eaa25962ef0e4..f31e028226c27 100644 --- a/src/cli_plugin/install/cleanup.js +++ b/src/cli_plugin/install/cleanup.js @@ -21,7 +21,7 @@ import del from 'del'; import fs from 'fs'; export function cleanPrevious(settings, logger) { - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { try { fs.statSync(settings.workingPath); diff --git a/src/cli_plugin/install/cleanup.test.js b/src/cli_plugin/install/cleanup.test.js index c6602636cb481..46089f61d5e83 100644 --- a/src/cli_plugin/install/cleanup.test.js +++ b/src/cli_plugin/install/cleanup.test.js @@ -24,32 +24,32 @@ import del from 'del'; import { cleanPrevious, cleanArtifacts } from './cleanup'; import Logger from '../lib/logger'; -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('pluginCleaner', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('pluginCleaner', function () { const settings = { workingPath: 'dummy', }; - describe('cleanPrevious', function() { + describe('cleanPrevious', function () { let errorStub; let logger; - beforeEach(function() { + beforeEach(function () { errorStub = sinon.stub(); logger = new Logger(settings); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); }); - afterEach(function() { + afterEach(function () { logger.log.restore(); logger.error.restore(); fs.statSync.restore(); del.sync.restore(); }); - it('should resolve if the working path does not exist', function() { + it('should resolve if the working path does not exist', function () { sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync').callsFake(() => { const error = new Error('ENOENT'); @@ -59,75 +59,75 @@ describe('kibana cli', function() { return cleanPrevious(settings, logger) .catch(errorStub) - .then(function() { + .then(function () { expect(errorStub.called).toBe(false); }); }); - it('should rethrow any exception except ENOENT from fs.statSync', function() { + it('should rethrow any exception except ENOENT from fs.statSync', function () { sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync').throws(new Error('An Unhandled Error')); errorStub = sinon.stub(); return cleanPrevious(settings, logger) .catch(errorStub) - .then(function() { + .then(function () { expect(errorStub.called).toBe(true); }); }); - it('should log a message if there was a working directory', function() { + it('should log a message if there was a working directory', function () { sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync'); return cleanPrevious(settings, logger) .catch(errorStub) - .then(function() { + .then(function () { expect(logger.log.calledWith('Found previous install attempt. Deleting...')).toBe( true ); }); }); - it('should rethrow any exception from del.sync', function() { + it('should rethrow any exception from del.sync', function () { sinon.stub(fs, 'statSync'); sinon.stub(del, 'sync').throws(new Error('I am an error thrown by del')); errorStub = sinon.stub(); return cleanPrevious(settings, logger) .catch(errorStub) - .then(function() { + .then(function () { expect(errorStub.called).toBe(true); }); }); - it('should resolve if the working path is deleted', function() { + it('should resolve if the working path is deleted', function () { sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync'); return cleanPrevious(settings, logger) .catch(errorStub) - .then(function() { + .then(function () { expect(errorStub.called).toBe(false); }); }); }); - describe('cleanArtifacts', function() { - beforeEach(function() {}); + describe('cleanArtifacts', function () { + beforeEach(function () {}); - afterEach(function() { + afterEach(function () { del.sync.restore(); }); - it('should attempt to delete the working directory', function() { + it('should attempt to delete the working directory', function () { sinon.stub(del, 'sync'); cleanArtifacts(settings); expect(del.sync.calledWith(settings.workingPath)).toBe(true); }); - it('should swallow any errors thrown by del.sync', function() { + it('should swallow any errors thrown by del.sync', function () { sinon.stub(del, 'sync').throws(new Error('Something bad happened.')); expect(() => cleanArtifacts(settings)).not.toThrow(); diff --git a/src/cli_plugin/install/download.js b/src/cli_plugin/install/download.js index fc1fe8323520b..10d20367c1b7b 100644 --- a/src/cli_plugin/install/download.js +++ b/src/cli_plugin/install/download.js @@ -80,7 +80,7 @@ export function download(settings, logger) { logger.log(`Attempting to transfer from ${sourceUrl}`); - return _downloadSingle(settings, logger, sourceUrl).catch(err => { + return _downloadSingle(settings, logger, sourceUrl).catch((err) => { const isUnsupportedProtocol = err instanceof UnsupportedProtocolError; const isDownloadResourceNotFound = err.message === 'ENOTFOUND'; if (isUnsupportedProtocol || isDownloadResourceNotFound) { diff --git a/src/cli_plugin/install/download.test.js b/src/cli_plugin/install/download.test.js index ef924f28a65e7..93e5e414fed74 100644 --- a/src/cli_plugin/install/download.test.js +++ b/src/cli_plugin/install/download.test.js @@ -28,8 +28,8 @@ import { download, _downloadSingle, _getFilePath, _checkFilePathDeprecation } fr import { join } from 'path'; import http from 'http'; -describe('kibana cli', function() { - describe('plugin downloader', function() { +describe('kibana cli', function () { + describe('plugin downloader', function () { const testWorkingPath = join(__dirname, '.test.data.download'); const tempArchiveFilePath = join(testWorkingPath, 'archive.part'); @@ -57,46 +57,44 @@ describe('kibana cli', function() { throw new Error('expected the promise to reject'); } - beforeEach(function() { + beforeEach(function () { sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); del.sync(testWorkingPath); Fs.mkdirSync(testWorkingPath, { recursive: true }); }); - afterEach(function() { + afterEach(function () { logger.log.restore(); logger.error.restore(); del.sync(testWorkingPath); }); - describe('_downloadSingle', function() { - beforeEach(function() {}); + describe('_downloadSingle', function () { + beforeEach(function () {}); - describe('http downloader', function() { - it('should throw an ENOTFOUND error for a http ulr that returns 404', function() { - nock('http://example.com') - .get('/plugin.tar.gz') - .reply(404); + describe('http downloader', function () { + it('should throw an ENOTFOUND error for a http ulr that returns 404', function () { + nock('http://example.com').get('/plugin.tar.gz').reply(404); const sourceUrl = 'http://example.com/plugin.tar.gz'; - return _downloadSingle(settings, logger, sourceUrl).then(shouldReject, function(err) { + return _downloadSingle(settings, logger, sourceUrl).then(shouldReject, function (err) { expect(err.message).toMatch(/ENOTFOUND/); expectWorkingPathEmpty(); }); }); - it('should throw an UnsupportedProtocolError for an invalid url', function() { + it('should throw an UnsupportedProtocolError for an invalid url', function () { const sourceUrl = 'i am an invalid url'; - return _downloadSingle(settings, logger, sourceUrl).then(shouldReject, function(err) { + return _downloadSingle(settings, logger, sourceUrl).then(shouldReject, function (err) { expect(err).toBeInstanceOf(UnsupportedProtocolError); expectWorkingPathEmpty(); }); }); - it('should download a file from a valid http url', function() { + it('should download a file from a valid http url', function () { const filePath = join(__dirname, '__fixtures__/replies/banana.jpg'); nock('http://example.com') @@ -109,40 +107,40 @@ describe('kibana cli', function() { const sourceUrl = 'http://example.com/plugin.zip'; - return _downloadSingle(settings, logger, sourceUrl).then(function() { + return _downloadSingle(settings, logger, sourceUrl).then(function () { expectWorkingPathNotEmpty(); }); }); }); - describe('local file downloader', function() { - it('should throw an ENOTFOUND error for an invalid local file', function() { + describe('local file downloader', function () { + it('should throw an ENOTFOUND error for an invalid local file', function () { const filePath = join(__dirname, '__fixtures__/replies/i-am-not-there.zip'); const sourceUrl = 'file://' + filePath.replace(/\\/g, '/'); - return _downloadSingle(settings, logger, sourceUrl).then(shouldReject, function(err) { + return _downloadSingle(settings, logger, sourceUrl).then(shouldReject, function (err) { expect(err.message).toMatch(/ENOTFOUND/); expectWorkingPathEmpty(); }); }); - it('should copy a valid local file', function() { + it('should copy a valid local file', function () { const filePath = join(__dirname, '__fixtures__/replies/banana.jpg'); const sourceUrl = 'file://' + filePath.replace(/\\/g, '/'); - return _downloadSingle(settings, logger, sourceUrl).then(function() { + return _downloadSingle(settings, logger, sourceUrl).then(function () { expectWorkingPathNotEmpty(); }); }); }); }); - describe('_getFilePath', function() { - it('should decode paths', function() { + describe('_getFilePath', function () { + it('should decode paths', function () { expect(_getFilePath('Test%20folder/file.zip')).toBe('Test folder/file.zip'); }); - it('should remove the leading slash from windows paths', function() { + it('should remove the leading slash from windows paths', function () { const platform = Object.getOwnPropertyDescriptor(process, 'platform'); Object.defineProperty(process, 'platform', { value: 'win32' }); @@ -152,8 +150,8 @@ describe('kibana cli', function() { }); }); - describe('Windows file:// deprecation', function() { - it('should log a warning if a file:// path is used', function() { + describe('Windows file:// deprecation', function () { + it('should log a warning if a file:// path is used', function () { const platform = Object.getOwnPropertyDescriptor(process, 'platform'); Object.defineProperty(process, 'platform', { value: 'win32' }); const logger = { @@ -169,8 +167,8 @@ describe('kibana cli', function() { }); }); - describe('download', function() { - it('should loop through bad urls until it finds a good one.', function() { + describe('download', function () { + it('should loop through bad urls until it finds a good one.', function () { const filePath = join(__dirname, '__fixtures__/replies/test_plugin.zip'); settings.urls = [ 'http://example.com/badfile1.tar.gz', @@ -190,7 +188,7 @@ describe('kibana cli', function() { .get('/goodfile.tar.gz') .replyWithFile(200, filePath); - return download(settings, logger).then(function() { + return download(settings, logger).then(function () { expect(logger.log.getCall(0).args[0]).toMatch(/badfile1.tar.gz/); expect(logger.log.getCall(1).args[0]).toMatch(/badfile2.tar.gz/); expect(logger.log.getCall(2).args[0]).toMatch(/I am a bad uri/); @@ -199,7 +197,7 @@ describe('kibana cli', function() { }); }); - it('should stop looping through urls when it finds a good one.', function() { + it('should stop looping through urls when it finds a good one.', function () { const filePath = join(__dirname, '__fixtures__/replies/test_plugin.zip'); settings.urls = [ 'http://example.com/badfile1.tar.gz', @@ -221,7 +219,7 @@ describe('kibana cli', function() { .get('/badfile3.tar.gz') .reply(404); - return download(settings, logger).then(function() { + return download(settings, logger).then(function () { for (let i = 0; i < logger.log.callCount; i++) { expect(logger.log.getCall(i).args[0]).not.toMatch(/badfile3.tar.gz/); } @@ -229,7 +227,7 @@ describe('kibana cli', function() { }); }); - it("should throw an error when it doesn't find a good url.", function() { + it("should throw an error when it doesn't find a good url.", function () { settings.urls = [ 'http://example.com/badfile1.tar.gz', 'http://example.com/badfile2.tar.gz', @@ -247,25 +245,25 @@ describe('kibana cli', function() { .get('/badfile3.tar.gz') .reply(404); - return download(settings, logger).then(shouldReject, function(err) { + return download(settings, logger).then(shouldReject, function (err) { expect(err.message).toMatch(/no valid url specified/i); expectWorkingPathEmpty(); }); }); - afterAll(function() { + afterAll(function () { nock.cleanAll(); }); }); - describe('proxy support', function() { + describe('proxy support', function () { const proxyPort = 2626; const proxyUrl = `http://localhost:${proxyPort}`; let proxyHit = false; let proxyConnectHit = false; - const proxy = http.createServer(function(req, res) { + const proxy = http.createServer(function (req, res) { proxyHit = true; // Our test proxy simply returns an empty 200 response, since we only // care about the download promise being resolved. @@ -301,29 +299,29 @@ describe('kibana cli', function() { .replyWithFile(200, join(__dirname, '__fixtures__/replies/test_plugin.zip')); } - beforeAll(function(done) { + beforeAll(function (done) { proxy.listen(proxyPort, done); }); - beforeEach(function() { + beforeEach(function () { proxyHit = false; proxyConnectHit = false; }); - afterEach(function() { + afterEach(function () { delete process.env.http_proxy; delete process.env.https_proxy; delete process.env.no_proxy; }); - it('should use http_proxy env variable', function() { + it('should use http_proxy env variable', function () { process.env.http_proxy = proxyUrl; settings.urls = ['http://example.com/plugin.zip']; return download(settings, logger).then(expectProxyHit); }); - it('should use https_proxy for secure URLs', function() { + it('should use https_proxy for secure URLs', function () { process.env.https_proxy = proxyUrl; settings.urls = ['https://example.com/plugin.zip']; @@ -340,7 +338,7 @@ describe('kibana cli', function() { ); }); - it('should not use http_proxy for HTTPS urls', function() { + it('should not use http_proxy for HTTPS urls', function () { process.env.http_proxy = proxyUrl; settings.urls = ['https://example.com/plugin.zip']; @@ -349,7 +347,7 @@ describe('kibana cli', function() { return download(settings, logger).then(expectNoProxyHit); }); - it('should not use https_proxy for HTTP urls', function() { + it('should not use https_proxy for HTTP urls', function () { process.env.https_proxy = proxyUrl; settings.urls = ['http://example.com/plugin.zip']; @@ -358,7 +356,7 @@ describe('kibana cli', function() { return download(settings, logger).then(expectNoProxyHit); }); - it('should support domains in no_proxy', function() { + it('should support domains in no_proxy', function () { process.env.http_proxy = proxyUrl; process.env.no_proxy = 'foo.bar, example.com'; settings.urls = ['http://example.com/plugin.zip']; @@ -368,7 +366,7 @@ describe('kibana cli', function() { return download(settings, logger).then(expectNoProxyHit); }); - it('should support subdomains in no_proxy', function() { + it('should support subdomains in no_proxy', function () { process.env.http_proxy = proxyUrl; process.env.no_proxy = 'foo.bar,plugins.example.com'; settings.urls = ['http://plugins.example.com/plugin.zip']; @@ -378,7 +376,7 @@ describe('kibana cli', function() { return download(settings, logger).then(expectNoProxyHit); }); - it('should accept wildcard subdomains in no_proxy', function() { + it('should accept wildcard subdomains in no_proxy', function () { process.env.http_proxy = proxyUrl; process.env.no_proxy = 'foo.bar, .example.com'; settings.urls = ['http://plugins.example.com/plugin.zip']; @@ -388,7 +386,7 @@ describe('kibana cli', function() { return download(settings, logger).then(expectNoProxyHit); }); - it('should support asterisk wildcard no_proxy syntax', function() { + it('should support asterisk wildcard no_proxy syntax', function () { process.env.http_proxy = proxyUrl; process.env.no_proxy = '*.example.com'; settings.urls = ['http://plugins.example.com/plugin.zip']; @@ -398,7 +396,7 @@ describe('kibana cli', function() { return download(settings, logger).then(expectNoProxyHit); }); - it('should support implicit ports in no_proxy', function() { + it('should support implicit ports in no_proxy', function () { process.env.https_proxy = proxyUrl; process.env.no_proxy = 'example.com:443'; settings.urls = ['https://example.com/plugin.zip']; @@ -408,7 +406,7 @@ describe('kibana cli', function() { return download(settings, logger).then(expectNoProxyHit); }); - afterAll(function(done) { + afterAll(function (done) { proxy.close(done); }); }); diff --git a/src/cli_plugin/install/downloaders/file.js b/src/cli_plugin/install/downloaders/file.js index eee8ddb21d6dd..56f83b03d5a90 100644 --- a/src/cli_plugin/install/downloaders/file.js +++ b/src/cli_plugin/install/downloaders/file.js @@ -43,7 +43,7 @@ async function copyFile({ readStream, writeStream, progress }) { writeStream.on('error', reject); // report progress as we transfer - readStream.on('data', chunk => { + readStream.on('data', (chunk) => { progress.progress(chunk.length); }); diff --git a/src/cli_plugin/install/downloaders/http.js b/src/cli_plugin/install/downloaders/http.js index 88dcdabe70dfd..0fc01453f2b4c 100644 --- a/src/cli_plugin/install/downloaders/http.js +++ b/src/cli_plugin/install/downloaders/http.js @@ -76,7 +76,7 @@ function downloadResponse({ resp, targetPath, progress }) { writeStream.on('error', reject); // report progress as we download - resp.on('data', chunk => { + resp.on('data', (chunk) => { progress.progress(chunk.length); }); diff --git a/src/cli_plugin/install/index.test.js b/src/cli_plugin/install/index.test.js index 6a64a673bb93e..39352f52f20fd 100644 --- a/src/cli_plugin/install/index.test.js +++ b/src/cli_plugin/install/index.test.js @@ -20,25 +20,25 @@ import sinon from 'sinon'; import index from './index'; -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('commander options', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('commander options', function () { const program = { - command: function() { + command: function () { return program; }, - description: function() { + description: function () { return program; }, - option: function() { + option: function () { return program; }, - action: function() { + action: function () { return program; }, }; - it('should define the command', function() { + it('should define the command', function () { sinon.spy(program, 'command'); index(program); @@ -47,7 +47,7 @@ describe('kibana cli', function() { program.command.restore(); }); - it('should define the description', function() { + it('should define the description', function () { sinon.spy(program, 'description'); index(program); @@ -56,7 +56,7 @@ describe('kibana cli', function() { program.description.restore(); }); - it('should define the command line options', function() { + it('should define the command line options', function () { const spy = sinon.spy(program, 'option'); const options = [/-q/, /-s/, /-c/, /-t/, /-d/]; @@ -77,7 +77,7 @@ describe('kibana cli', function() { expect(options).toHaveLength(0); }); - it('should call the action function', function() { + it('should call the action function', function () { sinon.spy(program, 'action'); index(program); diff --git a/src/cli_plugin/install/kibana.test.js b/src/cli_plugin/install/kibana.test.js index bbf364a755f8a..8c5dd00d09953 100644 --- a/src/cli_plugin/install/kibana.test.js +++ b/src/cli_plugin/install/kibana.test.js @@ -30,9 +30,9 @@ beforeEach(() => { jest.clearAllMocks(); }); -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('kibana', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('kibana', function () { const testWorkingPath = join(__dirname, '.test.data.kibana'); const tempArchiveFilePath = join(testWorkingPath, 'archive.part'); const pluginDir = join(__dirname, 'plugins'); @@ -48,21 +48,21 @@ describe('kibana cli', function() { const logger = new Logger(settings); - describe('assertVersion', function() { - beforeEach(function() { + describe('assertVersion', function () { + beforeEach(function () { del.sync(testWorkingPath); fs.mkdirSync(testWorkingPath, { recursive: true }); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); }); - afterEach(function() { + afterEach(function () { logger.log.restore(); logger.error.restore(); del.sync(testWorkingPath); }); - it('should succeed with exact match', function() { + it('should succeed with exact match', function () { const settings = { workingPath: testWorkingPath, tempArchiveFile: tempArchiveFilePath, @@ -76,60 +76,60 @@ describe('kibana cli', function() { expect(() => assertVersion(settings)).not.toThrow(); }); - it('should throw an error if plugin is missing a kibana version.', function() { + it('should throw an error if plugin is missing a kibana version.', function () { expect(() => assertVersion(settings)).toThrow( /plugin package\.json is missing both a version property/i ); }); - it('should throw an error if plugin kibanaVersion does not match kibana version', function() { + it('should throw an error if plugin kibanaVersion does not match kibana version', function () { settings.plugins[0].kibanaVersion = '1.2.3.4'; expect(() => assertVersion(settings)).toThrow(/incompatible with Kibana/i); }); - it('should not throw an error if plugin kibanaVersion matches kibana version', function() { + it('should not throw an error if plugin kibanaVersion matches kibana version', function () { settings.plugins[0].kibanaVersion = '1.0.0'; expect(() => assertVersion(settings)).not.toThrow(); }); - it('should ignore version info after the dash in checks on valid version', function() { + it('should ignore version info after the dash in checks on valid version', function () { settings.plugins[0].kibanaVersion = '1.0.0-foo-bar-version-1.2.3'; expect(() => assertVersion(settings)).not.toThrow(); }); - it('should ignore version info after the dash in checks on invalid version', function() { + it('should ignore version info after the dash in checks on invalid version', function () { settings.plugins[0].kibanaVersion = '2.0.0-foo-bar-version-1.2.3'; expect(() => assertVersion(settings)).toThrow(/incompatible with Kibana/i); }); }); - describe('existingInstall', function() { + describe('existingInstall', function () { let processExitStub; - beforeEach(function() { + beforeEach(function () { processExitStub = sinon.stub(process, 'exit'); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); }); - afterEach(function() { + afterEach(function () { processExitStub.restore(); logger.log.restore(); logger.error.restore(); }); - it('should throw an error if the plugin already exists.', function() { + it('should throw an error if the plugin already exists.', function () { fs.statSync.mockImplementationOnce(() => true); existingInstall(settings, logger); expect(logger.error.firstCall.args[0]).toMatch(/already exists/); expect(process.exit.called).toBe(true); }); - it('should not throw an error if the plugin does not exist.', function() { + it('should not throw an error if the plugin does not exist.', function () { fs.statSync.mockImplementationOnce(() => { throw { code: 'ENOENT' }; }); diff --git a/src/cli_plugin/install/pack.test.js b/src/cli_plugin/install/pack.test.js index efe310a4fed40..05a60107f80ff 100644 --- a/src/cli_plugin/install/pack.test.js +++ b/src/cli_plugin/install/pack.test.js @@ -27,8 +27,8 @@ import { extract, getPackData } from './pack'; import { _downloadSingle } from './download'; import { join } from 'path'; -describe('kibana cli', function() { - describe('pack', function() { +describe('kibana cli', function () { + describe('pack', function () { let testNum = 0; const workingPathRoot = join(__dirname, '.test.data.pack'); let testWorkingPath; @@ -37,7 +37,7 @@ describe('kibana cli', function() { let logger; let settings; - beforeEach(function() { + beforeEach(function () { //These tests are dependent on the file system, and I had some inconsistent //behavior with del.sync show up. Until these tests are re-written to not //depend on the file system, I make sure that each test uses a different @@ -60,7 +60,7 @@ describe('kibana cli', function() { Fs.mkdirSync(testWorkingPath, { recursive: true }); }); - afterEach(function() { + afterEach(function () { logger.log.restore(); logger.error.restore(); del.sync(workingPathRoot); @@ -77,10 +77,10 @@ describe('kibana cli', function() { throw new Error('expected the promise to reject'); } - describe('extract', function() { + describe('extract', function () { //Also only extracts the content from the kibana folder. //Ignores the others. - it('successfully extract a valid zip', function() { + it('successfully extract a valid zip', function () { return copyReplyFile('test_plugin.zip') .then(() => { return getPackData(settings, logger); @@ -104,8 +104,8 @@ describe('kibana cli', function() { }); }); - describe('getPackData', function() { - it('populate settings.plugins', function() { + describe('getPackData', function () { + it('populate settings.plugins', function () { return copyReplyFile('test_plugin.zip') .then(() => { return getPackData(settings, logger); @@ -118,7 +118,7 @@ describe('kibana cli', function() { }); }); - it('populate settings.plugin.kibanaVersion', function() { + it('populate settings.plugin.kibanaVersion', function () { //kibana.version is defined in this package.json and is different than plugin version return copyReplyFile('test_plugin_different_version.zip') .then(() => { @@ -129,7 +129,7 @@ describe('kibana cli', function() { }); }); - it('populate settings.plugin.kibanaVersion (default to plugin version)', function() { + it('populate settings.plugin.kibanaVersion (default to plugin version)', function () { //kibana.version is not defined in this package.json, defaults to plugin version return copyReplyFile('test_plugin.zip') .then(() => { @@ -140,7 +140,7 @@ describe('kibana cli', function() { }); }); - it('populate settings.plugins with multiple plugins', function() { + it('populate settings.plugins with multiple plugins', function () { return copyReplyFile('test_plugin_many.zip') .then(() => { return getPackData(settings, logger); @@ -172,32 +172,32 @@ describe('kibana cli', function() { }); }); - it('throw an error if there is no kibana plugin', function() { + it('throw an error if there is no kibana plugin', function () { return copyReplyFile('test_plugin_no_kibana.zip') .then(() => { return getPackData(settings, logger); }) - .then(shouldReject, err => { + .then(shouldReject, (err) => { expect(err.message).toMatch(/No kibana plugins found in archive/i); }); }); - it('throw an error with a corrupt zip', function() { + it('throw an error with a corrupt zip', function () { return copyReplyFile('corrupt.zip') .then(() => { return getPackData(settings, logger); }) - .then(shouldReject, err => { + .then(shouldReject, (err) => { expect(err.message).toMatch(/error retrieving/i); }); }); - it('throw an error if there an invalid plugin name', function() { + it('throw an error if there an invalid plugin name', function () { return copyReplyFile('invalid_name.zip') .then(() => { return getPackData(settings, logger); }) - .then(shouldReject, err => { + .then(shouldReject, (err) => { expect(err.message).toMatch(/invalid plugin name/i); }); }); diff --git a/src/cli_plugin/install/progress.test.js b/src/cli_plugin/install/progress.test.js index 5430af75968bb..3b66e8b3dc86c 100644 --- a/src/cli_plugin/install/progress.test.js +++ b/src/cli_plugin/install/progress.test.js @@ -21,26 +21,26 @@ import sinon from 'sinon'; import Progress from './progress'; import Logger from '../lib/logger'; -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('progressReporter', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('progressReporter', function () { let logger; let progress; - beforeEach(function() { + beforeEach(function () { logger = new Logger({ silent: false, quiet: false }); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); progress = new Progress(logger); }); - afterEach(function() { + afterEach(function () { logger.log.restore(); logger.error.restore(); }); - describe('handleData', function() { - it('should show a max of 20 dots for full progress', function() { + describe('handleData', function () { + it('should show a max of 20 dots for full progress', function () { progress.init(1000); progress.progress(1000); progress.complete(); @@ -70,7 +70,7 @@ describe('kibana cli', function() { expect(logger.log.getCall(21).args[0]).toMatch(/complete/i); }); - it('should show dot for each 5% of completion', function() { + it('should show dot for each 5% of completion', function () { progress.init(1000); expect(logger.log.callCount).toBe(1); diff --git a/src/cli_plugin/install/rename.js b/src/cli_plugin/install/rename.js index 92adb21368007..1e5d94d474375 100644 --- a/src/cli_plugin/install/rename.js +++ b/src/cli_plugin/install/rename.js @@ -21,7 +21,7 @@ import { rename } from 'fs'; import { delay } from 'lodash'; export function renamePlugin(workingPath, finalPath) { - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { const start = Date.now(); const retryTime = 3000; const retryDelay = 100; diff --git a/src/cli_plugin/install/rename.test.js b/src/cli_plugin/install/rename.test.js index c725a1218cbd2..40df75adc5efa 100644 --- a/src/cli_plugin/install/rename.test.js +++ b/src/cli_plugin/install/rename.test.js @@ -22,63 +22,63 @@ import fs from 'fs'; import { renamePlugin } from './rename'; -describe('plugin folder rename', function() { +describe('plugin folder rename', function () { let renameStub; - beforeEach(function() { + beforeEach(function () { renameStub = sinon.stub(); }); - afterEach(function() { + afterEach(function () { fs.rename.restore(); }); - it('should rethrow any exceptions', function() { + it('should rethrow any exceptions', function () { renameStub = sinon.stub(fs, 'rename').callsFake((from, to, cb) => { cb({ code: 'error', }); }); - return renamePlugin('/foo/bar', '/bar/foo').catch(function(err) { + return renamePlugin('/foo/bar', '/bar/foo').catch(function (err) { expect(err.code).toBe('error'); expect(renameStub.callCount).toBe(1); }); }); - it('should resolve if there are no errors', function() { + it('should resolve if there are no errors', function () { renameStub = sinon.stub(fs, 'rename').callsFake((from, to, cb) => { cb(); }); return renamePlugin('/foo/bar', '/bar/foo') - .then(function() { + .then(function () { expect(renameStub.callCount).toBe(1); }) - .catch(function() { + .catch(function () { throw new Error("We shouldn't have any errors"); }); }); - describe('Windows', function() { + describe('Windows', function () { let platform; - beforeEach(function() { + beforeEach(function () { platform = Object.getOwnPropertyDescriptor(process, 'platform'); Object.defineProperty(process, 'platform', { value: 'win32', }); }); - afterEach(function() { + afterEach(function () { Object.defineProperty(process, 'platform', platform); }); - it('should retry on Windows EPERM errors for up to 3 seconds', function() { + it('should retry on Windows EPERM errors for up to 3 seconds', function () { renameStub = sinon.stub(fs, 'rename').callsFake((from, to, cb) => { cb({ code: 'EPERM', }); }); - return renamePlugin('/foo/bar', '/bar/foo').catch(function(err) { + return renamePlugin('/foo/bar', '/bar/foo').catch(function (err) { expect(err.code).toBe('EPERM'); expect(renameStub.callCount).toBeGreaterThan(1); }); diff --git a/src/cli_plugin/install/settings.js b/src/cli_plugin/install/settings.js index 1f924caddf1b7..40c845fc37a9e 100644 --- a/src/cli_plugin/install/settings.js +++ b/src/cli_plugin/install/settings.js @@ -56,7 +56,7 @@ export function parse(command, options, kbnPackage) { settings.workingPath = resolve(settings.pluginDir, '.plugin.installing'); settings.tempArchiveFile = resolve(settings.workingPath, 'archive.part'); settings.tempPackageFile = resolve(settings.workingPath, 'package.json'); - settings.setPlugin = function(plugin) { + settings.setPlugin = function (plugin) { settings.plugin = plugin; settings.pluginPath = resolve(settings.pluginDir, settings.plugin.name); }; diff --git a/src/cli_plugin/install/settings.test.js b/src/cli_plugin/install/settings.test.js index 1f0aef4377b40..39ca07405ade2 100644 --- a/src/cli_plugin/install/settings.test.js +++ b/src/cli_plugin/install/settings.test.js @@ -21,23 +21,23 @@ import { fromRoot } from '../../core/server/utils'; import { resolve } from 'path'; import { parseMilliseconds, parse } from './settings'; -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('command line option parsing', function() { - describe('parseMilliseconds function', function() { - it('should return 0 for an empty string', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('command line option parsing', function () { + describe('parseMilliseconds function', function () { + it('should return 0 for an empty string', function () { const value = ''; const result = parseMilliseconds(value); expect(result).toBe(0); }); - it('should return 0 for a number with an invalid unit of measure', function() { + it('should return 0 for a number with an invalid unit of measure', function () { const result = parseMilliseconds('1gigablasts'); expect(result).toBe(0); }); - it('should assume a number with no unit of measure is specified as milliseconds', function() { + it('should assume a number with no unit of measure is specified as milliseconds', function () { const result = parseMilliseconds(1); expect(result).toBe(1); @@ -45,53 +45,53 @@ describe('kibana cli', function() { expect(result2).toBe(1); }); - it('should interpret a number with "s" as the unit of measure as seconds', function() { + it('should interpret a number with "s" as the unit of measure as seconds', function () { const result = parseMilliseconds('5s'); expect(result).toBe(5 * 1000); }); - it('should interpret a number with "second" as the unit of measure as seconds', function() { + it('should interpret a number with "second" as the unit of measure as seconds', function () { const result = parseMilliseconds('5second'); expect(result).toBe(5 * 1000); }); - it('should interpret a number with "seconds" as the unit of measure as seconds', function() { + it('should interpret a number with "seconds" as the unit of measure as seconds', function () { const result = parseMilliseconds('5seconds'); expect(result).toBe(5 * 1000); }); - it('should interpret a number with "m" as the unit of measure as minutes', function() { + it('should interpret a number with "m" as the unit of measure as minutes', function () { const result = parseMilliseconds('9m'); expect(result).toBe(9 * 1000 * 60); }); - it('should interpret a number with "minute" as the unit of measure as minutes', function() { + it('should interpret a number with "minute" as the unit of measure as minutes', function () { const result = parseMilliseconds('9minute'); expect(result).toBe(9 * 1000 * 60); }); - it('should interpret a number with "minutes" as the unit of measure as minutes', function() { + it('should interpret a number with "minutes" as the unit of measure as minutes', function () { const result = parseMilliseconds('9minutes'); expect(result).toBe(9 * 1000 * 60); }); }); - describe('parse function', function() { + describe('parse function', function () { const command = 'plugin name'; let options = {}; const kbnPackage = { version: 1234 }; - beforeEach(function() { + beforeEach(function () { options = { pluginDir: fromRoot('plugins') }; }); - describe('timeout option', function() { - it('should default to 0 (milliseconds)', function() { + describe('timeout option', function () { + it('should default to 0 (milliseconds)', function () { const settings = parse(command, options, kbnPackage); expect(settings.timeout).toBe(0); }); - it('should set settings.timeout property', function() { + it('should set settings.timeout property', function () { options.timeout = 1234; const settings = parse(command, options, kbnPackage); @@ -99,14 +99,14 @@ describe('kibana cli', function() { }); }); - describe('quiet option', function() { - it('should default to false', function() { + describe('quiet option', function () { + it('should default to false', function () { const settings = parse(command, options, kbnPackage); expect(settings.quiet).toBe(false); }); - it('should set settings.quiet property to true', function() { + it('should set settings.quiet property to true', function () { options.quiet = true; const settings = parse(command, options, kbnPackage); @@ -114,14 +114,14 @@ describe('kibana cli', function() { }); }); - describe('silent option', function() { - it('should default to false', function() { + describe('silent option', function () { + it('should default to false', function () { const settings = parse(command, options, kbnPackage); expect(settings.silent).toBe(false); }); - it('should set settings.silent property to true', function() { + it('should set settings.silent property to true', function () { options.silent = true; const settings = parse(command, options, kbnPackage); @@ -129,14 +129,14 @@ describe('kibana cli', function() { }); }); - describe('config option', function() { - it('should default to ZLS', function() { + describe('config option', function () { + it('should default to ZLS', function () { const settings = parse(command, options, kbnPackage); expect(settings.config).toBe(''); }); - it('should set settings.config property', function() { + it('should set settings.config property', function () { options.config = 'foo bar baz'; const settings = parse(command, options, kbnPackage); @@ -144,14 +144,14 @@ describe('kibana cli', function() { }); }); - describe('pluginDir option', function() { - it('should default to plugins', function() { + describe('pluginDir option', function () { + it('should default to plugins', function () { const settings = parse(command, options, kbnPackage); expect(settings.pluginDir).toBe(fromRoot('plugins')); }); - it('should set settings.config property', function() { + it('should set settings.config property', function () { options.pluginDir = 'foo bar baz'; const settings = parse(command, options, kbnPackage); @@ -159,16 +159,16 @@ describe('kibana cli', function() { }); }); - describe('command value', function() { - it('should set settings.plugin property', function() { + describe('command value', function () { + it('should set settings.plugin property', function () { const settings = parse(command, options, kbnPackage); expect(settings.plugin).toBe(command); }); }); - describe('urls collection', function() { - it('should populate the settings.urls property', function() { + describe('urls collection', function () { + it('should populate the settings.urls property', function () { const settings = parse(command, options, kbnPackage); const expected = [ @@ -180,8 +180,8 @@ describe('kibana cli', function() { }); }); - describe('workingPath value', function() { - it('should set settings.workingPath property', function() { + describe('workingPath value', function () { + it('should set settings.workingPath property', function () { options.pluginDir = 'foo/bar/baz'; const settings = parse(command, options, kbnPackage); const expected = resolve('foo/bar/baz', '.plugin.installing'); @@ -190,8 +190,8 @@ describe('kibana cli', function() { }); }); - describe('tempArchiveFile value', function() { - it('should set settings.tempArchiveFile property', function() { + describe('tempArchiveFile value', function () { + it('should set settings.tempArchiveFile property', function () { options.pluginDir = 'foo/bar/baz'; const settings = parse(command, options, kbnPackage); const expected = resolve('foo/bar/baz', '.plugin.installing', 'archive.part'); @@ -200,8 +200,8 @@ describe('kibana cli', function() { }); }); - describe('tempPackageFile value', function() { - it('should set settings.tempPackageFile property', function() { + describe('tempPackageFile value', function () { + it('should set settings.tempPackageFile property', function () { options.pluginDir = 'foo/bar/baz'; const settings = parse(command, options, kbnPackage); const expected = resolve('foo/bar/baz', '.plugin.installing', 'package.json'); diff --git a/src/cli_plugin/install/zip.js b/src/cli_plugin/install/zip.js index 03e6edb63b4ff..52eba2ea239a2 100644 --- a/src/cli_plugin/install/zip.js +++ b/src/cli_plugin/install/zip.js @@ -34,29 +34,29 @@ export function analyzeArchive(archive) { const regExp = new RegExp('(kibana[\\\\/][^\\\\/]+)[\\\\/]package.json', 'i'); return new Promise((resolve, reject) => { - yauzl.open(archive, { lazyEntries: true }, function(err, zipfile) { + yauzl.open(archive, { lazyEntries: true }, function (err, zipfile) { if (err) { return reject(err); } zipfile.readEntry(); - zipfile.on('entry', function(entry) { + zipfile.on('entry', function (entry) { const match = entry.fileName.match(regExp); if (!match) { return zipfile.readEntry(); } - zipfile.openReadStream(entry, function(err, readable) { + zipfile.openReadStream(entry, function (err, readable) { const chunks = []; if (err) { return reject(err); } - readable.on('data', chunk => chunks.push(chunk)); + readable.on('data', (chunk) => chunks.push(chunk)); - readable.on('end', function() { + readable.on('end', function () { const contents = Buffer.concat(chunks).toString(); const pkg = JSON.parse(contents); @@ -92,14 +92,14 @@ export function _isDirectory(filename) { export function extractArchive(archive, targetDir, extractPath) { return new Promise((resolve, reject) => { - yauzl.open(archive, { lazyEntries: true }, function(err, zipfile) { + yauzl.open(archive, { lazyEntries: true }, function (err, zipfile) { if (err) { return reject(err); } zipfile.readEntry(); zipfile.on('close', resolve); - zipfile.on('entry', function(entry) { + zipfile.on('entry', function (entry) { let fileName = entry.fileName; if (extractPath && fileName.startsWith(extractPath)) { @@ -113,7 +113,7 @@ export function extractArchive(archive, targetDir, extractPath) { } if (_isDirectory(fileName)) { - mkdir(fileName, { recursive: true }, function(err) { + mkdir(fileName, { recursive: true }, function (err) { if (err) { return reject(err); } @@ -122,13 +122,13 @@ export function extractArchive(archive, targetDir, extractPath) { }); } else { // file entry - zipfile.openReadStream(entry, function(err, readStream) { + zipfile.openReadStream(entry, function (err, readStream) { if (err) { return reject(err); } // ensure parent directory exists - mkdir(path.dirname(fileName), { recursive: true }, function(err) { + mkdir(path.dirname(fileName), { recursive: true }, function (err) { if (err) { return reject(err); } @@ -136,7 +136,7 @@ export function extractArchive(archive, targetDir, extractPath) { readStream.pipe( createWriteStream(fileName, { mode: entry.externalFileAttributes >>> 16 }) ); - readStream.on('end', function() { + readStream.on('end', function () { zipfile.readEntry(); }); }); diff --git a/src/cli_plugin/install/zip.test.js b/src/cli_plugin/install/zip.test.js index 8f75367ec8eb4..28367e9e24453 100644 --- a/src/cli_plugin/install/zip.test.js +++ b/src/cli_plugin/install/zip.test.js @@ -24,8 +24,8 @@ import glob from 'glob'; import fs from 'fs'; import { analyzeArchive, extractArchive, _isDirectory } from './zip'; -describe('kibana cli', function() { - describe('zip', function() { +describe('kibana cli', function () { + describe('zip', function () { const repliesPath = path.resolve(__dirname, '__fixtures__', 'replies'); const archivePath = path.resolve(repliesPath, 'test_plugin.zip'); @@ -40,7 +40,7 @@ describe('kibana cli', function() { del.sync(tempPath, { force: true }); }); - describe('analyzeArchive', function() { + describe('analyzeArchive', function () { it('returns array of plugins', async () => { const packages = await analyzeArchive(archivePath); const plugin = packages[0]; diff --git a/src/cli_plugin/lib/log_warnings.js b/src/cli_plugin/lib/log_warnings.js index 3adf0ba849c23..b4542acecb305 100644 --- a/src/cli_plugin/lib/log_warnings.js +++ b/src/cli_plugin/lib/log_warnings.js @@ -17,8 +17,8 @@ * under the License. */ -export default function(settings, logger) { - process.on('warning', warning => { +export default function (settings, logger) { + process.on('warning', (warning) => { // deprecation warnings do no reflect a current problem for // the user and therefor should be filtered out. if (warning.name === 'DeprecationWarning') { diff --git a/src/cli_plugin/lib/logger.test.js b/src/cli_plugin/lib/logger.test.js index db8454f465b47..00cad1a9bbb11 100644 --- a/src/cli_plugin/lib/logger.test.js +++ b/src/cli_plugin/lib/logger.test.js @@ -20,21 +20,21 @@ import sinon from 'sinon'; import Logger from './logger'; -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('logger', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('logger', function () { let logger; - describe('logger.log', function() { - beforeEach(function() { + describe('logger.log', function () { + beforeEach(function () { sinon.stub(process.stdout, 'write'); }); - afterEach(function() { + afterEach(function () { process.stdout.write.restore(); }); - it('should log messages to the console and append a new line', function() { + it('should log messages to the console and append a new line', function () { logger = new Logger({ silent: false, quiet: false }); const message = 'this is my message'; @@ -45,7 +45,7 @@ describe('kibana cli', function() { expect(process.stdout.write.getCall(callCount - 1).args[0]).toBe('\n'); }); - it('should log messages to the console and append not append a new line', function() { + it('should log messages to the console and append not append a new line', function () { logger = new Logger({ silent: false, quiet: false }); for (let i = 0; i < 10; i++) { logger.log('.', true); @@ -68,7 +68,7 @@ describe('kibana cli', function() { expect(process.stdout.write.getCall(12).args[0]).toBe('\n'); }); - it('should not log any messages when quiet is set', function() { + it('should not log any messages when quiet is set', function () { logger = new Logger({ silent: false, quiet: true }); const message = 'this is my message'; @@ -82,7 +82,7 @@ describe('kibana cli', function() { expect(process.stdout.write.callCount).toBe(0); }); - it('should not log any messages when silent is set', function() { + it('should not log any messages when silent is set', function () { logger = new Logger({ silent: true, quiet: false }); const message = 'this is my message'; @@ -97,16 +97,16 @@ describe('kibana cli', function() { }); }); - describe('logger.error', function() { - beforeEach(function() { + describe('logger.error', function () { + beforeEach(function () { sinon.stub(process.stderr, 'write'); }); - afterEach(function() { + afterEach(function () { process.stderr.write.restore(); }); - it('should log error messages to the console and append a new line', function() { + it('should log error messages to the console and append a new line', function () { logger = new Logger({ silent: false, quiet: false }); const message = 'this is my error'; @@ -114,7 +114,7 @@ describe('kibana cli', function() { expect(process.stderr.write.calledWith(message + '\n')).toBe(true); }); - it('should log error messages to the console when quiet is set', function() { + it('should log error messages to the console when quiet is set', function () { logger = new Logger({ silent: false, quiet: true }); const message = 'this is my error'; @@ -122,7 +122,7 @@ describe('kibana cli', function() { expect(process.stderr.write.calledWith(message + '\n')).toBe(true); }); - it('should not log any error messages when silent is set', function() { + it('should not log any error messages when silent is set', function () { logger = new Logger({ silent: true, quiet: false }); const message = 'this is my error'; diff --git a/src/cli_plugin/list/list.js b/src/cli_plugin/list/list.js index d53e868b32e36..b34631e5dfd08 100644 --- a/src/cli_plugin/list/list.js +++ b/src/cli_plugin/list/list.js @@ -21,7 +21,7 @@ import { statSync, readdirSync, readFileSync } from 'fs'; import { join } from 'path'; export default function list(settings, logger) { - readdirSync(settings.pluginDir).forEach(filename => { + readdirSync(settings.pluginDir).forEach((filename) => { const stat = statSync(join(settings.pluginDir, filename)); if (stat.isDirectory() && filename[0] !== '.') { diff --git a/src/cli_plugin/list/list.test.js b/src/cli_plugin/list/list.test.js index c6480ca52b59a..071a253fa87fe 100644 --- a/src/cli_plugin/list/list.test.js +++ b/src/cli_plugin/list/list.test.js @@ -30,8 +30,8 @@ function createPlugin(name, version, pluginBaseDir) { appendFileSync(join(pluginDir, 'package.json'), '{"version": "' + version + '"}'); } -describe('kibana cli', function() { - describe('plugin lister', function() { +describe('kibana cli', function () { + describe('plugin lister', function () { const pluginDir = join(__dirname, '.test.data.list'); let logger; @@ -39,7 +39,7 @@ describe('kibana cli', function() { pluginDir: pluginDir, }; - beforeEach(function() { + beforeEach(function () { logger = new Logger(settings); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); @@ -47,13 +47,13 @@ describe('kibana cli', function() { mkdirSync(pluginDir, { recursive: true }); }); - afterEach(function() { + afterEach(function () { logger.log.restore(); logger.error.restore(); del.sync(pluginDir); }); - it('list all of the folders in the plugin folder', function() { + it('list all of the folders in the plugin folder', function () { createPlugin('plugin1', '5.0.0-alpha2', pluginDir); createPlugin('plugin2', '3.2.1', pluginDir); createPlugin('plugin3', '1.2.3', pluginDir); @@ -65,7 +65,7 @@ describe('kibana cli', function() { expect(logger.log.calledWith('plugin3@1.2.3')).toBe(true); }); - it('ignore folders that start with a period', function() { + it('ignore folders that start with a period', function () { createPlugin('.foo', '1.0.0', pluginDir); createPlugin('plugin1', '5.0.0-alpha2', pluginDir); createPlugin('plugin2', '3.2.1', pluginDir); @@ -78,7 +78,7 @@ describe('kibana cli', function() { expect(logger.log.calledWith('.bar@1.0.0')).toBe(false); }); - it('list should only list folders', function() { + it('list should only list folders', function () { createPlugin('plugin1', '1.0.0', pluginDir); createPlugin('plugin2', '1.0.0', pluginDir); createPlugin('plugin3', '1.0.0', pluginDir); @@ -91,22 +91,22 @@ describe('kibana cli', function() { expect(logger.log.calledWith('plugin3@1.0.0')).toBe(true); }); - it('list should throw an exception if a plugin does not have a package.json', function() { + it('list should throw an exception if a plugin does not have a package.json', function () { createPlugin('plugin1', '1.0.0', pluginDir); mkdirSync(join(pluginDir, 'empty-plugin'), { recursive: true }); - expect(function() { + expect(function () { list(settings, logger); }).toThrowError('Unable to read package.json file for plugin empty-plugin'); }); - it('list should throw an exception if a plugin have an empty package.json', function() { + it('list should throw an exception if a plugin have an empty package.json', function () { createPlugin('plugin1', '1.0.0', pluginDir); const invalidPluginDir = join(pluginDir, 'invalid-plugin'); mkdirSync(invalidPluginDir, { recursive: true }); appendFileSync(join(invalidPluginDir, 'package.json'), ''); - expect(function() { + expect(function () { list(settings, logger); }).toThrowError('Unable to read package.json file for plugin invalid-plugin'); }); diff --git a/src/cli_plugin/list/settings.test.js b/src/cli_plugin/list/settings.test.js index 144b5d8661527..85e6cb88e82fd 100644 --- a/src/cli_plugin/list/settings.test.js +++ b/src/cli_plugin/list/settings.test.js @@ -20,24 +20,24 @@ import { fromRoot } from '../../core/server/utils'; import { parse } from './settings'; -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('command line option parsing', function() { - describe('parse function', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('command line option parsing', function () { + describe('parse function', function () { let command; const options = {}; - beforeEach(function() { + beforeEach(function () { command = { pluginDir: fromRoot('plugins') }; }); - describe('pluginDir option', function() { - it('should default to plugins', function() { + describe('pluginDir option', function () { + it('should default to plugins', function () { const settings = parse(command, options); expect(settings.pluginDir).toBe(fromRoot('plugins')); }); - it('should set settings.config property', function() { + it('should set settings.config property', function () { command.pluginDir = 'foo bar baz'; const settings = parse(command, options); diff --git a/src/cli_plugin/remove/remove.test.js b/src/cli_plugin/remove/remove.test.js index 032a17abe209e..4bf061820aa05 100644 --- a/src/cli_plugin/remove/remove.test.js +++ b/src/cli_plugin/remove/remove.test.js @@ -25,15 +25,15 @@ import remove from './remove'; import { join } from 'path'; import { writeFileSync, existsSync, mkdirSync } from 'fs'; -describe('kibana cli', function() { - describe('plugin remover', function() { +describe('kibana cli', function () { + describe('plugin remover', function () { const pluginDir = join(__dirname, '.test.data.remove'); let processExitStub; let logger; const settings = { pluginDir }; - beforeEach(function() { + beforeEach(function () { processExitStub = sinon.stub(process, 'exit'); logger = new Logger(settings); sinon.stub(logger, 'log'); @@ -42,14 +42,14 @@ describe('kibana cli', function() { mkdirSync(pluginDir, { recursive: true }); }); - afterEach(function() { + afterEach(function () { processExitStub.restore(); logger.log.restore(); logger.error.restore(); del.sync(pluginDir); }); - it('throw an error if the plugin is not installed.', function() { + it('throw an error if the plugin is not installed.', function () { settings.pluginPath = join(pluginDir, 'foo'); settings.plugin = 'foo'; @@ -58,7 +58,7 @@ describe('kibana cli', function() { expect(process.exit.called).toBe(true); }); - it('throw an error if the specified plugin is not a folder.', function() { + it('throw an error if the specified plugin is not a folder.', function () { writeFileSync(join(pluginDir, 'foo'), 'This is a file, and not a folder.'); remove(settings, logger); @@ -85,7 +85,7 @@ describe('kibana cli', function() { ); }); - it('delete the specified folder.', function() { + it('delete the specified folder.', function () { settings.pluginPath = join(pluginDir, 'foo'); mkdirSync(join(pluginDir, 'foo'), { recursive: true }); mkdirSync(join(pluginDir, 'bar'), { recursive: true }); diff --git a/src/cli_plugin/remove/settings.test.js b/src/cli_plugin/remove/settings.test.js index 5bb4b30cfff09..b3110e1ff0499 100644 --- a/src/cli_plugin/remove/settings.test.js +++ b/src/cli_plugin/remove/settings.test.js @@ -20,25 +20,25 @@ import { fromRoot } from '../../core/server/utils'; import { parse } from './settings'; -describe('kibana cli', function() { - describe('plugin installer', function() { - describe('command line option parsing', function() { - describe('parse function', function() { +describe('kibana cli', function () { + describe('plugin installer', function () { + describe('command line option parsing', function () { + describe('parse function', function () { const command = 'plugin name'; let options = {}; const kbnPackage = { version: 1234 }; - beforeEach(function() { + beforeEach(function () { options = { pluginDir: fromRoot('plugins') }; }); - describe('quiet option', function() { - it('should default to false', function() { + describe('quiet option', function () { + it('should default to false', function () { const settings = parse(command, options, kbnPackage); expect(settings.quiet).toBe(false); }); - it('should set settings.quiet property to true', function() { + it('should set settings.quiet property to true', function () { options.quiet = true; const settings = parse(command, options, kbnPackage); @@ -46,14 +46,14 @@ describe('kibana cli', function() { }); }); - describe('silent option', function() { - it('should default to false', function() { + describe('silent option', function () { + it('should default to false', function () { const settings = parse(command, options, kbnPackage); expect(settings.silent).toBe(false); }); - it('should set settings.silent property to true', function() { + it('should set settings.silent property to true', function () { options.silent = true; const settings = parse(command, options, kbnPackage); @@ -61,14 +61,14 @@ describe('kibana cli', function() { }); }); - describe('config option', function() { - it('should default to ZLS', function() { + describe('config option', function () { + it('should default to ZLS', function () { const settings = parse(command, options, kbnPackage); expect(settings.config).toBe(''); }); - it('should set settings.config property', function() { + it('should set settings.config property', function () { options.config = 'foo bar baz'; const settings = parse(command, options, kbnPackage); @@ -76,14 +76,14 @@ describe('kibana cli', function() { }); }); - describe('pluginDir option', function() { - it('should default to plugins', function() { + describe('pluginDir option', function () { + it('should default to plugins', function () { const settings = parse(command, options, kbnPackage); expect(settings.pluginDir).toBe(fromRoot('plugins')); }); - it('should set settings.config property', function() { + it('should set settings.config property', function () { options.pluginDir = 'foo bar baz'; const settings = parse(command, options, kbnPackage); @@ -91,8 +91,8 @@ describe('kibana cli', function() { }); }); - describe('command value', function() { - it('should set settings.plugin property', function() { + describe('command value', function () { + it('should set settings.plugin property', function () { const settings = parse(command, options, kbnPackage); expect(settings.plugin).toBe(command); diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 447c6f396945f..a4f50e73f1c57 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -167,17 +167,21 @@ leverage this pattern. import React from 'react'; import ReactDOM from 'react-dom'; -import { CoreStart, AppMountParams } from '../../src/core/public'; +import { CoreStart, AppMountParameters } from 'src/core/public'; import { MyAppRoot } from './components/app.ts'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. */ -export const renderApp = (core: CoreStart, deps: MyPluginDepsStart, { element, history }: AppMountParams) => { +export const renderApp = ( + core: CoreStart, + deps: MyPluginDepsStart, + { element, history }: AppMountParameters +) => { ReactDOM.render(, element); return () => ReactDOM.unmountComponentAtNode(element); -} +}; ``` ```ts @@ -332,7 +336,7 @@ import { SavedObjectsType } from 'src/core/server'; export const myType: SavedObjectsType = { name: 'my-type', hidden: false, - namespaceAgnostic: true, + namespaceType: 'single', mappings: { properties: { someField: { diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 5cec20fb900f2..6bb5a845ea2ab 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -850,7 +850,7 @@ import { SavedObjectsType } from 'src/core/server'; export const firstType: SavedObjectsType = { name: 'first-type', hidden: false, - namespaceAgnostic: true, + namespaceType: 'agnostic', mappings: { properties: { someField: { @@ -888,7 +888,7 @@ import { SavedObjectsType } from 'src/core/server'; export const secondType: SavedObjectsType = { name: 'second-type', hidden: true, - namespaceAgnostic: false, + namespaceType: 'single', mappings: { properties: { textField: { @@ -936,7 +936,7 @@ export class MyPlugin implements Plugin { The NP `registerType` expected input is very close to the legacy format. However, there are some minor changes: -- The `schema.isNamespaceAgnostic` property has been renamed: `SavedObjectsType.namespaceAgnostic` +- The `schema.isNamespaceAgnostic` property has been renamed: `SavedObjectsType.namespaceType`. It no longer accepts a boolean but instead an enum of 'single', 'multiple', or 'agnostic' (see [SavedObjectsNamespaceType](/docs/development/core/server/kibana-plugin-core-server.savedobjectsnamespacetype.md)). - The `schema.indexPattern` was accepting either a `string` or a `(config: LegacyConfig) => string`. `SavedObjectsType.indexPattern` only accepts a string, as you can access the configuration during your plugin's setup phase. diff --git a/src/core/TESTING.md b/src/core/TESTING.md index cb38dac0e20ce..bed41ab583496 100644 --- a/src/core/TESTING.md +++ b/src/core/TESTING.md @@ -475,10 +475,14 @@ The more interesting logic is in `renderApp`: import React from 'react'; import ReactDOM from 'react-dom'; -import { AppMountParams, CoreStart } from 'src/core/public'; +import { AppMountParameters, CoreStart } from 'src/core/public'; import { AppRoot } from './components/app_root'; -export const renderApp = ({ element, history }: AppMountParams, core: CoreStart, plugins: MyPluginDepsStart) => { +export const renderApp = ( + { element, history }: AppMountParameters, + core: CoreStart, + plugins: MyPluginDepsStart +) => { // Hide the chrome while this app is mounted for a full screen experience core.chrome.setIsVisible(false); diff --git a/src/core/public/application/application_leave.test.ts b/src/core/public/application/application_leave.test.ts index e06183d8bb8d9..b560bbc0cbc25 100644 --- a/src/core/public/application/application_leave.test.ts +++ b/src/core/public/application/application_leave.test.ts @@ -31,16 +31,16 @@ describe('isConfirmAction', () => { describe('getLeaveAction', () => { it('returns the default action provided by the handler', () => { - expect(getLeaveAction(actions => actions.default())).toEqual({ + expect(getLeaveAction((actions) => actions.default())).toEqual({ type: AppLeaveActionType.default, }); }); it('returns the confirm action provided by the handler', () => { - expect(getLeaveAction(actions => actions.confirm('some message'))).toEqual({ + expect(getLeaveAction((actions) => actions.confirm('some message'))).toEqual({ type: AppLeaveActionType.confirm, text: 'some message', }); - expect(getLeaveAction(actions => actions.confirm('another message', 'a title'))).toEqual({ + expect(getLeaveAction((actions) => actions.confirm('another message', 'a title'))).toEqual({ type: AppLeaveActionType.confirm, text: 'another message', title: 'a title', diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index d2a827d381be5..300b09e17d15d 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -25,8 +25,8 @@ import { InternalApplicationStart, ApplicationStart, InternalApplicationSetup, - App, - LegacyApp, + PublicAppInfo, + PublicLegacyAppInfo, } from './types'; import { ApplicationServiceContract } from './test_types'; @@ -47,9 +47,11 @@ const createStartContractMock = (): jest.Mocked => { const currentAppId$ = new Subject(); return { + applications$: new BehaviorSubject>(new Map()), currentAppId$: currentAppId$.asObservable(), capabilities: capabilitiesServiceMock.createStartContract().capabilities, navigateToApp: jest.fn(), + navigateToUrl: jest.fn(), getUrlForApp: jest.fn(), registerMountContext: jest.fn(), }; @@ -59,12 +61,13 @@ const createInternalStartContractMock = (): jest.Mocked(); return { - applications$: new BehaviorSubject>(new Map()), + applications$: new BehaviorSubject>(new Map()), capabilities: capabilitiesServiceMock.createStartContract().capabilities, currentAppId$: currentAppId$.asObservable(), getComponent: jest.fn(), getUrlForApp: jest.fn(), - navigateToApp: jest.fn().mockImplementation(appId => currentAppId$.next(appId)), + navigateToApp: jest.fn().mockImplementation((appId) => currentAppId$.next(appId)), + navigateToUrl: jest.fn(), registerMountContext: jest.fn(), }; }; diff --git a/src/core/public/application/application_service.test.mocks.ts b/src/core/public/application/application_service.test.mocks.ts index d829cf18e56be..a096f05209708 100644 --- a/src/core/public/application/application_service.test.mocks.ts +++ b/src/core/public/application/application_service.test.mocks.ts @@ -34,3 +34,9 @@ export const createBrowserHistoryMock = jest.fn().mockReturnValue(MockHistory); jest.doMock('history', () => ({ createBrowserHistory: createBrowserHistoryMock, })); + +export const parseAppUrlMock = jest.fn(); +jest.doMock('./utils', () => ({ + ...jest.requireActual('./utils'), + parseAppUrl: parseAppUrlMock, +})); diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index 04ff844ffc150..400d1881a5af8 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -17,6 +17,12 @@ * under the License. */ +import { + MockCapabilitiesService, + MockHistory, + parseAppUrlMock, +} from './application_service.test.mocks'; + import { createElement } from 'react'; import { BehaviorSubject, Subject } from 'rxjs'; import { bufferCount, take, takeUntil } from 'rxjs/operators'; @@ -26,10 +32,17 @@ import { injectedMetadataServiceMock } from '../injected_metadata/injected_metad import { contextServiceMock } from '../context/context_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { overlayServiceMock } from '../overlays/overlay_service.mock'; -import { MockCapabilitiesService, MockHistory } from './application_service.test.mocks'; import { MockLifecycle } from './test_types'; import { ApplicationService } from './application_service'; -import { App, AppNavLinkStatus, AppStatus, AppUpdater, LegacyApp } from './types'; +import { + App, + PublicAppInfo, + AppNavLinkStatus, + AppStatus, + AppUpdater, + LegacyApp, + PublicLegacyAppInfo, +} from './types'; import { act } from 'react-dom/test-utils'; const createApp = (props: Partial): App => { @@ -61,6 +74,7 @@ describe('#setup()', () => { http, context: contextServiceMock.createSetupContract(), injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + redirectTo: jest.fn(), }; setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); startDeps = { http, overlays: overlayServiceMock.createStartContract() }; @@ -92,7 +106,7 @@ describe('#setup()', () => { const setup = service.setup(setupDeps); const pluginId = Symbol('plugin'); - const updater$ = new BehaviorSubject(app => ({})); + const updater$ = new BehaviorSubject((app) => ({})); setup.register(pluginId, createApp({ id: 'app1', updater$ })); setup.register(pluginId, createApp({ id: 'app2' })); const { applications$ } = await service.start(startDeps); @@ -116,7 +130,7 @@ describe('#setup()', () => { }) ); - updater$.next(app => ({ + updater$.next((app) => ({ status: AppStatus.inaccessible, tooltip: 'App inaccessible due to reason', defaultPath: 'foo/bar', @@ -174,6 +188,10 @@ describe('#setup()', () => { ).toThrowErrorMatchingInlineSnapshot( `"Cannot register an application route that includes HTTP base path"` ); + + expect(() => + register(Symbol(), createApp({ id: 'app3', appRoute: '/base-path-i-am-not' })) + ).not.toThrow(); }); }); @@ -220,7 +238,7 @@ describe('#setup()', () => { setup.register(pluginId, createApp({ id: 'app1' })); setup.register(pluginId, createApp({ id: 'app2' })); setup.registerAppUpdater( - new BehaviorSubject(app => { + new BehaviorSubject((app) => { if (app.id === 'app1') { return { status: AppStatus.inaccessible, @@ -260,7 +278,7 @@ describe('#setup()', () => { it(`properly combine with application's updater$`, async () => { const setup = service.setup(setupDeps); const pluginId = Symbol('plugin'); - const appStatusUpdater$ = new BehaviorSubject(app => ({ + const appStatusUpdater$ = new BehaviorSubject((app) => ({ status: AppStatus.inaccessible, navLinkStatus: AppNavLinkStatus.disabled, })); @@ -268,7 +286,7 @@ describe('#setup()', () => { setup.register(pluginId, createApp({ id: 'app2' })); setup.registerAppUpdater( - new BehaviorSubject(app => { + new BehaviorSubject((app) => { if (app.id === 'app1') { return { status: AppStatus.accessible, @@ -311,7 +329,7 @@ describe('#setup()', () => { const pluginId = Symbol('plugin'); setup.register(pluginId, createApp({ id: 'app1' })); setup.registerAppUpdater( - new BehaviorSubject(app => { + new BehaviorSubject((app) => { return { status: AppStatus.inaccessible, navLinkStatus: AppNavLinkStatus.disabled, @@ -319,7 +337,7 @@ describe('#setup()', () => { }) ); setup.registerAppUpdater( - new BehaviorSubject(app => { + new BehaviorSubject((app) => { return { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.default, @@ -347,7 +365,7 @@ describe('#setup()', () => { const pluginId = Symbol('plugin'); setup.register(pluginId, createApp({ id: 'app1' })); - const statusUpdater = new BehaviorSubject(app => { + const statusUpdater = new BehaviorSubject((app) => { return { status: AppStatus.inaccessible, navLinkStatus: AppNavLinkStatus.disabled, @@ -356,8 +374,11 @@ describe('#setup()', () => { setup.registerAppUpdater(statusUpdater); const start = await service.start(startDeps); - let latestValue: ReadonlyMap = new Map(); - start.applications$.subscribe(apps => { + let latestValue: ReadonlyMap = new Map< + string, + PublicAppInfo | PublicLegacyAppInfo + >(); + start.applications$.subscribe((apps) => { latestValue = apps; }); @@ -370,7 +391,7 @@ describe('#setup()', () => { }) ); - statusUpdater.next(app => { + statusUpdater.next((app) => { return { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.hidden, @@ -393,7 +414,7 @@ describe('#setup()', () => { setup.registerLegacyApp(createLegacyApp({ id: 'app1' })); setup.registerAppUpdater( - new BehaviorSubject(app => { + new BehaviorSubject((app) => { return { status: AppStatus.inaccessible, navLinkStatus: AppNavLinkStatus.hidden, @@ -423,7 +444,7 @@ describe('#setup()', () => { const pluginId = Symbol('plugin'); setup.register(pluginId, createApp({ id: 'app1' })); - const updater = new BehaviorSubject(app => ({})); + const updater = new BehaviorSubject((app) => ({})); setup.registerAppUpdater(updater); const start = await service.start(startDeps); @@ -431,17 +452,17 @@ describe('#setup()', () => { expect(MockHistory.push).toHaveBeenCalledWith('/app/app1', undefined); MockHistory.push.mockClear(); - updater.next(app => ({ defaultPath: 'default-path' })); + updater.next((app) => ({ defaultPath: 'default-path' })); await start.navigateToApp('app1'); expect(MockHistory.push).toHaveBeenCalledWith('/app/app1/default-path', undefined); MockHistory.push.mockClear(); - updater.next(app => ({ defaultPath: 'another-path' })); + updater.next((app) => ({ defaultPath: 'another-path' })); await start.navigateToApp('app1'); expect(MockHistory.push).toHaveBeenCalledWith('/app/app1/another-path', undefined); MockHistory.push.mockClear(); - updater.next(app => ({})); + updater.next((app) => ({})); await start.navigateToApp('app1'); expect(MockHistory.push).toHaveBeenCalledWith('/app/app1', undefined); MockHistory.push.mockClear(); @@ -462,12 +483,14 @@ describe('#setup()', () => { describe('#start()', () => { beforeEach(() => { MockHistory.push.mockReset(); + parseAppUrlMock.mockReset(); const http = httpServiceMock.createSetupContract({ basePath: '/base-path' }); setupDeps = { http, context: contextServiceMock.createSetupContract(), injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + redirectTo: jest.fn(), }; setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); startDeps = { http, overlays: overlayServiceMock.createStartContract() }; @@ -775,7 +798,6 @@ describe('#start()', () => { }); it('redirects when in legacyMode', async () => { - setupDeps.redirectTo = jest.fn(); setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); service.setup(setupDeps); @@ -816,11 +838,11 @@ describe('#start()', () => { const history = createMemoryHistory(); setupDeps.history = history; - const flushPromises = () => new Promise(resolve => setImmediate(resolve)); + const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); // Create an app and a promise that allows us to control when the app completes mounting const createWaitingApp = (props: Partial): [App, () => void] => { let finishMount: () => void; - const mountPromise = new Promise(resolve => (finishMount = resolve)); + const mountPromise = new Promise((resolve) => (finishMount = resolve)); const app = { id: 'some-id', title: 'some-title', @@ -881,7 +903,6 @@ describe('#start()', () => { it('sets window.location.href when navigating to legacy apps', async () => { setupDeps.http = httpServiceMock.createSetupContract({ basePath: '/test' }); setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - setupDeps.redirectTo = jest.fn(); service.setup(setupDeps); const { navigateToApp } = await service.start(startDeps); @@ -893,7 +914,6 @@ describe('#start()', () => { it('handles legacy apps with subapps', async () => { setupDeps.http = httpServiceMock.createSetupContract({ basePath: '/test' }); setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - setupDeps.redirectTo = jest.fn(); const { registerLegacyApp } = service.setup(setupDeps); @@ -905,6 +925,30 @@ describe('#start()', () => { expect(setupDeps.redirectTo).toHaveBeenCalledWith('/test/app/baseApp'); }); }); + + describe('navigateToUrl', () => { + it('calls `redirectTo` when the url is not parseable', async () => { + parseAppUrlMock.mockReturnValue(undefined); + service.setup(setupDeps); + const { navigateToUrl } = await service.start(startDeps); + + await navigateToUrl('/not-an-app-path'); + + expect(MockHistory.push).not.toHaveBeenCalled(); + expect(setupDeps.redirectTo).toHaveBeenCalledWith('/not-an-app-path'); + }); + + it('calls `navigateToApp` when the url is an internal app link', async () => { + parseAppUrlMock.mockReturnValue({ app: 'foo', path: '/some-path' }); + service.setup(setupDeps); + const { navigateToUrl } = await service.start(startDeps); + + await navigateToUrl('/an-app-path'); + + expect(MockHistory.push).toHaveBeenCalledWith('/app/foo/some-path', undefined); + expect(setupDeps.redirectTo).not.toHaveBeenCalled(); + }); + }); }); describe('#stop()', () => { diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 6802c2363b9f8..2224f72e2bd91 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -46,17 +46,14 @@ import { Mounter, } from './types'; import { getLeaveAction, isConfirmAction } from './application_leave'; -import { appendAppPath } from './utils'; +import { appendAppPath, parseAppUrl, relativeToAbsolute, getAppInfo } from './utils'; interface SetupDeps { context: ContextSetup; http: HttpSetup; injectedMetadata: InjectedMetadataSetup; history?: History; - /** - * Only necessary for redirecting to legacy apps - * @deprecated - */ + /** Used to redirect to external urls (and legacy apps) */ redirectTo?: (path: string) => void; } @@ -109,6 +106,7 @@ export class ApplicationService { private history?: History; private mountContext?: IContextContainer; private navigate?: (url: string, state: any) => void; + private redirectTo?: (url: string) => void; public setup({ context, @@ -131,12 +129,12 @@ export class ApplicationService { this.navigate = (url, state) => // basePath not needed here because `history` is configured with basename this.history ? this.history.push(url, state) : redirectTo(basePath.prepend(url)); - + this.redirectTo = redirectTo; this.mountContext = context.createContextContainer(); const registerStatusUpdater = (application: string, updater$: Observable) => { const updaterId = Symbol(); - const subscription = updater$.subscribe(updater => { + const subscription = updater$.subscribe((updater) => { const nextValue = new Map(this.statusUpdaters$.getValue()); nextValue.set(updaterId, { application, @@ -160,7 +158,7 @@ export class ApplicationService { } else { handler = app.mount; } - return async params => { + return async (params) => { this.currentAppId$.next(app.id); return handler(params); }; @@ -179,7 +177,7 @@ export class ApplicationService { throw new Error( `An application is already registered with the appRoute "${app.appRoute}"` ); - } else if (basename && app.appRoute!.startsWith(basename)) { + } else if (basename && app.appRoute!.startsWith(`${basename}/`)) { throw new Error('Cannot register an application route that includes HTTP base path'); } @@ -201,14 +199,14 @@ export class ApplicationService { legacy: false, }); }, - registerLegacyApp: app => { + registerLegacyApp: (app) => { const appRoute = `/app/${app.id.split(':')[0]}`; if (this.registrationClosed) { throw new Error('Applications cannot be registered after "setup"'); } else if (this.apps.has(app.id)) { throw new Error(`An application is already registered with the id "${app.id}"`); - } else if (basename && appRoute!.startsWith(basename)) { + } else if (basename && appRoute!.startsWith(`${basename}/`)) { throw new Error('Cannot register an application route that includes HTTP base path'); } @@ -262,7 +260,7 @@ export class ApplicationService { const applications$ = new BehaviorSubject(availableApps); this.statusUpdaters$ .pipe( - map(statusUpdaters => { + map((statusUpdaters) => { return new Map( [...availableApps].map(([id, app]) => [ id, @@ -271,18 +269,35 @@ export class ApplicationService { ); }) ) - .subscribe(apps => applications$.next(apps)); + .subscribe((apps) => applications$.next(apps)); const applicationStatuses$ = applications$.pipe( - map(apps => new Map([...apps.entries()].map(([id, app]) => [id, app.status!]))), + map((apps) => new Map([...apps.entries()].map(([id, app]) => [id, app.status!]))), shareReplay(1) ); + const navigateToApp: InternalApplicationStart['navigateToApp'] = async ( + appId, + { path, state }: { path?: string; state?: any } = {} + ) => { + if (await this.shouldNavigate(overlays)) { + if (path === undefined) { + path = applications$.value.get(appId)?.defaultPath; + } + this.appLeaveHandlers.delete(this.currentAppId$.value!); + this.navigate!(getAppUrl(availableMounters, appId, path), state); + this.currentAppId$.next(appId); + } + }; + return { - applications$, + applications$: applications$.pipe( + map((apps) => new Map([...apps.entries()].map(([id, app]) => [id, getAppInfo(app)]))), + shareReplay(1) + ), capabilities, currentAppId$: this.currentAppId$.pipe( - filter(appId => appId !== undefined), + filter((appId) => appId !== undefined), distinctUntilChanged(), takeUntil(this.stop$) ), @@ -294,14 +309,13 @@ export class ApplicationService { const relUrl = http.basePath.prepend(getAppUrl(availableMounters, appId, path)); return absolute ? relativeToAbsolute(relUrl) : relUrl; }, - navigateToApp: async (appId, { path, state }: { path?: string; state?: any } = {}) => { - if (await this.shouldNavigate(overlays)) { - if (path === undefined) { - path = applications$.value.get(appId)?.defaultPath; - } - this.appLeaveHandlers.delete(this.currentAppId$.value!); - this.navigate!(getAppUrl(availableMounters, appId, path), state); - this.currentAppId$.next(appId); + navigateToApp, + navigateToUrl: async (url) => { + const appInfo = parseAppUrl(url, http.basePath, this.apps); + if (appInfo) { + return navigateToApp(appInfo.app, { path: appInfo.path }); + } else { + return this.redirectTo!(url); } }, getComponent: () => { @@ -314,7 +328,7 @@ export class ApplicationService { mounters={availableMounters} appStatuses$={applicationStatuses$} setAppLeaveHandler={this.setAppLeaveHandler} - setIsMounting={isMounting => httpLoadingCount$.next(isMounting ? 1 : 0)} + setIsMounting={(isMounting) => httpLoadingCount$.next(isMounting ? 1 : 0)} /> ); }, @@ -360,14 +374,14 @@ export class ApplicationService { this.stop$.next(); this.currentAppId$.complete(); this.statusUpdaters$.complete(); - this.subscriptions.forEach(sub => sub.unsubscribe()); + this.subscriptions.forEach((sub) => sub.unsubscribe()); window.removeEventListener('beforeunload', this.onBeforeUnload); } } const updateStatus = (app: T, statusUpdaters: AppUpdaterWrapper[]): T => { let changes: Partial = {}; - statusUpdaters.forEach(wrapper => { + statusUpdaters.forEach((wrapper) => { if (wrapper.application !== allApplicationsFilter && wrapper.application !== app.id) { return; } @@ -388,10 +402,3 @@ const updateStatus = (app: T, statusUpdaters: AppUpdaterWrapp ...changes, }; }; - -function relativeToAbsolute(url: string) { - // convert all link urls to absolute urls - const a = document.createElement('a'); - a.setAttribute('href', url); - return a.href; -} diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index ec10d2bc22871..d51a4c0d69d42 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -39,7 +39,9 @@ export { AppLeaveAction, AppLeaveDefaultAction, AppLeaveConfirmAction, + LegacyApp, + PublicAppInfo, + PublicLegacyAppInfo, // Internal types InternalApplicationStart, - LegacyApp, } from './types'; 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 e399fbc726977..89f90a9899dda 100644 --- a/src/core/public/application/integration_tests/application_service.test.tsx +++ b/src/core/public/application/integration_tests/application_service.test.tsx @@ -31,7 +31,7 @@ import { overlayServiceMock } from '../../overlays/overlay_service.mock'; import { AppMountParameters } from '../types'; import { ScopedHistory } from '../scoped_history'; -const flushPromises = () => new Promise(resolve => setImmediate(resolve)); +const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); describe('ApplicationService', () => { let setupDeps: MockLifecycle<'setup'>; @@ -68,7 +68,7 @@ describe('ApplicationService', () => { const { register } = service.setup(setupDeps); let resolveMount: () => void; - const promise = new Promise(resolve => { + const promise = new Promise((resolve) => { resolveMount = resolve; }); @@ -102,7 +102,7 @@ describe('ApplicationService', () => { const { register } = service.setup(setupDeps); let resolveMount: () => void; - const promise = new Promise(resolve => { + const promise = new Promise((resolve) => { resolveMount = resolve; }); @@ -146,7 +146,7 @@ describe('ApplicationService', () => { id: 'app1', title: 'App1', mount: ({ onAppLeave }: AppMountParameters) => { - onAppLeave(actions => actions.default()); + onAppLeave((actions) => actions.default()); return () => undefined; }, }); @@ -178,7 +178,7 @@ describe('ApplicationService', () => { id: 'app1', title: 'App1', mount: ({ onAppLeave }: AppMountParameters) => { - onAppLeave(actions => actions.default()); + onAppLeave((actions) => actions.default()); return () => undefined; }, }); @@ -213,7 +213,7 @@ describe('ApplicationService', () => { id: 'app1', title: 'App1', mount: ({ onAppLeave }: AppMountParameters) => { - onAppLeave(actions => actions.confirm('confirmation-message', 'confirmation-title')); + onAppLeave((actions) => actions.confirm('confirmation-message', 'confirmation-title')); return () => undefined; }, }); @@ -252,7 +252,7 @@ describe('ApplicationService', () => { id: 'app1', title: 'App1', mount: ({ onAppLeave }: AppMountParameters) => { - onAppLeave(actions => actions.confirm('confirmation-message', 'confirmation-title')); + onAppLeave((actions) => actions.confirm('confirmation-message', 'confirmation-title')); return () => undefined; }, }); diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx index 9f379859dc34f..2827b93f6d17e 100644 --- a/src/core/public/application/integration_tests/router.test.tsx +++ b/src/core/public/application/integration_tests/router.test.tsx @@ -45,7 +45,7 @@ describe('AppRouter', () => { const mountersToAppStatus$ = () => { return new BehaviorSubject( new Map( - [...mounters.keys()].map(id => [ + [...mounters.keys()].map((id) => [ id, id.startsWith('disabled') ? AppStatus.inaccessible : AppStatus.accessible, ]) diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx index 6c1b81a26d63c..8590fb3c820ef 100644 --- a/src/core/public/application/integration_tests/utils.tsx +++ b/src/core/public/application/integration_tests/utils.tsx @@ -33,7 +33,7 @@ export const createRenderer = (element: ReactElement | null): Renderer => { const dom: Dom = element && mount({element}); return () => - new Promise(async resolve => { + new Promise(async (resolve) => { if (dom) { await act(async () => { dom.update(); diff --git a/src/core/public/application/scoped_history.test.ts b/src/core/public/application/scoped_history.test.ts index a56cffef1e2f2..2b217e54228c2 100644 --- a/src/core/public/application/scoped_history.test.ts +++ b/src/core/public/application/scoped_history.test.ts @@ -217,7 +217,7 @@ describe('ScopedHistory', () => { gh.push('/app/wow'); const h = new ScopedHistory(gh, '/app/wow'); const listenPaths: string[] = []; - h.listen(l => listenPaths.push(l.pathname)); + h.listen((l) => listenPaths.push(l.pathname)); h.push('/first-page'); h.push('/second-page'); h.push('/third-page'); @@ -237,7 +237,7 @@ describe('ScopedHistory', () => { gh.push('/app/wow'); const h = new ScopedHistory(gh, '/app/wow'); const listenPaths: string[] = []; - const unlisten = h.listen(l => listenPaths.push(l.pathname)); + const unlisten = h.listen((l) => listenPaths.push(l.pathname)); h.push('/first-page'); unlisten(); h.push('/second-page'); @@ -252,7 +252,7 @@ describe('ScopedHistory', () => { gh.push('/app/wow'); const h = new ScopedHistory(gh, '/app/wow'); const listenPaths: string[] = []; - h.listen(l => listenPaths.push(l.pathname)); + h.listen((l) => listenPaths.push(l.pathname)); h.push('/first-page'); gh.push('/app/other'); gh.push('/second-page'); diff --git a/src/core/public/application/scoped_history.ts b/src/core/public/application/scoped_history.ts index 9fa8f0b7f8148..4392cf4eca8d4 100644 --- a/src/core/public/application/scoped_history.ts +++ b/src/core/public/application/scoped_history.ts @@ -197,7 +197,7 @@ export class ScopedHistory prompt?: boolean | string | TransitionPromptHook ): UnregisterCallback => { throw new Error( - `history.block is not supported. Please use the AppMountParams.onAppLeave API.` + `history.block is not supported. Please use the AppMountParameters.onAppLeave API.` ); }; @@ -324,7 +324,7 @@ export class ScopedHistory throw new Error(`Unrecognized history action: ${action}`); } - [...this.listeners].forEach(listener => { + [...this.listeners].forEach((listener) => { listener(this.stripBasePath(location), action); }); }); diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 786d11a5ced7f..8006ec846138f 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -235,7 +235,7 @@ export interface App extends AppBase { appRoute?: string; } -/** @internal */ +/** @public */ export interface LegacyApp extends AppBase { appUrl: string; subUrlBase?: string; @@ -243,6 +243,24 @@ export interface LegacyApp extends AppBase { disableSubUrlTracking?: boolean; } +/** + * Public information about a registered {@link App | application} + * + * @public + */ +export type PublicAppInfo = Omit & { + legacy: false; +}; + +/** + * Information about a registered {@link LegacyApp | legacy application} + * + * @public + */ +export type PublicLegacyAppInfo = Omit & { + legacy: true; +}; + /** * A mount function called when the user navigates to this app's route. * @@ -435,10 +453,10 @@ export interface AppMountParameters { * import ReactDOM from 'react-dom'; * import { BrowserRouter, Route } from 'react-router-dom'; * - * import { CoreStart, AppMountParams } from 'src/core/public'; + * import { CoreStart, AppMountParameters } from 'src/core/public'; * import { MyPluginDepsStart } from './plugin'; * - * export renderApp = ({ element, history, onAppLeave }: AppMountParams) => { + * export renderApp = ({ element, history, onAppLeave }: AppMountParameters) => { * const { renderApp, hasUnsavedChanges } = await import('./application'); * onAppLeave(actions => { * if(hasUnsavedChanges()) { @@ -649,6 +667,15 @@ export interface ApplicationStart { */ capabilities: RecursiveReadonly; + /** + * Observable emitting the list of currently registered apps and their associated status. + * + * @remarks + * Applications disabled by {@link Capabilities} will not be present in the map. Applications manually disabled from + * the client-side using an {@link AppUpdater | application updater} are present, with their status properly set as `inaccessible`. + */ + applications$: Observable>; + /** * Navigate to a given app * @@ -659,12 +686,41 @@ export interface ApplicationStart { */ navigateToApp(appId: string, options?: { path?: string; state?: any }): Promise; + /** + * Navigate to given url, which can either be an absolute url or a relative path, in a SPA friendly way when possible. + * + * If all these criteria are true for the given url: + * - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location + * - The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space) + * - The pathname segment after the basePath matches any known application route (eg. /app// or any application's `appRoute` configuration) + * + * Then a SPA navigation will be performed using `navigateToApp` using the corresponding application and path. + * Otherwise, fallback to a full page reload to navigate to the url using `window.location.assign` + * + * @example + * ```ts + * // current url: `https://kibana:8080/base-path/s/my-space/app/dashboard` + * + * // will call `application.navigateToApp('discover', { path: '/some-path?foo=bar'})` + * application.navigateToUrl('https://kibana:8080/base-path/s/my-space/app/discover/some-path?foo=bar') + * application.navigateToUrl('/base-path/s/my-space/app/discover/some-path?foo=bar') + * + * // will perform a full page reload using `window.location.assign` + * application.navigateToUrl('https://elsewhere:8080/base-path/s/my-space/app/discover/some-path') // origin does not match + * application.navigateToUrl('/app/discover/some-path') // does not include the current basePath + * application.navigateToUrl('/base-path/s/my-space/app/unknown-app/some-path') // unknown application + * ``` + * + * @param url - an absolute url, or a relative path, to navigate to. + */ + navigateToUrl(url: string): Promise; + /** * Returns an URL to a given app, including the global base path. * By default, the URL is relative (/basePath/app/my-app). * Use the `absolute` option to generate an absolute url (http://host:port/basePath/app/my-app) * - * Note that when generating absolute urls, the protocol, host and port are determined from the browser location. + * Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's location. * * @param appId * @param options.path - optional path inside application to deep link to @@ -677,7 +733,6 @@ export interface ApplicationStart { * plugin that registered this context. Deprecated, use {@link CoreSetup.getStartServices}. * * @deprecated - * @param pluginOpaqueId - The opaque ID of the plugin that is registering the context. * @param contextName - The key of {@link AppMountContext} this provider's return value should be attached to. * @param provider - A {@link IContextProvider} function */ @@ -693,18 +748,7 @@ export interface ApplicationStart { } /** @internal */ -export interface InternalApplicationStart - extends Pick< - ApplicationStart, - 'capabilities' | 'navigateToApp' | 'getUrlForApp' | 'currentAppId$' - > { - /** - * Apps available based on the current capabilities. - * Should be used to show navigation links and make routing decisions. - * Applications manually disabled from the client-side using {@link AppUpdater} - */ - applications$: Observable>; - +export interface InternalApplicationStart extends Omit { /** * Register a context provider for application mounting. Will only be available to applications that depend on the * plugin that registered this context. Deprecated, use {@link CoreSetup.getStartServices}. diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index 5d573d47bd420..229354a014103 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -37,14 +37,14 @@ describe('AppContainer', () => { }); const flushPromises = async () => { - await new Promise(async resolve => { + await new Promise(async (resolve) => { setImmediate(() => resolve()); }); }; const createResolver = (): [Promise, () => void] => { let resolve: () => void | undefined; - const promise = new Promise(r => { + const promise = new Promise((r) => { resolve = r; }); return [promise, resolve!]; diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index 4317ede547202..332c31c64b6ba 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -83,7 +83,7 @@ export const AppContainer: FunctionComponent = ({ appBasePath: mounter.appBasePath, history: createScopedHistory(appPath), element: elementRef.current!, - onAppLeave: handler => setAppLeaveHandler(appId, handler), + onAppLeave: (handler) => setAppLeaveHandler(appId, handler), })) || null; } catch (e) { // TODO: add error UI diff --git a/src/core/public/application/utils.test.ts b/src/core/public/application/utils.test.ts index 7ed0919f88c61..b41945aa43682 100644 --- a/src/core/public/application/utils.test.ts +++ b/src/core/public/application/utils.test.ts @@ -17,7 +17,17 @@ * under the License. */ -import { removeSlashes, appendAppPath } from './utils'; +import { of } from 'rxjs'; +import { LegacyApp, App, AppStatus, AppNavLinkStatus } from './types'; +import { BasePath } from '../http/base_path'; +import { + removeSlashes, + appendAppPath, + isLegacyApp, + relativeToAbsolute, + parseAppUrl, + getAppInfo, +} from './utils'; describe('removeSlashes', () => { it('only removes duplicates by default', () => { @@ -69,3 +79,438 @@ describe('appendAppPath', () => { expect(appendAppPath('/app/my-app', '/some-path#/hash')).toEqual('/app/my-app/some-path#/hash'); }); }); + +describe('isLegacyApp', () => { + it('returns true for legacy apps', () => { + expect( + isLegacyApp({ + id: 'legacy', + title: 'Legacy App', + appUrl: '/some-url', + legacy: true, + }) + ).toEqual(true); + }); + it('returns false for non-legacy apps', () => { + expect( + isLegacyApp({ + id: 'legacy', + title: 'Legacy App', + mount: () => () => undefined, + legacy: false, + }) + ).toEqual(false); + }); +}); + +describe('relativeToAbsolute', () => { + it('converts a relative path to an absolute url', () => { + const origin = window.location.origin; + expect(relativeToAbsolute('path')).toEqual(`${origin}/path`); + expect(relativeToAbsolute('/path#hash')).toEqual(`${origin}/path#hash`); + expect(relativeToAbsolute('/path?query=foo')).toEqual(`${origin}/path?query=foo`); + }); +}); + +describe('parseAppUrl', () => { + let apps: Map | LegacyApp>; + let basePath: BasePath; + + const getOrigin = () => 'https://kibana.local:8080'; + + const createApp = (props: Partial): App => { + const app: App = { + id: 'some-id', + title: 'some-title', + mount: () => () => undefined, + ...props, + legacy: false, + }; + apps.set(app.id, app); + return app; + }; + + const createLegacyApp = (props: Partial): LegacyApp => { + const app: LegacyApp = { + id: 'some-id', + title: 'some-title', + appUrl: '/my-url', + ...props, + legacy: true, + }; + apps.set(app.id, app); + return app; + }; + + beforeEach(() => { + apps = new Map(); + basePath = new BasePath('/base-path'); + + createApp({ + id: 'foo', + }); + createApp({ + id: 'bar', + appRoute: '/custom-bar', + }); + createLegacyApp({ + id: 'legacy', + appUrl: '/app/legacy', + }); + }); + + describe('with relative paths', () => { + it('parses the app id', () => { + expect(parseAppUrl('/base-path/app/foo', basePath, apps, getOrigin)).toEqual({ + app: 'foo', + path: undefined, + }); + expect(parseAppUrl('/base-path/custom-bar', basePath, apps, getOrigin)).toEqual({ + app: 'bar', + path: undefined, + }); + }); + it('parses the path', () => { + expect(parseAppUrl('/base-path/app/foo/some/path', basePath, apps, getOrigin)).toEqual({ + app: 'foo', + path: '/some/path', + }); + expect(parseAppUrl('/base-path/custom-bar/another/path/', basePath, apps, getOrigin)).toEqual( + { + app: 'bar', + path: '/another/path/', + } + ); + }); + it('includes query and hash in the path for default app route', () => { + expect(parseAppUrl('/base-path/app/foo#hash/bang', basePath, apps, getOrigin)).toEqual({ + app: 'foo', + path: '#hash/bang', + }); + expect(parseAppUrl('/base-path/app/foo?hello=dolly', basePath, apps, getOrigin)).toEqual({ + app: 'foo', + path: '?hello=dolly', + }); + expect(parseAppUrl('/base-path/app/foo/path?hello=dolly', basePath, apps, getOrigin)).toEqual( + { + app: 'foo', + path: '/path?hello=dolly', + } + ); + expect(parseAppUrl('/base-path/app/foo/path#hash/bang', basePath, apps, getOrigin)).toEqual({ + app: 'foo', + path: '/path#hash/bang', + }); + expect( + parseAppUrl('/base-path/app/foo/path#hash/bang?hello=dolly', basePath, apps, getOrigin) + ).toEqual({ + app: 'foo', + path: '/path#hash/bang?hello=dolly', + }); + }); + it('includes query and hash in the path for custom app route', () => { + expect(parseAppUrl('/base-path/custom-bar#hash/bang', basePath, apps, getOrigin)).toEqual({ + app: 'bar', + path: '#hash/bang', + }); + expect(parseAppUrl('/base-path/custom-bar?hello=dolly', basePath, apps, getOrigin)).toEqual({ + app: 'bar', + path: '?hello=dolly', + }); + expect( + parseAppUrl('/base-path/custom-bar/path?hello=dolly', basePath, apps, getOrigin) + ).toEqual({ + app: 'bar', + path: '/path?hello=dolly', + }); + expect( + parseAppUrl('/base-path/custom-bar/path#hash/bang', basePath, apps, getOrigin) + ).toEqual({ + app: 'bar', + path: '/path#hash/bang', + }); + expect( + parseAppUrl('/base-path/custom-bar/path#hash/bang?hello=dolly', basePath, apps, getOrigin) + ).toEqual({ + app: 'bar', + path: '/path#hash/bang?hello=dolly', + }); + }); + it('works with legacy apps', () => { + expect(parseAppUrl('/base-path/app/legacy', basePath, apps, getOrigin)).toEqual({ + app: 'legacy', + path: undefined, + }); + expect( + parseAppUrl('/base-path/app/legacy/path#hash?query=bar', basePath, apps, getOrigin) + ).toEqual({ + app: 'legacy', + path: '/path#hash?query=bar', + }); + }); + it('returns undefined when the app is not known', () => { + expect(parseAppUrl('/base-path/app/non-registered', basePath, apps, getOrigin)).toEqual( + undefined + ); + expect(parseAppUrl('/base-path/unknown-path', basePath, apps, getOrigin)).toEqual(undefined); + }); + }); + + describe('with absolute urls', () => { + it('parses the app id', () => { + expect( + parseAppUrl('https://kibana.local:8080/base-path/app/foo', basePath, apps, getOrigin) + ).toEqual({ + app: 'foo', + path: undefined, + }); + expect( + parseAppUrl('https://kibana.local:8080/base-path/custom-bar', basePath, apps, getOrigin) + ).toEqual({ + app: 'bar', + path: undefined, + }); + }); + it('parses the path', () => { + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/foo/some/path', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'foo', + path: '/some/path', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/custom-bar/another/path/', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'bar', + path: '/another/path/', + }); + }); + it('includes query and hash in the path for default app routes', () => { + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/foo#hash/bang', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'foo', + path: '#hash/bang', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/foo?hello=dolly', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'foo', + path: '?hello=dolly', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/foo/path?hello=dolly', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'foo', + path: '/path?hello=dolly', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/foo/path#hash/bang', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'foo', + path: '/path#hash/bang', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/foo/path#hash/bang?hello=dolly', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'foo', + path: '/path#hash/bang?hello=dolly', + }); + }); + it('includes query and hash in the path for custom app route', () => { + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/custom-bar#hash/bang', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'bar', + path: '#hash/bang', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/custom-bar?hello=dolly', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'bar', + path: '?hello=dolly', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/custom-bar/path?hello=dolly', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'bar', + path: '/path?hello=dolly', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/custom-bar/path#hash/bang', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'bar', + path: '/path#hash/bang', + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/custom-bar/path#hash/bang?hello=dolly', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'bar', + path: '/path#hash/bang?hello=dolly', + }); + }); + it('works with legacy apps', () => { + expect( + parseAppUrl('https://kibana.local:8080/base-path/app/legacy', basePath, apps, getOrigin) + ).toEqual({ + app: 'legacy', + path: undefined, + }); + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/legacy/path#hash?query=bar', + basePath, + apps, + getOrigin + ) + ).toEqual({ + app: 'legacy', + path: '/path#hash?query=bar', + }); + }); + it('returns undefined when the app is not known', () => { + expect( + parseAppUrl( + 'https://kibana.local:8080/base-path/app/non-registered', + basePath, + apps, + getOrigin + ) + ).toEqual(undefined); + expect( + parseAppUrl('https://kibana.local:8080/base-path/unknown-path', basePath, apps, getOrigin) + ).toEqual(undefined); + }); + it('returns undefined when origin does not match', () => { + expect( + parseAppUrl( + 'https://other-kibana.external:8080/base-path/app/foo', + basePath, + apps, + getOrigin + ) + ).toEqual(undefined); + expect( + parseAppUrl( + 'https://other-kibana.external:8080/base-path/custom-bar', + basePath, + apps, + getOrigin + ) + ).toEqual(undefined); + }); + }); +}); + +describe('getAppInfo', () => { + const createApp = (props: Partial = {}): App => ({ + mount: () => () => undefined, + updater$: of(() => undefined), + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: `/app/some-id`, + legacy: false, + ...props, + }); + + const createLegacyApp = (props: Partial = {}): LegacyApp => ({ + appUrl: '/my-app-url', + updater$: of(() => undefined), + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + legacy: true, + ...props, + }); + + it('converts an application and remove sensitive properties', () => { + const app = createApp(); + const info = getAppInfo(app); + + expect(info).toEqual({ + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: `/app/some-id`, + legacy: false, + }); + }); + + it('converts a legacy application and remove sensitive properties', () => { + const app = createLegacyApp(); + const info = getAppInfo(app); + + expect(info).toEqual({ + appUrl: '/my-app-url', + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + legacy: true, + }); + }); +}); diff --git a/src/core/public/application/utils.ts b/src/core/public/application/utils.ts index 048f195fe1223..1abd710548745 100644 --- a/src/core/public/application/utils.ts +++ b/src/core/public/application/utils.ts @@ -17,6 +17,14 @@ * under the License. */ +import { IBasePath } from '../http'; +import { App, LegacyApp, PublicAppInfo, PublicLegacyAppInfo } from './types'; + +export interface AppUrlInfo { + app: string; + path?: string; +} + /** * Utility to remove trailing, leading or duplicate slashes. * By default will only remove duplicates. @@ -52,3 +60,78 @@ export const appendAppPath = (appBasePath: string, path: string = '') => { leading: false, }); }; + +export function isLegacyApp(app: App | LegacyApp): app is LegacyApp { + return app.legacy === true; +} + +/** + * Converts a relative path to an absolute url. + * Implementation is based on a specified behavior of the browser to automatically convert + * a relative url to an absolute one when setting the `href` attribute of a `` html element. + * + * @example + * ```ts + * // current url: `https://kibana:8000/base-path/app/my-app` + * relativeToAbsolute('/base-path/app/another-app') => `https://kibana:8000/base-path/app/another-app` + * ``` + */ +export const relativeToAbsolute = (url: string): string => { + const a = document.createElement('a'); + a.setAttribute('href', url); + return a.href; +}; + +/** + * Parse given url and return the associated app id and path if any app matches. + * Input can either be: + * - a path containing the basePath, ie `/base-path/app/my-app/some-path` + * - an absolute url matching the `origin` of the kibana instance (as seen by the browser), + * i.e `https://kibana:8080/base-path/app/my-app/some-path` + */ +export const parseAppUrl = ( + url: string, + basePath: IBasePath, + apps: Map | LegacyApp>, + getOrigin: () => string = () => window.location.origin +): AppUrlInfo | undefined => { + url = removeBasePath(url, basePath, getOrigin()); + if (!url.startsWith('/')) { + return undefined; + } + + for (const app of apps.values()) { + const appPath = isLegacyApp(app) ? app.appUrl : app.appRoute || `/app/${app.id}`; + + if (url.startsWith(appPath)) { + const path = url.substr(appPath.length); + return { + app: app.id, + path: path.length ? path : undefined, + }; + } + } +}; + +const removeBasePath = (url: string, basePath: IBasePath, origin: string): string => { + if (url.startsWith(origin)) { + url = url.substring(origin.length); + } + return basePath.remove(url); +}; + +export function getAppInfo(app: App | LegacyApp): PublicAppInfo | PublicLegacyAppInfo { + if (isLegacyApp(app)) { + const { updater$, ...infos } = app; + return { + ...infos, + legacy: true, + }; + } else { + const { updater$, mount, ...infos } = app; + return { + ...infos, + legacy: false, + }; + } +} diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index b5cf900d9c39f..e39733cc10de7 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -21,7 +21,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import * as Rx from 'rxjs'; import { take, toArray } from 'rxjs/operators'; -import { App } from '../application'; +import { App, PublicAppInfo } from '../application'; import { applicationServiceMock } from '../application/application_service.mock'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; @@ -29,6 +29,7 @@ import { injectedMetadataServiceMock } from '../injected_metadata/injected_metad import { notificationServiceMock } from '../notifications/notifications_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { ChromeService } from './chrome_service'; +import { getAppInfo } from '../application/utils'; class FakeApp implements App { public title = `${this.id} App`; @@ -55,8 +56,8 @@ function defaultStartDeps(availableApps?: App[]) { }; if (availableApps) { - deps.application.applications$ = new Rx.BehaviorSubject>( - new Map(availableApps.map(app => [app.id, app])) + deps.application.applications$ = new Rx.BehaviorSubject>( + new Map(availableApps.map((app) => [app.id, getAppInfo(app) as PublicAppInfo])) ); } @@ -133,10 +134,7 @@ describe('start', () => { describe('brand', () => { it('updates/emits the brand as it changes', async () => { const { chrome, service } = await start(); - const promise = chrome - .getBrand$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getBrand$().pipe(toArray()).toPromise(); chrome.setBrand({ logo: 'big logo', @@ -166,10 +164,7 @@ describe('start', () => { describe('visibility', () => { it('emits false when no application is mounted', async () => { const { chrome, service } = await start(); - const promise = chrome - .getIsVisible$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getIsVisible$().pipe(toArray()).toPromise(); chrome.setIsVisible(true); chrome.setIsVisible(false); @@ -192,10 +187,7 @@ describe('start', () => { const { navigateToApp } = startDeps.application; const { chrome, service } = await start({ startDeps }); - const promise = chrome - .getIsVisible$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getIsVisible$().pipe(toArray()).toPromise(); await navigateToApp('alpha'); @@ -222,13 +214,10 @@ describe('start', () => { ]); const { applications$, navigateToApp } = startDeps.application; const { chrome, service } = await start({ startDeps }); - const promise = chrome - .getIsVisible$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getIsVisible$().pipe(toArray()).toPromise(); const availableApps = await applications$.pipe(take(1)).toPromise(); - [...availableApps.keys()].forEach(appId => navigateToApp(appId)); + [...availableApps.keys()].forEach((appId) => navigateToApp(appId)); service.stop(); await expect(promise).resolves.toMatchInlineSnapshot(` @@ -245,10 +234,7 @@ describe('start', () => { const startDeps = defaultStartDeps([new FakeApp('alpha', true)]); const { navigateToApp } = startDeps.application; const { chrome, service } = await start({ startDeps }); - const promise = chrome - .getIsVisible$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getIsVisible$().pipe(toArray()).toPromise(); await navigateToApp('alpha'); chrome.setIsVisible(true); @@ -267,10 +253,7 @@ describe('start', () => { describe('application classes', () => { it('updates/emits the application classes', async () => { const { chrome, service } = await start(); - const promise = chrome - .getApplicationClasses$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getApplicationClasses$().pipe(toArray()).toPromise(); chrome.addApplicationClass('foo'); chrome.addApplicationClass('foo'); @@ -318,10 +301,7 @@ describe('start', () => { describe('badge', () => { it('updates/emits the current badge', async () => { const { chrome, service } = await start(); - const promise = chrome - .getBadge$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getBadge$().pipe(toArray()).toPromise(); chrome.setBadge({ text: 'foo', tooltip: `foo's tooltip` }); chrome.setBadge({ text: 'bar', tooltip: `bar's tooltip` }); @@ -348,10 +328,7 @@ describe('start', () => { describe('breadcrumbs', () => { it('updates/emits the current set of breadcrumbs', async () => { const { chrome, service } = await start(); - const promise = chrome - .getBreadcrumbs$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getBreadcrumbs$().pipe(toArray()).toPromise(); chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]); chrome.setBreadcrumbs([{ text: 'foo' }]); @@ -389,10 +366,7 @@ describe('start', () => { describe('help extension', () => { it('updates/emits the current help extension', async () => { const { chrome, service } = await start(); - const promise = chrome - .getHelpExtension$() - .pipe(toArray()) - .toPromise(); + const promise = chrome.getHelpExtension$().pipe(toArray()).toPromise(); chrome.setHelpExtension({ appName: 'App name', content: () => () => undefined }); chrome.setHelpExtension(undefined); diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index a921e514050b2..67cd43f0647e4 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -36,7 +36,7 @@ import { ChromeDocTitle, DocTitleService } from './doc_title'; import { ChromeNavControls, NavControlsService } from './nav_controls'; import { ChromeNavLinks, NavLinksService } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; -import { Header, LoadingIndicator } from './ui'; +import { Header } from './ui'; import { NavType } from './ui/header'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; @@ -117,9 +117,9 @@ export class ChromeService { // in the sense that the chrome UI should not be displayed until a non-chromeless app is mounting or mounted of(true), application.currentAppId$.pipe( - flatMap(appId => + flatMap((appId) => application.applications$.pipe( - map(applications => { + map((applications) => { return !!appId && applications.has(appId) && !!applications.get(appId)!.chromeless; }) ) @@ -214,31 +214,29 @@ export class ChromeService { docTitle, getHeaderComponent: () => ( - - -
- +
), setAppTitle: (appTitle: string) => appTitle$.next(appTitle), @@ -260,7 +258,7 @@ export class ChromeService { getApplicationClasses$: () => applicationClasses$.pipe( - map(set => [...set]), + map((set) => [...set]), takeUntil(this.stop$) ), diff --git a/src/core/public/chrome/doc_title/doc_title_service.ts b/src/core/public/chrome/doc_title/doc_title_service.ts index 9453abe54de66..c6e9ec7a40b77 100644 --- a/src/core/public/chrome/doc_title/doc_title_service.ts +++ b/src/core/public/chrome/doc_title/doc_title_service.ts @@ -86,7 +86,7 @@ export class DocTitleService { this.applyTitle(defaultTitle); }, __legacy: { - setBaseTitle: baseTitle => { + setBaseTitle: (baseTitle) => { this.baseTitle = baseTitle; }, }, diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.test.ts b/src/core/public/chrome/nav_controls/nav_controls_service.test.ts index c8f168bbcb2f7..ac556c1d1cc5d 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.test.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.test.ts @@ -30,12 +30,7 @@ describe('RecentlyAccessed#start()', () => { const navControls = getStart(); const nc = { mount: jest.fn() }; navControls.registerLeft(nc); - expect( - await navControls - .getLeft$() - .pipe(take(1)) - .toPromise() - ).toEqual([nc]); + expect(await navControls.getLeft$().pipe(take(1)).toPromise()).toEqual([nc]); }); it('sorts controls by order property', async () => { @@ -46,12 +41,7 @@ describe('RecentlyAccessed#start()', () => { navControls.registerLeft(nc1); navControls.registerLeft(nc2); navControls.registerLeft(nc3); - expect( - await navControls - .getLeft$() - .pipe(take(1)) - .toPromise() - ).toEqual([nc2, nc1, nc3]); + expect(await navControls.getLeft$().pipe(take(1)).toPromise()).toEqual([nc2, nc1, nc3]); }); }); @@ -60,12 +50,7 @@ describe('RecentlyAccessed#start()', () => { const navControls = getStart(); const nc = { mount: jest.fn() }; navControls.registerRight(nc); - expect( - await navControls - .getRight$() - .pipe(take(1)) - .toPromise() - ).toEqual([nc]); + expect(await navControls.getRight$().pipe(take(1)).toPromise()).toEqual([nc]); }); it('sorts controls by order property', async () => { @@ -76,12 +61,7 @@ describe('RecentlyAccessed#start()', () => { navControls.registerRight(nc1); navControls.registerRight(nc2); navControls.registerRight(nc3); - expect( - await navControls - .getRight$() - .pipe(take(1)) - .toPromise() - ).toEqual([nc2, nc1, nc3]); + expect(await navControls.getRight$().pipe(take(1)).toPromise()).toEqual([nc2, nc1, nc3]); }); }); }); diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts index 7f9c75595a4ce..167948e01cb36 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -74,12 +74,12 @@ export class NavControlsService { getLeft$: () => navControlsLeft$.pipe( - map(controls => sortBy([...controls.values()], 'order')), + map((controls) => sortBy([...controls.values()], 'order')), takeUntil(this.stop$) ), getRight$: () => navControlsRight$.pipe( - map(controls => sortBy([...controls.values()], 'order')), + map((controls) => sortBy([...controls.values()], 'order')), takeUntil(this.stop$) ), }; diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index fb2972735c2b7..55b5c80526bab 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -62,7 +62,7 @@ export interface ChromeNavLink { /** * A EUI iconType that will be used for the app's icon. This icon - * takes precendence over the `icon` property. + * takes precedence over the `icon` property. */ readonly euiIconType?: string; @@ -72,6 +72,14 @@ export interface ChromeNavLink { */ readonly icon?: string; + /** + * Settled state between `url`, `baseUrl`, and `active` + * + * @internalRemarks + * This should be required once legacy apps are gone. + */ + readonly href?: string; + /** LEGACY FIELDS */ /** @@ -144,7 +152,7 @@ export interface ChromeNavLink { /** @public */ export type ChromeNavLinkUpdateableFields = Partial< - Pick + Pick >; export class NavLinkWrapper { @@ -162,7 +170,7 @@ export class NavLinkWrapper { public update(newProps: ChromeNavLinkUpdateableFields) { // Enforce limited properties at runtime for JS code - newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase']); + newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase', 'href']); return new NavLinkWrapper({ ...this.properties, ...newProps }); } } diff --git a/src/core/public/chrome/nav_links/nav_links_service.test.ts b/src/core/public/chrome/nav_links/nav_links_service.test.ts index 3d9a4bfdb6a56..8f610e238b0fd 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.test.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.test.ts @@ -90,7 +90,7 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.map(l => l.id)) + map((links) => links.map((l) => l.id)) ) .toPromise() ).not.toContain('chromelessApp'); @@ -102,16 +102,16 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.map(l => l.id)) + map((links) => links.map((l) => l.id)) ) .toPromise() ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); }); it('emits multiple values', async () => { - const navLinkIds$ = start.getNavLinks$().pipe(map(links => links.map(l => l.id))); + const navLinkIds$ = start.getNavLinks$().pipe(map((links) => links.map((l) => l.id))); const emittedLinks: string[][] = []; - navLinkIds$.subscribe(r => emittedLinks.push(r)); + navLinkIds$.subscribe((r) => emittedLinks.push(r)); start.update('legacyApp1', { active: true }); service.stop(); @@ -122,10 +122,7 @@ describe('NavLinksService', () => { }); it('completes when service is stopped', async () => { - const last$ = start - .getNavLinks$() - .pipe(takeLast(1)) - .toPromise(); + const last$ = start.getNavLinks$().pipe(takeLast(1)).toPromise(); service.stop(); await expect(last$).resolves.toBeInstanceOf(Array); }); @@ -143,7 +140,7 @@ describe('NavLinksService', () => { describe('#getAll()', () => { it('returns a sorted array of navlinks', () => { - expect(start.getAll().map(l => l.id)).toEqual([ + expect(start.getAll().map((l) => l.id)).toEqual([ 'app2', 'legacyApp2', 'app1', @@ -171,7 +168,7 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.map(l => l.id)) + map((links) => links.map((l) => l.id)) ) .toPromise() ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); @@ -184,7 +181,7 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.map(l => l.id)) + map((links) => links.map((l) => l.id)) ) .toPromise() ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); @@ -197,7 +194,7 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.map(l => l.id)) + map((links) => links.map((l) => l.id)) ) .toPromise() ).toEqual(['legacyApp1']); @@ -211,7 +208,7 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.map(l => l.id)) + map((links) => links.map((l) => l.id)) ) .toPromise() ).toEqual(['legacyApp2']); @@ -236,7 +233,7 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.filter(l => l.hidden).map(l => l.id)) + map((links) => links.filter((l) => l.hidden).map((l) => l.id)) ) .toPromise(); expect(hiddenLinkIds).toEqual(['legacyApp1']); @@ -253,7 +250,7 @@ describe('NavLinksService', () => { .getNavLinks$() .pipe( take(1), - map(links => links.filter(l => l.hidden).map(l => l.id)) + map((links) => links.filter((l) => l.hidden).map((l) => l.id)) ) .toPromise(); expect(hiddenLinkIds).toEqual(['legacyApp1']); @@ -262,21 +259,15 @@ describe('NavLinksService', () => { describe('#enableForcedAppSwitcherNavigation()', () => { it('flips #getForceAppSwitcherNavigation$()', async () => { - await expect( - start - .getForceAppSwitcherNavigation$() - .pipe(take(1)) - .toPromise() - ).resolves.toBe(false); + await expect(start.getForceAppSwitcherNavigation$().pipe(take(1)).toPromise()).resolves.toBe( + false + ); start.enableForcedAppSwitcherNavigation(); - await expect( - start - .getForceAppSwitcherNavigation$() - .pipe(take(1)) - .toPromise() - ).resolves.toBe(true); + await expect(start.getForceAppSwitcherNavigation$().pipe(take(1)).toPromise()).resolves.toBe( + true + ); }); }); }); diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index fec9322b0d77d..3095bb86b72e2 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -108,7 +108,7 @@ export class NavLinksService { public start({ application, http }: StartDeps): ChromeNavLinks { const appLinks$ = application.applications$.pipe( - map(apps => { + map((apps) => { return new Map( [...apps] .filter(([, app]) => !app.chromeless) @@ -129,7 +129,7 @@ export class NavLinksService { return linkUpdaters.reduce((links, updater) => updater(links), appLinks); }) ) - .subscribe(navlinks => { + .subscribe((navlinks) => { navLinks$.next(navlinks); }); @@ -158,7 +158,7 @@ export class NavLinksService { return; } - const updater: LinksUpdater = navLinks => + const updater: LinksUpdater = (navLinks) => new Map([...navLinks.entries()].filter(([linkId]) => linkId === id)); linkUpdaters$.next([...linkUpdaters$.value, updater]); @@ -169,7 +169,7 @@ export class NavLinksService { return; } - const updater: LinksUpdater = navLinks => + const updater: LinksUpdater = (navLinks) => new Map( [...navLinks.entries()].map(([linkId, link]) => { return [linkId, link.id === id ? link.update(values) : link] as [ @@ -200,7 +200,7 @@ export class NavLinksService { function sortNavLinks(navLinks: ReadonlyMap) { return sortBy( - [...navLinks.values()].map(link => link.properties), + [...navLinks.values()].map((link) => link.properties), 'order' ); } diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts index 4c319873af804..ba04dbed49cd4 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.test.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -17,15 +17,12 @@ * under the License. */ -import { App, AppMount, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; import { toNavLink } from './to_nav_link'; import { httpServiceMock } from '../../mocks'; -function mount() {} - -const app = (props: Partial = {}): App => ({ - mount: (mount as unknown) as AppMount, +const app = (props: Partial = {}): PublicAppInfo => ({ id: 'some-id', title: 'some-title', status: AppStatus.accessible, @@ -35,7 +32,7 @@ const app = (props: Partial = {}): App => ({ ...props, }); -const legacyApp = (props: Partial = {}): LegacyApp => ({ +const legacyApp = (props: Partial = {}): PublicLegacyAppInfo => ({ appUrl: '/my-app-url', id: 'some-id', title: 'some-title', diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts index f79b1df77f8e1..2dedbfd5f36ac 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.ts @@ -17,14 +17,22 @@ * under the License. */ -import { App, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; import { IBasePath } from '../../http'; import { NavLinkWrapper } from './nav_link'; import { appendAppPath } from '../../application/utils'; -export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWrapper { +export function toNavLink( + app: PublicAppInfo | PublicLegacyAppInfo, + basePath: IBasePath +): NavLinkWrapper { const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default; - const baseUrl = isLegacyApp(app) ? basePath.prepend(app.appUrl) : basePath.prepend(app.appRoute!); + const relativeBaseUrl = isLegacyApp(app) + ? basePath.prepend(app.appUrl) + : basePath.prepend(app.appRoute!); + const url = relativeToAbsolute(appendAppPath(relativeBaseUrl, app.defaultPath)); + const baseUrl = relativeToAbsolute(relativeBaseUrl); + return new NavLinkWrapper({ ...app, hidden: useAppStatus @@ -32,22 +40,30 @@ export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWra : app.navLinkStatus === AppNavLinkStatus.hidden, disabled: useAppStatus ? false : app.navLinkStatus === AppNavLinkStatus.disabled, legacy: isLegacyApp(app), - baseUrl: relativeToAbsolute(baseUrl), + baseUrl, ...(isLegacyApp(app) ? {} : { - url: relativeToAbsolute(appendAppPath(baseUrl, app.defaultPath)), + href: url, + url, }), }); } -function relativeToAbsolute(url: string) { - // convert all link urls to absolute urls +/** + * @param {string} url - a relative or root relative url. If a relative path is given then the + * absolute url returned will depend on the current page where this function is called from. For example + * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get + * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that + * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". + * @return {string} the relative url transformed into an absolute url + */ +export function relativeToAbsolute(url: string) { const a = document.createElement('a'); a.setAttribute('href', url); return a.href; } -function isLegacyApp(app: App | LegacyApp): app is LegacyApp { +function isLegacyApp(app: PublicAppInfo | PublicLegacyAppInfo): app is PublicLegacyAppInfo { return app.legacy === true; } diff --git a/src/core/public/chrome/recently_accessed/persisted_log.ts b/src/core/public/chrome/recently_accessed/persisted_log.ts index 421f553f6a315..ca94e0bcddfaf 100644 --- a/src/core/public/chrome/recently_accessed/persisted_log.ts +++ b/src/core/public/chrome/recently_accessed/persisted_log.ts @@ -57,7 +57,7 @@ export class PersistedLog { const nextItems = [ val, // remove any duplicate items - ...[...this.items$.value].filter(item => !this.isEqual(item, val)), + ...[...this.items$.value].filter((item) => !this.isEqual(item, val)), ].slice(0, this.maxLength); // truncate // Persist the stack to storage @@ -73,7 +73,7 @@ export class PersistedLog { } public get$() { - return this.items$.pipe(map(items => cloneDeep(items))); + return this.items$.pipe(map((items) => cloneDeep(items))); } private loadItems() { diff --git a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts index 27dbc288d18cb..86c7f3a1ef765 100644 --- a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts +++ b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts @@ -76,7 +76,7 @@ export interface ChromeRecentlyAccessed { * * @param link a relative URL to the resource (not including the {@link HttpStart.basePath | `http.basePath`}) * @param label the label to display in the UI - * @param id a unique string used to de-duplicate the recently accessed llist. + * @param id a unique string used to de-duplicate the recently accessed list. */ add(link: string, label: string, id: string): void; diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 866ea5f45d986..f5b17f8d214e9 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -2,140 +2,295 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
+`; + +exports[`Header renders 2`] = ` +
+ +
+
+
+ +
+ +
+ +
+ +
+ + + +
+
+ +
+ + + + + + + + + + +
+ +
+ + + + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + repositionOnScroll={true} + > + +
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+ + + + +
+
+`; + +exports[`Header renders 3`] = ` +
+ +
+
+
+ +
+ +
+ +
+ +
+ + + +
+
+ +
+ + + + +
+ + + + +
+ + +
+ + + + + + + + + + +
+ +
+ + + + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + repositionOnScroll={true} + > + +
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + +
+ +
+
+
+ + +
+ } + onActivation={[Function]} + onDeactivation={[Function]} + persistentFocus={false} + > + + +
+ } + onActivation={[Function]} + onDeactivation={[Function]} + persistentFocus={false} + /> + + +
+
+ +
+ + + + + +
+
+`; + +exports[`Header renders 4`] = ` +
+ +
+
+
+ +
+ +
+ +
+ + +
+ + + +
+
+
+ +
+ + + + +
+ + + + +
+ + +
+ + + + + + + + + + +
+ +
+ + + + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + repositionOnScroll={true} + > + +
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + + +
+
+`; diff --git a/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap index d089019915686..fdaa17c279a10 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap @@ -5,6 +5,7 @@ exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 1`] = aria-current="page" className="euiBreadcrumb euiBreadcrumb--last" data-test-subj="breadcrumb first last" + title="First" > First @@ -39,6 +40,7 @@ Array [ aria-current="page" className="euiBreadcrumb euiBreadcrumb--last" data-test-subj="breadcrumb last" + title="Second" > Second , diff --git a/src/core/public/chrome/ui/header/_index.scss b/src/core/public/chrome/ui/header/_index.scss index 1b0438d748ff0..5c5e7f18b60a4 100644 --- a/src/core/public/chrome/ui/header/_index.scss +++ b/src/core/public/chrome/ui/header/_index.scss @@ -1,5 +1,3 @@ -@import './collapsible_nav'; - // TODO #64541 // Delete this block .chrHeaderWrapper:not(.headerWrapper) { diff --git a/src/core/public/chrome/ui/header/_collapsible_nav.scss b/src/core/public/chrome/ui/header/collapsible_nav.scss similarity index 100% rename from src/core/public/chrome/ui/header/_collapsible_nav.scss rename to src/core/public/chrome/ui/header/collapsible_nav.scss diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index 527f0df598c7c..5a734d55445a2 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -19,11 +19,13 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; +import { BehaviorSubject } from 'rxjs'; import sinon from 'sinon'; -import { CollapsibleNav } from './collapsible_nav'; -import { DEFAULT_APP_CATEGORIES } from '../../..'; import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; -import { NavLink, RecentNavLink } from './nav_link'; +import { ChromeNavLink, DEFAULT_APP_CATEGORIES } from '../../..'; +import { httpServiceMock } from '../../../http/http_service.mock'; +import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; +import { CollapsibleNav } from './collapsible_nav'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', @@ -31,40 +33,42 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES; -function mockLink({ label = 'discover', category, onClick }: Partial) { +function mockLink({ title = 'discover', category }: Partial) { return { - key: label, - label, - href: label, - isActive: true, - onClick: onClick || (() => {}), + title, category, - 'data-test-subj': label, + id: title, + href: title, + baseUrl: '/', + legacy: false, + isActive: true, + 'data-test-subj': title, }; } -function mockRecentNavLink({ label = 'recent', onClick }: Partial) { +function mockRecentNavLink({ label = 'recent' }: Partial) { return { - href: label, label, - title: label, - 'aria-label': label, - onClick, + link: label, + id: label, }; } function mockProps() { return { - id: 'collapsible-nav', - homeHref: '/', + appId$: new BehaviorSubject('test'), + basePath: httpServiceMock.createSetupContract({ basePath: '/test' }).basePath, + id: 'collapsibe-nav', isLocked: false, isOpen: false, - navLinks: [], - recentNavLinks: [], + homeHref: '/', + legacyMode: false, + navLinks$: new BehaviorSubject([]), + recentlyAccessed$: new BehaviorSubject([]), storage: new StubBrowserStorage(), - onIsOpenUpdate: () => {}, onIsLockedUpdate: () => {}, - navigateToApp: () => {}, + closeNav: () => {}, + navigateToApp: () => Promise.resolve(), }; } @@ -103,14 +107,14 @@ describe('CollapsibleNav', () => { it('renders links grouped by category', () => { // just a test of category functionality, categories are not accurate const navLinks = [ - mockLink({ label: 'discover', category: kibana }), - mockLink({ label: 'siem', category: security }), - mockLink({ label: 'metrics', category: observability }), - mockLink({ label: 'monitoring', category: management }), - mockLink({ label: 'visualize', category: kibana }), - mockLink({ label: 'dashboard', category: kibana }), - mockLink({ label: 'canvas' }), // links should be able to be rendered top level as well - mockLink({ label: 'logs', category: observability }), + mockLink({ title: 'discover', category: kibana }), + mockLink({ title: 'siem', category: security }), + mockLink({ title: 'metrics', category: observability }), + mockLink({ title: 'monitoring', category: management }), + mockLink({ title: 'visualize', category: kibana }), + mockLink({ title: 'dashboard', category: kibana }), + mockLink({ title: 'canvas' }), // links should be able to be rendered top level as well + mockLink({ title: 'logs', category: observability }), ]; const recentNavLinks = [ mockRecentNavLink({ label: 'recent 1' }), @@ -120,8 +124,8 @@ describe('CollapsibleNav', () => { ); expect(component).toMatchSnapshot(); @@ -134,8 +138,8 @@ describe('CollapsibleNav', () => { ); expectShownNavLinksCount(component, 3); @@ -149,32 +153,34 @@ describe('CollapsibleNav', () => { }); it('closes the nav after clicking a link', () => { - const onClick = sinon.spy(); - const onIsOpenUpdate = sinon.spy(); - const navLinks = [mockLink({ category: kibana, onClick })]; - const recentNavLinks = [mockRecentNavLink({ onClick })]; + const onClose = sinon.spy(); + const navLinks = [mockLink({ category: kibana }), mockLink({ title: 'categoryless' })]; + const recentNavLinks = [mockRecentNavLink({})]; const component = mount( ); component.setProps({ - onIsOpenUpdate: (isOpen: boolean) => { - component.setProps({ isOpen }); - onIsOpenUpdate(); + closeNav: () => { + component.setProps({ isOpen: false }); + onClose(); }, }); component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); - expect(onClick.callCount).toEqual(1); - expect(onIsOpenUpdate.callCount).toEqual(1); + expect(onClose.callCount).toEqual(1); expectNavIsClosed(component); component.setProps({ isOpen: true }); component.find('[data-test-subj="collapsibleNavGroup-kibana"] a').simulate('click'); - expect(onClick.callCount).toEqual(2); - expect(onIsOpenUpdate.callCount).toEqual(2); + expect(onClose.callCount).toEqual(2); + expectNavIsClosed(component); + component.setProps({ isOpen: true }); + component.find('[data-test-subj="collapsibleNavGroup-noCategory"] a').simulate('click'); + expect(onClose.callCount).toEqual(3); + expectNavIsClosed(component); }); }); diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 60463d8dccc9b..9494e22920de8 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -17,6 +17,7 @@ * under the License. */ +import './collapsible_nav.scss'; import { EuiCollapsibleNav, EuiCollapsibleNavGroup, @@ -30,11 +31,16 @@ import { import { i18n } from '@kbn/i18n'; import { groupBy, sortBy } from 'lodash'; import React, { useRef } from 'react'; +import { useObservable } from 'react-use'; +import * as Rx from 'rxjs'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../..'; import { AppCategory } from '../../../../types'; +import { InternalApplicationStart } from '../../../application/types'; +import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; -import { NavLink, RecentNavLink } from './nav_link'; +import { createEuiListItem, createRecentNavLink } from './nav_link'; -function getAllCategories(allCategorizedLinks: Record) { +function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; for (const [key, value] of Object.entries(allCategorizedLinks)) { @@ -45,12 +51,12 @@ function getAllCategories(allCategorizedLinks: Record) { } function getOrderedCategories( - mainCategories: Record, + mainCategories: Record, categoryDictionary: ReturnType ) { return sortBy( Object.keys(mainCategories), - categoryName => categoryDictionary[categoryName]?.order + (categoryName) => categoryDictionary[categoryName]?.order ); } @@ -69,35 +75,53 @@ function setIsCategoryOpen(id: string, isOpen: boolean, storage: Storage) { } interface Props { + appId$: InternalApplicationStart['currentAppId$']; + basePath: HttpStart['basePath']; + id: string; isLocked: boolean; isOpen: boolean; - navLinks: NavLink[]; - recentNavLinks: RecentNavLink[]; homeHref: string; - id: string; + legacyMode: boolean; + navLinks$: Rx.Observable; + recentlyAccessed$: Rx.Observable; storage?: Storage; onIsLockedUpdate: OnIsLockedUpdate; - onIsOpenUpdate: (isOpen?: boolean) => void; - navigateToApp: (appId: string) => void; + closeNav: () => void; + navigateToApp: InternalApplicationStart['navigateToApp']; } export function CollapsibleNav({ + basePath, + id, isLocked, isOpen, - navLinks, - recentNavLinks, - onIsLockedUpdate, - onIsOpenUpdate, homeHref, - id, - navigateToApp, + legacyMode, storage = window.localStorage, + onIsLockedUpdate, + closeNav, + navigateToApp, + ...observables }: Props) { + const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); + const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); + const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); - const groupedNavLinks = groupBy(navLinks, link => link?.category?.id); + const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); + const readyForEUI = (link: ChromeNavLink, needsIcon: boolean = false) => { + return createEuiListItem({ + link, + legacyMode, + appId, + dataTestSubj: 'collapsibleNavAppLink', + navigateToApp, + onClick: closeNav, + ...(needsIcon && { basePath }), + }); + }; return ( {/* Pinned items */} @@ -127,7 +151,7 @@ export function CollapsibleNav({ iconType: 'home', href: homeHref, onClick: (event: React.MouseEvent) => { - onIsOpenUpdate(false); + closeNav(); if ( event.isDefaultPrevented() || event.altKey || @@ -156,24 +180,25 @@ export function CollapsibleNav({ title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })} isCollapsible={true} initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)} - onToggle={isCategoryOpen => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} + onToggle={(isCategoryOpen) => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} data-test-subj="collapsibleNavGroup-recentlyViewed" > - {recentNavLinks.length > 0 ? ( + {recentlyAccessed.length > 0 ? ( {}, ...link }) => ({ - 'data-test-subj': 'collapsibleNavAppLink--recent', - onClick: (e: React.MouseEvent) => { - onIsOpenUpdate(false); - onClick(e); - }, - ...link, - }))} + listItems={recentlyAccessed.map((link) => { + // TODO #64541 + // Can remove icon from recent links completely + const { iconType, ...hydratedLink } = createRecentNavLink(link, navLinks, basePath); + + return { + ...hydratedLink, + 'data-test-subj': 'collapsibleNavAppLink--recent', + onClick: closeNav, + }; + })} maxWidth="none" color="subdued" gutterSize="none" @@ -195,21 +220,8 @@ export function CollapsibleNav({ {/* Kibana, Observability, Security, and Management sections */} - {orderedCategories.map((categoryName, i) => { + {orderedCategories.map((categoryName) => { const category = categoryDictionary[categoryName]!; - const links = allCategorizedLinks[categoryName].map( - ({ label, href, isActive, isDisabled, onClick }) => ({ - label, - href, - isActive, - isDisabled, - 'data-test-subj': 'collapsibleNavAppLink', - onClick: (e: React.MouseEvent) => { - onIsOpenUpdate(false); - onClick(e); - }, - }) - ); return ( setIsCategoryOpen(category.id, isCategoryOpen, storage)} + onToggle={(isCategoryOpen) => setIsCategoryOpen(category.id, isCategoryOpen, storage)} data-test-subj={`collapsibleNavGroup-${category.id}`} > readyForEUI(link))} maxWidth="none" color="subdued" gutterSize="none" @@ -237,23 +249,10 @@ export function CollapsibleNav({ })} {/* Things with no category (largely for custom plugins) */} - {unknowns.map(({ label, href, icon, isActive, isDisabled, onClick }, i) => ( - + {unknowns.map((link, i) => ( + - ) => { - onIsOpenUpdate(false); - onClick(e); - }} - /> + ))} diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx new file mode 100644 index 0000000000000..13e1f6f086ae2 --- /dev/null +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { BehaviorSubject } from 'rxjs'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { NavType } from '.'; +import { httpServiceMock } from '../../../http/http_service.mock'; +import { applicationServiceMock } from '../../../mocks'; +import { Header } from './header'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; + +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => 'mockId', +})); + +function mockProps() { + const http = httpServiceMock.createSetupContract({ basePath: '/test' }); + const application = applicationServiceMock.createInternalStartContract(); + + return { + application, + kibanaVersion: '1.0.0', + appTitle$: new BehaviorSubject('test'), + badge$: new BehaviorSubject(undefined), + breadcrumbs$: new BehaviorSubject([]), + homeHref: '/', + isVisible$: new BehaviorSubject(true), + kibanaDocLink: '/docs', + navLinks$: new BehaviorSubject([]), + recentlyAccessed$: new BehaviorSubject([]), + forceAppSwitcherNavigation$: new BehaviorSubject(false), + helpExtension$: new BehaviorSubject(undefined), + helpSupportUrl$: new BehaviorSubject(''), + legacyMode: false, + navControlsLeft$: new BehaviorSubject([]), + navControlsRight$: new BehaviorSubject([]), + basePath: http.basePath, + isLocked$: new BehaviorSubject(false), + navType$: new BehaviorSubject('modern' as NavType), + loadingCount$: new BehaviorSubject(0), + onIsLockedUpdate: () => {}, + }; +} + +describe('Header', () => { + beforeAll(() => { + Object.defineProperty(window, 'localStorage', { + value: new StubBrowserStorage(), + }); + }); + + it('renders', () => { + const isVisible$ = new BehaviorSubject(false); + const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }]); + const isLocked$ = new BehaviorSubject(false); + const navType$ = new BehaviorSubject('modern' as NavType); + const navLinks$ = new BehaviorSubject([ + { id: 'kibana', title: 'kibana', baseUrl: '', legacy: false }, + ]); + const recentlyAccessed$ = new BehaviorSubject([ + { link: '', label: 'dashboard', id: 'dashboard' }, + ]); + const component = mountWithIntl( +
+ ); + expect(component).toMatchSnapshot(); + + act(() => isVisible$.next(true)); + component.update(); + expect(component).toMatchSnapshot(); + + act(() => isLocked$.next(true)); + component.update(); + expect(component).toMatchSnapshot(); + + act(() => navType$.next('legacy' as NavType)); + component.update(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index aaeb330dfe14b..d24b342e0386b 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -28,9 +28,11 @@ import { htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { Component, createRef } from 'react'; import classnames from 'classnames'; -import * as Rx from 'rxjs'; +import React, { createRef, useState } from 'react'; +import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; +import { LoadingIndicator } from '../'; import { ChromeBadge, ChromeBreadcrumb, @@ -41,192 +43,101 @@ import { import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { HeaderBadge } from './header_badge'; import { NavType, OnIsLockedUpdate } from './'; +import { CollapsibleNav } from './collapsible_nav'; +import { HeaderBadge } from './header_badge'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; import { HeaderHelpMenu } from './header_help_menu'; -import { HeaderNavControls } from './header_nav_controls'; -import { createNavLink, createRecentNavLink } from './nav_link'; import { HeaderLogo } from './header_logo'; +import { HeaderNavControls } from './header_nav_controls'; import { NavDrawer } from './nav_drawer'; -import { CollapsibleNav } from './collapsible_nav'; export interface HeaderProps { kibanaVersion: string; application: InternalApplicationStart; - appTitle$: Rx.Observable; - badge$: Rx.Observable; - breadcrumbs$: Rx.Observable; + appTitle$: Observable; + badge$: Observable; + breadcrumbs$: Observable; homeHref: string; - isVisible$: Rx.Observable; + isVisible$: Observable; kibanaDocLink: string; - navLinks$: Rx.Observable; - recentlyAccessed$: Rx.Observable; - forceAppSwitcherNavigation$: Rx.Observable; - helpExtension$: Rx.Observable; - helpSupportUrl$: Rx.Observable; + navLinks$: Observable; + recentlyAccessed$: Observable; + forceAppSwitcherNavigation$: Observable; + helpExtension$: Observable; + helpSupportUrl$: Observable; legacyMode: boolean; - navControlsLeft$: Rx.Observable; - navControlsRight$: Rx.Observable; + navControlsLeft$: Observable; + navControlsRight$: Observable; basePath: HttpStart['basePath']; - isLocked$: Rx.Observable; - navType$: Rx.Observable; + isLocked$: Observable; + navType$: Observable; + loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; } -interface State { - appTitle: string; - isVisible: boolean; - navLinks: ChromeNavLink[]; - recentlyAccessed: ChromeRecentlyAccessedHistoryItem[]; - forceNavigation: boolean; - navControlsLeft: readonly ChromeNavControl[]; - navControlsRight: readonly ChromeNavControl[]; - currentAppId: string | undefined; - isLocked: boolean; - navType: NavType; - isOpen: boolean; +function renderMenuTrigger(toggleOpen: () => void) { + return ( + + + + ); } -export class Header extends Component { - private subscription?: Rx.Subscription; - private navDrawerRef = createRef(); - private toggleCollapsibleNavRef = createRef(); - - constructor(props: HeaderProps) { - super(props); - - let isLocked = false; - props.isLocked$.subscribe(initialIsLocked => (isLocked = initialIsLocked)); - - this.state = { - appTitle: 'Kibana', - isVisible: true, - navLinks: [], - recentlyAccessed: [], - forceNavigation: false, - navControlsLeft: [], - navControlsRight: [], - currentAppId: '', - isLocked, - navType: 'modern', - isOpen: false, - }; +export function Header({ + kibanaVersion, + kibanaDocLink, + legacyMode, + application, + basePath, + onIsLockedUpdate, + homeHref, + ...observables +}: HeaderProps) { + const isVisible = useObservable(observables.isVisible$, true); + const navType = useObservable(observables.navType$, 'modern'); + const isLocked = useObservable(observables.isLocked$, false); + const [isOpen, setIsOpen] = useState(false); + + if (!isVisible) { + return ; } - public componentDidMount() { - this.subscription = Rx.combineLatest( - this.props.appTitle$, - this.props.isVisible$, - this.props.forceAppSwitcherNavigation$, - this.props.navLinks$, - this.props.recentlyAccessed$, - // Types for combineLatest only handle up to 6 inferred types so we combine these separately. - Rx.combineLatest( - this.props.navControlsLeft$, - this.props.navControlsRight$, - this.props.application.currentAppId$, - this.props.isLocked$, - this.props.navType$ - ) - ).subscribe({ - next: ([ - appTitle, - isVisible, - forceNavigation, - navLinks, - recentlyAccessed, - [navControlsLeft, navControlsRight, currentAppId, isLocked, navType], - ]) => { - this.setState({ - appTitle, - isVisible, - forceNavigation, - navLinks: navLinks.filter(navLink => !navLink.hidden), - recentlyAccessed, - navControlsLeft, - navControlsRight, - currentAppId, - isLocked, - navType, - }); - }, - }); - } - - public componentWillUnmount() { - if (this.subscription) { - this.subscription.unsubscribe(); + const navDrawerRef = createRef(); + const toggleCollapsibleNavRef = createRef(); + const navId = htmlIdGenerator()(); + const className = classnames( + 'chrHeaderWrapper', // TODO #64541 - delete this + 'hide-for-sharing', + { + 'chrHeaderWrapper--navIsLocked': isLocked, + headerWrapper: navType === 'modern', } - } - - public renderMenuTrigger() { - return ( - this.navDrawerRef.current?.toggleOpen()} - > - - - ); - } - - public render() { - const { appTitle, isVisible, navControlsLeft, navControlsRight } = this.state; - const { - badge$, - breadcrumbs$, - helpExtension$, - helpSupportUrl$, - kibanaDocLink, - kibanaVersion, - } = this.props; - const navLinks = this.state.navLinks.map(link => - createNavLink( - link, - this.props.legacyMode, - this.state.currentAppId, - this.props.basePath, - this.props.application.navigateToApp - ) - ); - const recentNavLinks = this.state.recentlyAccessed.map(link => - createRecentNavLink(link, this.state.navLinks, this.props.basePath) - ); + ); - if (!isVisible) { - return null; - } - - const className = classnames( - 'chrHeaderWrapper', // TODO #64541 - delete this - 'hide-for-sharing', - { - 'chrHeaderWrapper--navIsLocked': this.state.isLocked, - headerWrapper: this.state.navType === 'modern', - } - ); - const navId = htmlIdGenerator()(); - return ( + return ( + <> +
- {this.state.navType === 'modern' ? ( + {navType === 'modern' ? ( { - this.setState({ isOpen: !this.state.isOpen }); - }} - aria-expanded={this.state.isOpen} - aria-pressed={this.state.isOpen} + onClick={() => setIsOpen(!isOpen)} + aria-expanded={isOpen} + aria-pressed={isOpen} aria-controls={navId} - ref={this.toggleCollapsibleNavRef} + ref={toggleCollapsibleNavRef} > @@ -236,71 +147,79 @@ export class Header extends Component { // Delete this block - {this.renderMenuTrigger()} + {renderMenuTrigger(() => navDrawerRef.current?.toggleOpen())} )} - + - + - + - + - {this.state.navType === 'modern' ? ( + {navType === 'modern' ? ( { - this.setState({ isOpen }); - if (this.toggleCollapsibleNavRef.current) { - this.toggleCollapsibleNavRef.current.focus(); + isLocked={isLocked} + navLinks$={observables.navLinks$} + recentlyAccessed$={observables.recentlyAccessed$} + isOpen={isOpen} + homeHref={homeHref} + basePath={basePath} + legacyMode={legacyMode} + navigateToApp={application.navigateToApp} + onIsLockedUpdate={onIsLockedUpdate} + closeNav={() => { + setIsOpen(false); + if (toggleCollapsibleNavRef.current) { + toggleCollapsibleNavRef.current.focus(); } }} - navigateToApp={this.props.application.navigateToApp} /> ) : ( // TODO #64541 // Delete this block )}
- ); - } + + ); } diff --git a/src/core/public/chrome/ui/header/header_badge.tsx b/src/core/public/chrome/ui/header/header_badge.tsx index 4e529ad9a410b..d2d5e5d663300 100644 --- a/src/core/public/chrome/ui/header/header_badge.tsx +++ b/src/core/public/chrome/ui/header/header_badge.tsx @@ -77,7 +77,7 @@ export class HeaderBadge extends Component { } private subscribe() { - this.subscription = this.props.badge$.subscribe(badge => { + this.subscription = this.props.badge$.subscribe((badge) => { this.setState({ badge, }); diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index 0398f162f9af9..7fe2c91087090 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -19,26 +19,23 @@ import { mount } from 'enzyme'; import React from 'react'; -import * as Rx from 'rxjs'; - -import { ChromeBreadcrumb } from '../../chrome_service'; +import { act } from 'react-dom/test-utils'; +import { BehaviorSubject } from 'rxjs'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { - const breadcrumbs$ = new Rx.Subject(); - const wrapper = mount(); - - breadcrumbs$.next([{ text: 'First' }]); - // Unfortunately, enzyme won't update the wrapper until we call update. - wrapper.update(); + const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); + const wrapper = mount( + + ); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); - breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }]); + act(() => breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }])); wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); - breadcrumbs$.next([]); + act(() => breadcrumbs$.next([])); wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); }); diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx index 83840cff45d03..174c46981db53 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx @@ -17,88 +17,36 @@ * under the License. */ -import classNames from 'classnames'; -import React, { Component } from 'react'; -import * as Rx from 'rxjs'; - import { EuiHeaderBreadcrumbs } from '@elastic/eui'; +import classNames from 'classnames'; +import React from 'react'; +import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; import { ChromeBreadcrumb } from '../../chrome_service'; interface Props { - appTitle?: string; - breadcrumbs$: Rx.Observable; + appTitle$: Observable; + breadcrumbs$: Observable; } -interface State { - breadcrumbs: ChromeBreadcrumb[]; -} - -export class HeaderBreadcrumbs extends Component { - private subscription?: Rx.Subscription; - - constructor(props: Props) { - super(props); +export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { + const appTitle = useObservable(appTitle$, 'Kibana'); + const breadcrumbs = useObservable(breadcrumbs$, []); + let crumbs = breadcrumbs; - this.state = { breadcrumbs: [] }; + if (breadcrumbs.length === 0 && appTitle) { + crumbs = [{ text: appTitle }]; } - public componentDidMount() { - this.subscribe(); - } - - public componentDidUpdate(prevProps: Props) { - if (prevProps.breadcrumbs$ === this.props.breadcrumbs$) { - return; - } + crumbs = crumbs.map((breadcrumb, i) => ({ + ...breadcrumb, + 'data-test-subj': classNames( + 'breadcrumb', + breadcrumb['data-test-subj'], + i === 0 && 'first', + i === breadcrumbs.length - 1 && 'last' + ), + })); - this.unsubscribe(); - this.subscribe(); - } - - public componentWillUnmount() { - this.unsubscribe(); - } - - public render() { - return ( - - ); - } - - private subscribe() { - this.subscription = this.props.breadcrumbs$.subscribe(breadcrumbs => { - this.setState({ - breadcrumbs, - }); - }); - } - - private unsubscribe() { - if (this.subscription) { - this.subscription.unsubscribe(); - delete this.subscription; - } - } - - private getBreadcrumbs() { - let breadcrumbs = this.state.breadcrumbs; - - if (breadcrumbs.length === 0 && this.props.appTitle) { - breadcrumbs = [{ text: this.props.appTitle }]; - } - - return breadcrumbs.map((breadcrumb, i) => ({ - ...breadcrumb, - 'data-test-subj': classNames( - 'breadcrumb', - breadcrumb['data-test-subj'], - i === 0 && 'first', - i === breadcrumbs.length - 1 && 'last' - ), - })); - } + return ; } diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 4296064945455..9bec946b6b76e 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -17,11 +17,13 @@ * under the License. */ -import Url from 'url'; -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiHeaderLogo } from '@elastic/eui'; -import { NavLink } from './nav_link'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; +import Url from 'url'; +import { ChromeNavLink } from '../..'; function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { let current = element; @@ -41,7 +43,7 @@ function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { function onClick( event: React.MouseEvent, forceNavigation: boolean, - navLinks: NavLink[], + navLinks: ChromeNavLink[], navigateToApp: (appId: string) => void ) { const anchor = findClosestAnchor((event as any).nativeEvent.target); @@ -49,8 +51,8 @@ function onClick( return; } - const navLink = navLinks.find(item => item.href === anchor.href); - if (navLink && navLink.isDisabled) { + const navLink = navLinks.find((item) => item.href === anchor.href); + if (navLink && navLink.disabled) { event.preventDefault(); return; } @@ -85,17 +87,20 @@ function onClick( interface Props { href: string; - navLinks: NavLink[]; - forceNavigation: boolean; + navLinks$: Observable; + forceNavigation$: Observable; navigateToApp: (appId: string) => void; } -export function HeaderLogo({ href, forceNavigation, navLinks, navigateToApp }: Props) { +export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { + const forceNavigation = useObservable(observables.forceNavigation$, false); + const navLinks = useObservable(observables.navLinks$, []); + return ( onClick(e, forceNavigation, navLinks, navigateToApp)} + onClick={(e) => onClick(e, forceNavigation, navLinks, navigateToApp)} href={href} aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { defaultMessage: 'Go to home page', diff --git a/src/core/public/chrome/ui/header/header_nav_controls.tsx b/src/core/public/chrome/ui/header/header_nav_controls.tsx index 0e77eb918bb61..0941f7b27b662 100644 --- a/src/core/public/chrome/ui/header/header_nav_controls.tsx +++ b/src/core/public/chrome/ui/header/header_nav_controls.tsx @@ -17,37 +17,34 @@ * under the License. */ -import React, { Component } from 'react'; - -import { - // @ts-ignore - EuiHeaderSectionItem, -} from '@elastic/eui'; - -import { HeaderExtension } from './header_extension'; +import { EuiHeaderSectionItem } from '@elastic/eui'; +import React from 'react'; +import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; import { ChromeNavControl } from '../../nav_controls'; +import { HeaderExtension } from './header_extension'; interface Props { - navControls: readonly ChromeNavControl[]; + navControls$: Observable; side: 'left' | 'right'; } -export class HeaderNavControls extends Component { - public render() { - const { navControls } = this.props; - - if (!navControls) { - return null; - } +export function HeaderNavControls({ navControls$, side }: Props) { + const navControls = useObservable(navControls$, []); - return navControls.map(this.renderNavControl); + if (!navControls) { + return null; } // It should be performant to use the index as the key since these are unlikely // to change while Kibana is running. - private renderNavControl = (navControl: ChromeNavControl, index: number) => ( - - - + return ( + <> + {navControls.map((navControl: ChromeNavControl, index: number) => ( + + + + ))} + ); } diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx index 17df8569f6307..ee4bff6cc0ac4 100644 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -17,24 +17,39 @@ * under the License. */ -import React from 'react'; +import { EuiHorizontalRule, EuiNavDrawer, EuiNavDrawerGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { EuiNavDrawer, EuiHorizontalRule, EuiNavDrawerGroup } from '@elastic/eui'; +import React from 'react'; +import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; +import { InternalApplicationStart } from '../../../application/types'; +import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; -import { NavLink, RecentNavLink } from './nav_link'; +import { createEuiListItem, createRecentNavLink } from './nav_link'; import { RecentLinks } from './recent_links'; export interface Props { + appId$: InternalApplicationStart['currentAppId$']; + basePath: HttpStart['basePath']; isLocked?: boolean; + legacyMode: boolean; + navLinks$: Observable; + recentlyAccessed$: Observable; + navigateToApp: CoreStart['application']['navigateToApp']; onIsLockedUpdate?: OnIsLockedUpdate; - navLinks: NavLink[]; - recentNavLinks: RecentNavLink[]; } -function navDrawerRenderer( - { isLocked, onIsLockedUpdate, navLinks, recentNavLinks }: Props, +function NavDrawerRenderer( + { isLocked, onIsLockedUpdate, basePath, legacyMode, navigateToApp, ...observables }: Props, ref: React.Ref ) { + const appId = useObservable(observables.appId$, ''); + const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); + const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map((link) => + createRecentNavLink(link, navLinks, basePath) + ); + return ( + createEuiListItem({ + link, + legacyMode, + appId, + basePath, + navigateToApp, + dataTestSubj: 'navDrawerAppsMenuLink', + }) + )} aria-label={i18n.translate('core.ui.primaryNavList.screenReaderLabel', { defaultMessage: 'Primary navigation links', })} @@ -58,4 +82,4 @@ function navDrawerRenderer( ); } -export const NavDrawer = React.forwardRef(navDrawerRenderer); +export const NavDrawer = React.forwardRef(NavDrawerRenderer); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 8003c22b99a36..969b6728e0263 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -17,12 +17,12 @@ * under the License. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiImage } from '@elastic/eui'; -import { AppCategory } from 'src/core/types'; -import { ChromeNavLink, CoreStart, ChromeRecentlyAccessedHistoryItem } from '../../../'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; import { HttpStart } from '../../../http'; +import { relativeToAbsolute } from '../../nav_links/to_nav_link'; function isModifiedEvent(event: React.MouseEvent) { return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); @@ -32,62 +32,42 @@ function LinkIcon({ url }: { url: string }) { return ; } -export interface NavLink { - key: string; - label: string; - href: string; - isActive: boolean; - onClick(event: React.MouseEvent): void; - category?: AppCategory; - isDisabled?: boolean; - iconType?: string; - icon?: JSX.Element; - order?: number; - 'data-test-subj': string; +interface Props { + link: ChromeNavLink; + legacyMode: boolean; + appId: string | undefined; + basePath?: HttpStart['basePath']; + dataTestSubj: string; + onClick?: Function; + navigateToApp: CoreStart['application']['navigateToApp']; } -/** - * Create a link that's actually ready to be passed into EUI - * - * @param navLink - * @param legacyMode - * @param currentAppId - * @param basePath - * @param navigateToApp - */ -export function createNavLink( - navLink: ChromeNavLink, - legacyMode: boolean, - currentAppId: string | undefined, - basePath: HttpStart['basePath'], - navigateToApp: CoreStart['application']['navigateToApp'] -): NavLink { - const { - legacy, - url, - active, - baseUrl, - id, - title, - disabled, - euiIconType, - icon, - category, - order, - tooltip, - } = navLink; - let href = navLink.url ?? navLink.baseUrl; +// TODO #64541 +// Set return type to EuiListGroupItemProps +// Currently it's a subset of EuiListGroupItemProps+FlyoutMenuItem for CollapsibleNav and NavDrawer +// But FlyoutMenuItem isn't exported from EUI +export function createEuiListItem({ + link, + legacyMode, + appId, + basePath, + onClick = () => {}, + navigateToApp, + dataTestSubj, +}: Props) { + const { legacy, active, id, title, disabled, euiIconType, icon, tooltip } = link; + let { href } = link; if (legacy) { - href = url && !active ? url : baseUrl; + href = link.url && !active ? link.url : link.baseUrl; } return { - category, - key: id, label: tooltip ?? title, - href, // Use href and onClick to support "open in new tab" and SPA navigation in the same link - onClick(event) { + href, + /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ + onClick(event: React.MouseEvent) { + onClick(); if ( !legacyMode && // ignore when in legacy mode !legacy && // ignore links to legacy apps @@ -96,57 +76,31 @@ export function createNavLink( !isModifiedEvent(event) // ignore clicks with modifier keys ) { event.preventDefault(); - navigateToApp(navLink.id); + navigateToApp(id); } }, // Legacy apps use `active` property, NP apps should match the current app - isActive: active || currentAppId === id, + isActive: active || appId === id, isDisabled: disabled, - iconType: euiIconType, - icon: !euiIconType && icon ? : undefined, - order, - 'data-test-subj': 'navDrawerAppsMenuLink', + 'data-test-subj': dataTestSubj, + ...(basePath && { + iconType: euiIconType, + icon: !euiIconType && icon ? : undefined, + }), }; } -// Providing a buffer between the limit and the cut off index -// protects from truncating just the last couple (6) characters -const TRUNCATE_LIMIT: number = 64; -const TRUNCATE_AT: number = 58; - -function truncateRecentItemLabel(label: string): string { - if (label.length > TRUNCATE_LIMIT) { - label = `${label.substring(0, TRUNCATE_AT)}…`; - } - - return label; -} - -/** - * @param {string} url - a relative or root relative url. If a relative path is given then the - * absolute url returned will depend on the current page where this function is called from. For example - * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get - * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that - * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". - * @return {string} the relative url transformed into an absolute url - */ -function relativeToAbsolute(url: string) { - const a = document.createElement('a'); - a.setAttribute('href', url); - return a.href; -} - export interface RecentNavLink { href: string; label: string; title: string; 'aria-label': string; iconType?: string; - onClick?(event: React.MouseEvent): void; } /** * Add saved object type info to recently links + * TODO #64541 - set return type to EuiListGroupItemProps * * Recent nav links are similar to normal nav links but are missing some Kibana Platform magic and * because of legacy reasons have slightly different properties. @@ -161,7 +115,7 @@ export function createRecentNavLink( ) { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); - const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); + const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); let titleAndAriaLabel = label; if (navLink) { @@ -176,7 +130,7 @@ export function createRecentNavLink( return { href, - label: truncateRecentItemLabel(label), + label, title: titleAndAriaLabel, 'aria-label': titleAndAriaLabel, iconType: navLink?.euiIconType, diff --git a/src/core/public/chrome/ui/loading_indicator.tsx b/src/core/public/chrome/ui/loading_indicator.tsx index 7729302b6b612..0209612eae08c 100644 --- a/src/core/public/chrome/ui/loading_indicator.tsx +++ b/src/core/public/chrome/ui/loading_indicator.tsx @@ -35,7 +35,7 @@ export class LoadingIndicator extends React.Component { + this.loadingCountSubscription = this.props.loadingCount$.subscribe((count) => { this.setState({ visible: count > 0, }); diff --git a/src/core/public/entry_point.ts b/src/core/public/entry_point.ts deleted file mode 100644 index 9461acccf30b9..0000000000000 --- a/src/core/public/entry_point.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * This is the entry point used to boot the frontend when serving a application - * that lives in the Kibana Platform. - * - * Any changes to this file should be kept in sync with - * src/legacy/ui/ui_bundles/app_entry_template.js - */ - -import './index.scss'; -import { i18n } from '@kbn/i18n'; -import { CoreSystem } from './core_system'; - -const injectedMetadata = JSON.parse( - document.querySelector('kbn-injected-metadata')!.getAttribute('data')! -); - -if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && process.env.ELASTIC_APM_ACTIVE === 'true') { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { init } = require('@elastic/apm-rum'); - init(injectedMetadata.vars.apmConfig); -} - -i18n - .load(injectedMetadata.i18n.translationsUrl) - .catch(e => e) - .then(async i18nError => { - const coreSystem = new CoreSystem({ - injectedMetadata, - rootDomElement: document.body, - browserSupportsCsp: !(window as any).__kbnCspNotEnforced__, - }); - - const setup = await coreSystem.setup(); - if (i18nError && setup) { - setup.fatalErrors.add(i18nError); - } - - await coreSystem.start(); - }); diff --git a/src/core/public/fatal_errors/fatal_errors_screen.tsx b/src/core/public/fatal_errors/fatal_errors_screen.tsx index f7184b01a0a78..98eb9f0ef21d5 100644 --- a/src/core/public/fatal_errors/fatal_errors_screen.tsx +++ b/src/core/public/fatal_errors/fatal_errors_screen.tsx @@ -63,8 +63,8 @@ export class FatalErrorsScreen extends React.Component { // consume error notifications and set them to the component state this.props.errorInfo$.pipe( - tap(error => { - this.setState(state => ({ + tap((error) => { + this.setState((state) => ({ ...state, errors: [...state.errors, error], })); diff --git a/src/core/public/fatal_errors/fatal_errors_service.test.ts b/src/core/public/fatal_errors/fatal_errors_service.test.ts index 373b0efddc2cf..a39f31d2c559c 100644 --- a/src/core/public/fatal_errors/fatal_errors_service.test.ts +++ b/src/core/public/fatal_errors/fatal_errors_service.test.ts @@ -20,7 +20,7 @@ import * as Rx from 'rxjs'; expect.addSnapshotSerializer({ - test: val => val instanceof Rx.Observable, + test: (val) => val instanceof Rx.Observable, print: () => `Rx.Observable`, }); diff --git a/src/core/public/fatal_errors/fatal_errors_service.tsx b/src/core/public/fatal_errors/fatal_errors_service.tsx index 309f07859ef26..403f8925b99c7 100644 --- a/src/core/public/fatal_errors/fatal_errors_service.tsx +++ b/src/core/public/fatal_errors/fatal_errors_service.tsx @@ -85,7 +85,7 @@ export class FatalErrorsService { }) ) .subscribe({ - error: error => { + error: (error) => { // eslint-disable-next-line no-console console.error('Uncaught error in fatal error service internals', error); }, @@ -145,7 +145,7 @@ export class FatalErrorsService { private setupGlobalErrorHandlers(fatalErrorsSetup: FatalErrorsSetup) { if (window.addEventListener) { - window.addEventListener('unhandledrejection', function(e) { + window.addEventListener('unhandledrejection', function (e) { console.log(`Detected an unhandled Promise rejection.\n${e.reason}`); // eslint-disable-line no-console }); } diff --git a/src/core/public/http/base_path.ts b/src/core/public/http/base_path.ts index 67464a6196b02..ac85d71c793fe 100644 --- a/src/core/public/http/base_path.ts +++ b/src/core/public/http/base_path.ts @@ -49,7 +49,7 @@ export class BasePath { public prepend = (path: string): string => { if (!this.basePath) return path; - return modifyUrl(path, parts => { + return modifyUrl(path, (parts) => { if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) { parts.pathname = `${this.basePath}${parts.pathname}`; } diff --git a/src/core/public/http/fetch.test.ts b/src/core/public/http/fetch.test.ts index f223956075e97..d889fae335ece 100644 --- a/src/core/public/http/fetch.test.ts +++ b/src/core/public/http/fetch.test.ts @@ -28,7 +28,7 @@ import { BasePath } from './base_path'; import { HttpResponse, HttpFetchOptionsWithPath } from './types'; function delay(duration: number) { - return new Promise(r => setTimeout(r, duration)); + return new Promise((r) => setTimeout(r, duration)); } const BASE_PATH = 'http://localhost/myBase'; @@ -44,11 +44,7 @@ describe('Fetch', () => { }); describe('getRequestCount$', () => { - const getCurrentRequestCount = () => - fetchInstance - .getRequestCount$() - .pipe(first()) - .toPromise(); + const getCurrentRequestCount = () => fetchInstance.getRequestCount$().pipe(first()).toPromise(); it('should increase and decrease when request receives success response', async () => { fetchMock.get('*', 200); @@ -88,7 +84,7 @@ describe('Fetch', () => { const requestCounts: number[] = []; const subscription = fetchInstance .getRequestCount$() - .subscribe(count => requestCounts.push(count)); + .subscribe((count) => requestCounts.push(count)); const success1 = fetchInstance.fetch('/success'); const success2 = fetchInstance.fetch('/success'); @@ -371,7 +367,7 @@ describe('Fetch', () => { fetchMock.get('*', Promise.reject(abortError)); - await fetchInstance.fetch('/my/path').catch(e => { + await fetchInstance.fetch('/my/path').catch((e) => { expect(e.name).toEqual('AbortError'); }); }); diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts index d88dc2e3a9037..bf9b4235e9444 100644 --- a/src/core/public/http/fetch.ts +++ b/src/core/public/http/fetch.ts @@ -212,7 +212,7 @@ const validateFetchArguments = ( ); } - const invalidHeaders = Object.keys(fullOptions.headers ?? {}).filter(headerName => + const invalidHeaders = Object.keys(fullOptions.headers ?? {}).filter((headerName) => headerName.startsWith('kbn-') ); if (invalidHeaders.length) { diff --git a/src/core/public/http/intercept.ts b/src/core/public/http/intercept.ts index bacc8748d2680..be02ebbf94fae 100644 --- a/src/core/public/http/intercept.ts +++ b/src/core/public/http/intercept.ts @@ -31,7 +31,7 @@ export async function interceptRequest( return [...interceptors].reduceRight( (promise, interceptor) => promise.then( - async fetchOptions => { + async (fetchOptions) => { current = fetchOptions; checkHalt(controller); @@ -45,7 +45,7 @@ export async function interceptRequest( ...overrides, }; }, - async error => { + async (error) => { checkHalt(controller, error); if (!interceptor.requestError) { @@ -83,7 +83,7 @@ export async function interceptResponse( return await [...interceptors].reduce( (promise, interceptor) => promise.then( - async httpResponse => { + async (httpResponse) => { current = httpResponse; checkHalt(controller); @@ -98,7 +98,7 @@ export async function interceptResponse( ...interceptorOverrides, }; }, - async error => { + async (error) => { const request = error.request || (current && current.request); checkHalt(controller, error); diff --git a/src/core/public/http/loading_count_service.test.ts b/src/core/public/http/loading_count_service.test.ts index 3ba4d315178cc..706d62b4283ba 100644 --- a/src/core/public/http/loading_count_service.test.ts +++ b/src/core/public/http/loading_count_service.test.ts @@ -89,10 +89,7 @@ describe('LoadingCountService', () => { const countA$ = new Subject(); const countB$ = new Subject(); const countC$ = new Subject(); - const promise = loadingCount - .getLoadingCount$() - .pipe(toArray()) - .toPromise(); + const promise = loadingCount.getLoadingCount$().pipe(toArray()).toPromise(); loadingCount.addLoadingCountSource(countA$); loadingCount.addLoadingCountSource(countB$); @@ -125,10 +122,7 @@ describe('LoadingCountService', () => { const { service, loadingCount } = setup(); const count$ = new Subject(); - const promise = loadingCount - .getLoadingCount$() - .pipe(toArray()) - .toPromise(); + const promise = loadingCount.getLoadingCount$().pipe(toArray()).toPromise(); loadingCount.addLoadingCountSource(count$); count$.next(0); diff --git a/src/core/public/http/loading_count_service.ts b/src/core/public/http/loading_count_service.ts index 14b945e0801ca..e4a248daca8f2 100644 --- a/src/core/public/http/loading_count_service.ts +++ b/src/core/public/http/loading_count_service.ts @@ -56,7 +56,7 @@ export class LoadingCountService implements CoreService { + tap((count) => { if (count < 0) { throw new Error( 'Observables passed to loadingCount.add() must only emit positive numbers' @@ -73,10 +73,10 @@ export class LoadingCountService implements CoreService next - prev) ) .subscribe({ - next: delta => { + next: (delta) => { this.loadingCount$.next(this.loadingCount$.getValue() + delta); }, - error: error => fatalErrors.add(error), + error: (error) => fatalErrors.add(error), }); }, }; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 3698fdcfe9512..bd275ca1d4565 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -35,6 +35,8 @@ * @packageDocumentation */ +import './index.scss'; + import { ChromeBadge, ChromeBrand, @@ -104,6 +106,7 @@ export { ApplicationSetup, ApplicationStart, App, + PublicAppInfo, AppBase, AppMount, AppMountDeprecated, @@ -120,6 +123,8 @@ export { AppUpdatableFields, AppUpdater, ScopedHistory, + LegacyApp, + PublicLegacyAppInfo, } from './application'; export { @@ -360,3 +365,5 @@ export { UiSettingsState, NavType, }; + +export { __kbnBootstrap__ } from './kbn_bootstrap'; diff --git a/src/core/public/integrations/moment/moment_service.test.mocks.ts b/src/core/public/integrations/moment/moment_service.test.mocks.ts index bb13232157b78..4c0c584679b2c 100644 --- a/src/core/public/integrations/moment/moment_service.test.mocks.ts +++ b/src/core/public/integrations/moment/moment_service.test.mocks.ts @@ -22,7 +22,7 @@ export const momentMock = { tz: { setDefault: jest.fn(), zone: jest.fn( - z => [{ name: 'tz1' }, { name: 'tz2' }, { name: 'tz3' }].find(f => z === f.name) || null + (z) => [{ name: 'tz1' }, { name: 'tz2' }, { name: 'tz3' }].find((f) => z === f.name) || null ), }, weekdays: jest.fn(() => ['dow1', 'dow2', 'dow3']), diff --git a/src/core/public/integrations/moment/moment_service.test.ts b/src/core/public/integrations/moment/moment_service.test.ts index bc48ba2a85f63..5179ff468f84d 100644 --- a/src/core/public/integrations/moment/moment_service.test.ts +++ b/src/core/public/integrations/moment/moment_service.test.ts @@ -32,7 +32,7 @@ describe('MomentService', () => { }); afterEach(() => service.stop()); - const flushPromises = () => new Promise(resolve => setTimeout(resolve, 100)); + const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 100)); test('sets initial moment config', async () => { const tz$ = new BehaviorSubject('tz1'); diff --git a/src/core/public/integrations/styles/styles_service.test.ts b/src/core/public/integrations/styles/styles_service.test.ts index e413e9cc2f4d7..fc75c6b3e3d06 100644 --- a/src/core/public/integrations/styles/styles_service.test.ts +++ b/src/core/public/integrations/styles/styles_service.test.ts @@ -25,7 +25,7 @@ import { StylesService } from './styles_service'; import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock'; describe('StylesService', () => { - const flushPromises = () => new Promise(resolve => setTimeout(resolve, 100)); + const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 100)); const getDisableAnimationsTag = () => document.querySelector('style#disableAnimationsCss')!; afterEach(() => getDisableAnimationsTag().remove()); diff --git a/src/core/public/kbn_bootstrap.ts b/src/core/public/kbn_bootstrap.ts new file mode 100644 index 0000000000000..caeb95a540de3 --- /dev/null +++ b/src/core/public/kbn_bootstrap.ts @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This is the entry point used to boot the frontend when serving a application + * that lives in the Kibana Platform. + * + * Any changes to this file should be kept in sync with + * src/legacy/ui/ui_bundles/app_entry_template.js + */ + +import { i18n } from '@kbn/i18n'; +import { CoreSystem } from './core_system'; + +/** @internal */ +export function __kbnBootstrap__() { + const injectedMetadata = JSON.parse( + document.querySelector('kbn-injected-metadata')!.getAttribute('data')! + ); + + /** + * `apmConfig` would be populated with relavant APM RUM agent + * configuration if server is started with `ELASTIC_APM_ACTIVE=true` + */ + if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && injectedMetadata.vars.apmConfig != null) { + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { init } = require('@elastic/apm-rum'); + init(injectedMetadata.vars.apmConfig); + } + + i18n + .load(injectedMetadata.i18n.translationsUrl) + .catch((e) => e) + .then(async (i18nError) => { + const coreSystem = new CoreSystem({ + injectedMetadata, + rootDomElement: document.body, + browserSupportsCsp: !(window as any).__kbnCspNotEnforced__, + }); + + const setup = await coreSystem.setup(); + if (i18nError && setup) { + setup.fatalErrors.add(i18nError); + } + + await coreSystem.start(); + }); +} diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index 01837ba6f5940..d77676b350f93 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -115,8 +115,8 @@ export class LegacyPlatformService { // Initialize legacy sub urls core.chrome.navLinks .getAll() - .filter(link => link.legacy) - .forEach(navLink => { + .filter((link) => link.legacy) + .forEach((navLink) => { const lastSubUrl = lastSubUrlStorage.getItem(`lastSubUrl:${navLink.baseUrl}`); core.chrome.navLinks.update(navLink.id, { url: lastSubUrl || navLink.url || navLink.baseUrl, @@ -131,10 +131,12 @@ export class LegacyPlatformService { const legacyCore: LegacyCoreStart = { ...core, application: { + applications$: core.application.applications$, currentAppId$: core.application.currentAppId$, capabilities: core.application.capabilities, getUrlForApp: core.application.getUrlForApp, navigateToApp: core.application.navigateToApp, + navigateToUrl: core.application.navigateToUrl, registerMountContext: notSupported(`core.application.registerMountContext()`), }, }; diff --git a/src/core/public/notifications/toasts/global_toast_list.test.tsx b/src/core/public/notifications/toasts/global_toast_list.test.tsx index dc2a9dabe791e..1d5d5b86836fd 100644 --- a/src/core/public/notifications/toasts/global_toast_list.test.tsx +++ b/src/core/public/notifications/toasts/global_toast_list.test.tsx @@ -34,7 +34,7 @@ it('renders matching snapshot', () => { it('subscribes to toasts$ on mount and unsubscribes on unmount', () => { const unsubscribeSpy = jest.fn(); - const subscribeSpy = jest.fn(observer => { + const subscribeSpy = jest.fn((observer) => { observer.next([]); return unsubscribeSpy; }); diff --git a/src/core/public/notifications/toasts/global_toast_list.tsx b/src/core/public/notifications/toasts/global_toast_list.tsx index f96a0a6f362bf..cb2d90ff265bf 100644 --- a/src/core/public/notifications/toasts/global_toast_list.tsx +++ b/src/core/public/notifications/toasts/global_toast_list.tsx @@ -47,7 +47,7 @@ export class GlobalToastList extends React.Component { private subscription?: Rx.Subscription; public componentDidMount() { - this.subscription = this.props.toasts$.subscribe(toasts => { + this.subscription = this.props.toasts$.subscribe((toasts) => { this.setState({ toasts }); }); } diff --git a/src/core/public/notifications/toasts/toasts_api.test.ts b/src/core/public/notifications/toasts/toasts_api.test.ts index 7c0ef5576256a..08e1d9711322c 100644 --- a/src/core/public/notifications/toasts/toasts_api.test.ts +++ b/src/core/public/notifications/toasts/toasts_api.test.ts @@ -25,10 +25,7 @@ import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.moc import { i18nServiceMock } from '../../i18n/i18n_service.mock'; async function getCurrentToasts(toasts: ToastsApi) { - return await toasts - .get$() - .pipe(take(1)) - .toPromise(); + return await toasts.get$().pipe(take(1)).toPromise(); } function uiSettingsMock() { diff --git a/src/core/public/notifications/toasts/toasts_api.tsx b/src/core/public/notifications/toasts/toasts_api.tsx index 53717b9c2e174..db65ed76d1cb1 100644 --- a/src/core/public/notifications/toasts/toasts_api.tsx +++ b/src/core/public/notifications/toasts/toasts_api.tsx @@ -150,7 +150,7 @@ export class ToastsApi implements IToasts { public remove(toastOrId: Toast | string) { const toRemove = typeof toastOrId === 'string' ? toastOrId : toastOrId.id; const list = this.toasts$.getValue(); - const listWithoutToast = list.filter(t => t.id !== toRemove); + const listWithoutToast = list.filter((t) => t.id !== toRemove); if (listWithoutToast.length !== list.length) { this.toasts$.next(listWithoutToast); } diff --git a/src/core/public/overlays/banners/banners_list.tsx b/src/core/public/overlays/banners/banners_list.tsx index ee7aa73dc34a6..6503af985f9c8 100644 --- a/src/core/public/overlays/banners/banners_list.tsx +++ b/src/core/public/overlays/banners/banners_list.tsx @@ -47,7 +47,7 @@ export const BannersList: React.FunctionComponent = ({ banners$ }) => { return (
- {banners.map(banner => ( + {banners.map((banner) => ( ))}
diff --git a/src/core/public/overlays/banners/banners_service.test.ts b/src/core/public/overlays/banners/banners_service.test.ts index f11a5d6b88bc2..8e76a18178ff0 100644 --- a/src/core/public/overlays/banners/banners_service.test.ts +++ b/src/core/public/overlays/banners/banners_service.test.ts @@ -31,11 +31,7 @@ describe('OverlayBannersService', () => { }); }); - const currentBanners = () => - service - .get$() - .pipe(take(1)) - .toPromise(); + const currentBanners = () => service.get$().pipe(take(1)).toPromise(); describe('adding banners', () => { test('adds a single banner', async () => { diff --git a/src/core/public/overlays/banners/banners_service.tsx b/src/core/public/overlays/banners/banners_service.tsx index ed59ed819b1c2..c0f76a6deaac7 100644 --- a/src/core/public/overlays/banners/banners_service.tsx +++ b/src/core/public/overlays/banners/banners_service.tsx @@ -112,7 +112,7 @@ export class OverlayBannersService { }, get$() { - return banners$.pipe(map(bannerMap => [...bannerMap.values()])); + return banners$.pipe(map((bannerMap) => [...bannerMap.values()])); }, getComponent() { diff --git a/src/core/public/overlays/banners/user_banner_service.tsx b/src/core/public/overlays/banners/user_banner_service.tsx index e3f4d9dee5b78..643d95a1e3bb4 100644 --- a/src/core/public/overlays/banners/user_banner_service.tsx +++ b/src/core/public/overlays/banners/user_banner_service.tsx @@ -63,7 +63,7 @@ export class UserBannerService { id = banners.replace( id, - el => { + (el) => { ReactDOM.render( { describe('openModal()', () => { it('renders a modal to the DOM', () => { expect(mockReactDomRender).not.toHaveBeenCalled(); - modals.open(container => { + modals.open((container) => { const content = document.createElement('span'); content.textContent = 'Modal content'; container.append(content); @@ -104,7 +104,7 @@ describe('ModalService', () => { describe('openConfirm()', () => { it('renders a mountpoint confirm message', () => { expect(mockReactDomRender).not.toHaveBeenCalled(); - modals.openConfirm(container => { + modals.openConfirm((container) => { const content = document.createElement('span'); content.textContent = 'Modal content'; container.append(content); diff --git a/src/core/public/overlays/overlay.test.mocks.ts b/src/core/public/overlays/overlay.test.mocks.ts index 563f414a0ae99..f382511d445ea 100644 --- a/src/core/public/overlays/overlay.test.mocks.ts +++ b/src/core/public/overlays/overlay.test.mocks.ts @@ -19,7 +19,7 @@ export const mockReactDomRender = jest.fn(); export const mockReactDomUnmount = jest.fn(); -export const mockReactDomCreatePortal = jest.fn().mockImplementation(component => component); +export const mockReactDomCreatePortal = jest.fn().mockImplementation((component) => component); jest.doMock('react-dom', () => ({ render: mockReactDomRender, createPortal: mockReactDomCreatePortal, diff --git a/src/core/public/plugins/plugin.test.ts b/src/core/public/plugins/plugin.test.ts index 8fe745db9554d..3f77161f8c34d 100644 --- a/src/core/public/plugins/plugin.test.ts +++ b/src/core/public/plugins/plugin.test.ts @@ -101,7 +101,7 @@ describe('PluginWrapper', () => { setup: jest.fn(), start: jest.fn(async () => { // Add small delay to ensure startDependencies is not resolved until after the plugin instance's start resolves. - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise((resolve) => setTimeout(resolve, 10)); expect(startDependenciesResolved).toBe(false); return pluginStartContract; }), @@ -113,7 +113,7 @@ describe('PluginWrapper', () => { const deps = { otherDep: 'value' }; // Add promise callback prior to calling `start` to ensure calls in `setup` will not resolve before `start` is // called. - const startDependenciesCheck = plugin.startDependencies.then(res => { + const startDependenciesCheck = plugin.startDependencies.then((res) => { startDependenciesResolved = true; expect(res).toEqual([context, deps, pluginStartContract]); }); diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index c4b3c929415ee..65c6b6ce4edba 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -95,8 +95,8 @@ export function createPluginSetupContext< ): CoreSetup { return { application: { - register: app => deps.application.register(plugin.opaqueId, app), - registerAppUpdater: statusUpdater$ => deps.application.registerAppUpdater(statusUpdater$), + register: (app) => deps.application.register(plugin.opaqueId, app), + registerAppUpdater: (statusUpdater$) => deps.application.registerAppUpdater(statusUpdater$), registerMountContext: (contextName, provider) => deps.application.registerMountContext(plugin.opaqueId, contextName, provider), }, @@ -135,9 +135,11 @@ export function createPluginStartContext< ): CoreStart { return { application: { + applications$: deps.application.applications$, currentAppId$: deps.application.currentAppId$, capabilities: deps.application.capabilities, navigateToApp: deps.application.navigateToApp, + navigateToUrl: deps.application.navigateToUrl, getUrlForApp: deps.application.getUrlForApp, registerMountContext: (contextName, provider) => deps.application.registerMountContext(plugin.opaqueId, contextName, provider), diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index 6c5ab5fcedcfd..05127e73d0c7a 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -50,7 +50,7 @@ import { contextServiceMock } from '../context/context_service.mock'; export let mockPluginInitializers: Map; mockPluginInitializerProvider.mockImplementation( - pluginName => mockPluginInitializers.get(pluginName)! + (pluginName) => mockPluginInitializers.get(pluginName)! ); let plugins: InjectedPluginMetadata[]; @@ -251,7 +251,7 @@ describe('PluginsService', () => { }); describe('timeout', () => { - const flushPromises = () => new Promise(resolve => setImmediate(resolve)); + const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); beforeAll(() => { jest.useFakeTimers(); }); @@ -263,7 +263,7 @@ describe('PluginsService', () => { mockPluginInitializers.set( 'pluginA', jest.fn(() => ({ - setup: jest.fn(() => new Promise(i => i)), + setup: jest.fn(() => new Promise((i) => i)), start: jest.fn(() => ({ value: 1 })), stop: jest.fn(), })) @@ -344,7 +344,7 @@ describe('PluginsService', () => { 'pluginA', jest.fn(() => ({ setup: jest.fn(() => ({ value: 1 })), - start: jest.fn(() => new Promise(i => i)), + start: jest.fn(() => new Promise((i) => i)), stop: jest.fn(), })) ); diff --git a/src/core/public/plugins/plugins_service.ts b/src/core/public/plugins/plugins_service.ts index 862aa5043ad4b..f9bc40ca52601 100644 --- a/src/core/public/plugins/plugins_service.ts +++ b/src/core/public/plugins/plugins_service.ts @@ -60,14 +60,14 @@ export class PluginsService implements CoreService(plugins.map(p => [p.id, Symbol(p.id)])); + const opaqueIds = new Map(plugins.map((p) => [p.id, Symbol(p.id)])); // Setup dependency map and plugin wrappers plugins.forEach(({ id, plugin, config = {} }) => { // Setup map of dependencies this.pluginDependencies.set(id, [ ...plugin.requiredPlugins, - ...plugin.optionalPlugins.filter(optPlugin => opaqueIds.has(optPlugin)), + ...plugin.optionalPlugins.filter((optPlugin) => opaqueIds.has(optPlugin)), ]); // Construct plugin wrappers, depending on the topological order set by the server. @@ -87,7 +87,7 @@ export class PluginsService implements CoreService [ this.plugins.get(id)!.opaqueId, - deps.map(depId => this.plugins.get(depId)!.opaqueId), + deps.map((depId) => this.plugins.get(depId)!.opaqueId), ]) ); } diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index fbd8f474151fa..74c41d010ca8d 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -25,6 +25,9 @@ import { Type } from '@kbn/config-schema'; import { UnregisterCallback } from 'history'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; +// @internal (undocumented) +export function __kbnBootstrap__(): void; + // @public export interface App extends AppBase { appRoute?: string; @@ -106,6 +109,7 @@ export interface ApplicationSetup { // @public (undocumented) export interface ApplicationStart { + applications$: Observable>; capabilities: RecursiveReadonly; currentAppId$: Observable; getUrlForApp(appId: string, options?: { @@ -116,6 +120,7 @@ export interface ApplicationStart { path?: string; state?: any; }): Promise; + navigateToUrl(url: string): Promise; // @deprecated registerMountContext(contextName: T, provider: IContextProvider): void; } @@ -285,6 +290,7 @@ export interface ChromeNavLink { readonly disableSubUrlTracking?: boolean; readonly euiIconType?: string; readonly hidden?: boolean; + readonly href?: string; readonly icon?: string; readonly id: string; // @internal @@ -313,7 +319,7 @@ export interface ChromeNavLinks { } // @public (undocumented) -export type ChromeNavLinkUpdateableFields = Partial>; +export type ChromeNavLinkUpdateableFields = Partial>; // @public export interface ChromeRecentlyAccessed { @@ -855,6 +861,18 @@ export interface IUiSettingsClient { set: (key: string, value: any) => Promise; } +// @public (undocumented) +export interface LegacyApp extends AppBase { + // (undocumented) + appUrl: string; + // (undocumented) + disableSubUrlTracking?: boolean; + // (undocumented) + linkToLastSubUrl?: boolean; + // (undocumented) + subUrlBase?: string; +} + // @public @deprecated export interface LegacyCoreSetup extends CoreSetup { // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts @@ -991,6 +1009,16 @@ export interface PluginInitializerContext // @public (undocumented) export type PluginOpaqueId = symbol; +// @public +export type PublicAppInfo = Omit & { + legacy: false; +}; + +// @public +export type PublicLegacyAppInfo = Omit & { + legacy: true; +}; + // @public export type PublicUiSettingsParams = Omit; diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 7958a4f8134d3..cdc113871c447 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -156,8 +156,8 @@ export class SavedObjectsClient { this.bulkGet(queue) .then(({ savedObjects }) => { - queue.forEach(queueItem => { - const foundObject = savedObjects.find(savedObject => { + queue.forEach((queueItem) => { + const foundObject = savedObjects.find((savedObject) => { return savedObject.id === queueItem.id && savedObject.type === queueItem.type; }); @@ -168,8 +168,8 @@ export class SavedObjectsClient { queueItem.resolve(foundObject); }); }) - .catch(err => { - queue.forEach(queueItem => { + .catch((err) => { + queue.forEach((queueItem) => { queueItem.reject(err); }); }); @@ -216,7 +216,7 @@ export class SavedObjectsClient { }), }); - return createRequest.then(resp => this.createSavedObject(resp)); + return createRequest.then((resp) => this.createSavedObject(resp)); }; /** @@ -239,8 +239,8 @@ export class SavedObjectsClient { query, body: JSON.stringify(objects), }); - return request.then(resp => { - resp.saved_objects = resp.saved_objects.map(d => this.createSavedObject(d)); + return request.then((resp) => { + resp.saved_objects = resp.saved_objects.map((d) => this.createSavedObject(d)); return renameKeys< PromiseType>, SavedObjectsBatchResponse @@ -301,8 +301,8 @@ export class SavedObjectsClient { method: 'GET', query, }); - return request.then(resp => { - resp.saved_objects = resp.saved_objects.map(d => this.createSavedObject(d)); + return request.then((resp) => { + resp.saved_objects = resp.saved_objects.map((d) => this.createSavedObject(d)); return renameKeys< PromiseType>, SavedObjectsFindResponsePublic @@ -350,14 +350,14 @@ export class SavedObjectsClient { */ public bulkGet = (objects: Array<{ id: string; type: string }> = []) => { const path = this.getPath(['_bulk_get']); - const filteredObjects = objects.map(obj => pick(obj, ['id', 'type'])); + const filteredObjects = objects.map((obj) => pick(obj, ['id', 'type'])); const request: ReturnType = this.savedObjectsFetch(path, { method: 'POST', body: JSON.stringify(filteredObjects), }); - return request.then(resp => { - resp.saved_objects = resp.saved_objects.map(d => this.createSavedObject(d)); + return request.then((resp) => { + resp.saved_objects = resp.saved_objects.map((d) => this.createSavedObject(d)); return renameKeys< PromiseType>, SavedObjectsBatchResponse @@ -414,7 +414,7 @@ export class SavedObjectsClient { return this.savedObjectsFetch(path, { method: 'PUT', body: JSON.stringify(objects), - }).then(resp => { + }).then((resp) => { resp.saved_objects = resp.saved_objects.map((d: SavedObject) => this.createSavedObject(d)); return renameKeys< PromiseType>, diff --git a/src/core/public/ui_settings/ui_settings_api.test.ts b/src/core/public/ui_settings/ui_settings_api.test.ts index 9a462e0541347..bab7081509d53 100644 --- a/src/core/public/ui_settings/ui_settings_api.test.ts +++ b/src/core/public/ui_settings/ui_settings_api.test.ts @@ -26,7 +26,7 @@ import { setup as httpSetup } from '../../../test_utils/public/http_test_setup'; import { UiSettingsApi } from './ui_settings_api'; function setup() { - const { http } = httpSetup(injectedMetadata => { + const { http } = httpSetup((injectedMetadata) => { injectedMetadata.getBasePath.mockReturnValue('/foo/bar'); }); @@ -181,10 +181,7 @@ describe('#getLoadingCount$()', () => { const { uiSettingsApi } = setup(); const done$ = new Rx.Subject(); - const promise = uiSettingsApi - .getLoadingCount$() - .pipe(takeUntil(done$), toArray()) - .toPromise(); + const promise = uiSettingsApi.getLoadingCount$().pipe(takeUntil(done$), toArray()).toPromise(); await uiSettingsApi.batchSet('foo', 'bar'); done$.next(); @@ -209,10 +206,7 @@ describe('#getLoadingCount$()', () => { const { uiSettingsApi } = setup(); const done$ = new Rx.Subject(); - const promise = uiSettingsApi - .getLoadingCount$() - .pipe(takeUntil(done$), toArray()) - .toPromise(); + const promise = uiSettingsApi.getLoadingCount$().pipe(takeUntil(done$), toArray()).toPromise(); await uiSettingsApi.batchSet('foo', 'bar'); await expect(uiSettingsApi.batchSet('foo', 'bar')).rejects.toThrowError(); @@ -230,14 +224,8 @@ describe('#stop', () => { const { uiSettingsApi } = setup(); const promise = Promise.all([ - uiSettingsApi - .getLoadingCount$() - .pipe(toArray()) - .toPromise(), - uiSettingsApi - .getLoadingCount$() - .pipe(toArray()) - .toPromise(), + uiSettingsApi.getLoadingCount$().pipe(toArray()).toPromise(), + uiSettingsApi.getLoadingCount$().pipe(toArray()).toPromise(), ]); const batchSetPromise = uiSettingsApi.batchSet('foo', 'bar'); diff --git a/src/core/public/ui_settings/ui_settings_client.test.ts b/src/core/public/ui_settings/ui_settings_client.test.ts index f394036e3e046..3b67efb740e7d 100644 --- a/src/core/public/ui_settings/ui_settings_client.test.ts +++ b/src/core/public/ui_settings/ui_settings_client.test.ts @@ -88,20 +88,14 @@ describe('#get', () => { describe('#get$', () => { it('emits the current value when called', async () => { const { client } = setup(); - const values = await client - .get$('dateFormat') - .pipe(take(1), toArray()) - .toPromise(); + const values = await client.get$('dateFormat').pipe(take(1), toArray()).toPromise(); expect(values).toEqual(['Browser']); }); it('emits an error notification if the key is unknown', async () => { const { client } = setup(); - const values = await client - .get$('unknown key') - .pipe(materialize()) - .toPromise(); + const values = await client.get$('unknown key').pipe(materialize()).toPromise(); expect(values).toMatchInlineSnapshot(` Notification { @@ -124,10 +118,7 @@ You can use \`IUiSettingsClient.get("unknown key", defaultValue)\`, which will j client.set('dateFormat', 'new format'); }, 10); - const values = await client - .get$('dateFormat') - .pipe(take(2), toArray()) - .toPromise(); + const values = await client.get$('dateFormat').pipe(take(2), toArray()).toPromise(); expect(values).toEqual(['Browser', 'new format']); }); diff --git a/src/core/public/ui_settings/ui_settings_client.ts b/src/core/public/ui_settings/ui_settings_client.ts index f5596b1bc34fc..2a7c3c2fab2f3 100644 --- a/src/core/public/ui_settings/ui_settings_client.ts +++ b/src/core/public/ui_settings/ui_settings_client.ts @@ -97,7 +97,7 @@ You can use \`IUiSettingsClient.get("${key}", defaultValue)\`, which will just r return concat( defer(() => of(this.get(key, defaultOverride))), this.update$.pipe( - filter(update => update.key === key), + filter((update) => update.key === key), map(() => this.get(key, defaultOverride)) ) ); diff --git a/src/core/public/ui_settings/ui_settings_service.test.ts b/src/core/public/ui_settings/ui_settings_service.test.ts index 2747a78d93fa6..4c688d5b7b2c1 100644 --- a/src/core/public/ui_settings/ui_settings_service.test.ts +++ b/src/core/public/ui_settings/ui_settings_service.test.ts @@ -38,7 +38,7 @@ describe('#stop', () => { it('stops the uiSettingsClient and uiSettingsApi', async () => { const service = new UiSettingsService(); let loadingCount$: Rx.Observable; - defaultDeps.http.addLoadingCountSource.mockImplementation(obs$ => (loadingCount$ = obs$)); + defaultDeps.http.addLoadingCountSource.mockImplementation((obs$) => (loadingCount$ = obs$)); const client = service.setup(defaultDeps); service.stop(); diff --git a/src/core/public/utils/share_weak_replay.test.ts b/src/core/public/utils/share_weak_replay.test.ts index 6eaa140e5afad..beac851aa689c 100644 --- a/src/core/public/utils/share_weak_replay.test.ts +++ b/src/core/public/utils/share_weak_replay.test.ts @@ -38,7 +38,7 @@ function counter({ async = true }: { async?: boolean } = {}) { completedCounts += 1; } - return new Rx.Observable(subscriber => { + return new Rx.Observable((subscriber) => { if (!async) { sendCount(subscriber); return; @@ -53,7 +53,7 @@ async function record(observable: Rx.Observable) { return observable .pipe( materialize(), - map(n => (n.kind === 'N' ? `N:${n.value}` : n.kind === 'E' ? `E:${n.error.message}` : 'C')), + map((n) => (n.kind === 'N' ? `N:${n.value}` : n.kind === 'E' ? `E:${n.error.message}` : 'C')), toArray() ) .toPromise(); diff --git a/src/core/public/utils/share_weak_replay.ts b/src/core/public/utils/share_weak_replay.ts index 74ea6cc536888..5ed6f76c5a05a 100644 --- a/src/core/public/utils/share_weak_replay.ts +++ b/src/core/public/utils/share_weak_replay.ts @@ -39,7 +39,7 @@ export function shareWeakReplay(bufferSize?: number): Rx.MonoTypeOperatorFunc let subject: Rx.ReplaySubject | undefined; const stop$ = new Rx.Subject(); - return new Rx.Observable(observer => { + return new Rx.Observable((observer) => { if (!subject) { subject = new Rx.ReplaySubject(bufferSize); } diff --git a/src/core/server/bootstrap.ts b/src/core/server/bootstrap.ts index dff0c00a4625e..5dbe5a0b4f955 100644 --- a/src/core/server/bootstrap.ts +++ b/src/core/server/bootstrap.ts @@ -71,7 +71,7 @@ export async function bootstrap({ // This is only used by the LogRotator service // in order to be able to reload the log configuration // under the cluster mode - process.on('message', msg => { + process.on('message', (msg) => { if (!msg || msg.reloadLoggingConfig !== true) { return; } diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index d3d6b507da523..f0be9743d4d60 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -127,7 +127,7 @@ export class CapabilitiesService { () => mergeCapabilities( defaultCapabilities, - ...this.capabilitiesProviders.map(provider => provider()) + ...this.capabilitiesProviders.map((provider) => provider()) ), () => this.capabilitiesSwitchers ); @@ -150,7 +150,7 @@ export class CapabilitiesService { public start(): CapabilitiesStart { return { - resolveCapabilities: request => this.resolveCapabilities(request, []), + resolveCapabilities: (request) => this.resolveCapabilities(request, []), }; } } diff --git a/src/core/server/capabilities/resolve_capabilities.ts b/src/core/server/capabilities/resolve_capabilities.ts index dcb93bdca5f16..1be504d4bc314 100644 --- a/src/core/server/capabilities/resolve_capabilities.ts +++ b/src/core/server/capabilities/resolve_capabilities.ts @@ -64,7 +64,7 @@ function recursiveApplyChanges< TSource extends Record >(destination: TDestination, source: TSource): TDestination { return Object.keys(destination) - .map(key => { + .map((key) => { const orig = destination[key]; const changed = source[key]; if (changed == null) { diff --git a/src/core/server/config/config.ts b/src/core/server/config/config.ts index b1a6a8cc525bf..a4026b1d88ac3 100644 --- a/src/core/server/config/config.ts +++ b/src/core/server/config/config.ts @@ -34,7 +34,7 @@ export function isConfigPath(value: unknown): value is ConfigPath { return true; } - return Array.isArray(value) && value.every(segment => typeof segment === 'string'); + return Array.isArray(value) && value.every((segment) => typeof segment === 'string'); } /** diff --git a/src/core/server/config/config_service.test.ts b/src/core/server/config/config_service.test.ts index 773a444dea948..5f28fca1371b0 100644 --- a/src/core/server/config/config_service.test.ts +++ b/src/core/server/config/config_service.test.ts @@ -62,10 +62,10 @@ test('throws if config at path does not match schema', async () => { .atPath('key') .pipe(take(1)) .subscribe( - value => { + (value) => { valuesReceived.push(value); }, - error => { + (error) => { valuesReceived.push(error); } ); @@ -86,10 +86,10 @@ test('re-validate config when updated', async () => { const valuesReceived: any[] = []; await configService.atPath('key').subscribe( - value => { + (value) => { valuesReceived.push(value); }, - error => { + (error) => { valuesReceived.push(error); } ); @@ -133,7 +133,7 @@ test("does not push new configs when reloading if config at path hasn't changed" await configService.setSchema('key', schema.string()); const valuesReceived: any[] = []; - configService.atPath('key').subscribe(value => { + configService.atPath('key').subscribe((value) => { valuesReceived.push(value); }); @@ -150,7 +150,7 @@ test('pushes new config when reloading and config at path has changed', async () await configService.setSchema('key', schema.string()); const valuesReceived: any[] = []; - configService.atPath('key').subscribe(value => { + configService.atPath('key').subscribe((value) => { valuesReceived.push(value); }); diff --git a/src/core/server/config/config_service.ts b/src/core/server/config/config_service.ts index 61630f43bffb5..bceba420bb6ce 100644 --- a/src/core/server/config/config_service.ts +++ b/src/core/server/config/config_service.ts @@ -89,7 +89,7 @@ export class ConfigService { const flatPath = pathToString(path); this.deprecations.next([ ...this.deprecations.value, - ...provider(configDeprecationFactory).map(deprecation => ({ + ...provider(configDeprecationFactory).map((deprecation) => ({ deprecation, path: flatPath, })), @@ -104,9 +104,7 @@ export class ConfigService { public async validate() { const namespaces = [...this.schemas.keys()]; for (let i = 0; i < namespaces.length; i++) { - await this.validateConfigAtPath(namespaces[i]) - .pipe(first()) - .toPromise(); + await this.validateConfigAtPath(namespaces[i]).pipe(first()).toPromise(); } await this.logDeprecation(); @@ -138,7 +136,7 @@ export class ConfigService { */ public optionalAtPath(path: ConfigPath) { return this.getDistinctConfig(path).pipe( - map(config => { + map((config) => { if (config === undefined) return undefined; return this.validateAtPath(path, config) as TSchema; }) @@ -149,9 +147,7 @@ export class ConfigService { const namespace = pathToString(path); const validatedConfig = this.schemas.has(namespace) - ? await this.atPath<{ enabled?: boolean }>(path) - .pipe(first()) - .toPromise() + ? await this.atPath<{ enabled?: boolean }>(path).pipe(first()).toPromise() : undefined; const enabledPath = createPluginEnabledPath(path); @@ -186,26 +182,23 @@ export class ConfigService { const config = await this.config$.pipe(first()).toPromise(); const handledPaths = this.handledPaths.map(pathToString); - return config.getFlattenedPaths().filter(path => !isPathHandled(path, handledPaths)); + return config.getFlattenedPaths().filter((path) => !isPathHandled(path, handledPaths)); } public async getUsedPaths() { const config = await this.config$.pipe(first()).toPromise(); const handledPaths = this.handledPaths.map(pathToString); - return config.getFlattenedPaths().filter(path => isPathHandled(path, handledPaths)); + return config.getFlattenedPaths().filter((path) => isPathHandled(path, handledPaths)); } private async logDeprecation() { - const rawConfig = await this.rawConfigProvider - .getConfig$() - .pipe(take(1)) - .toPromise(); + const rawConfig = await this.rawConfigProvider.getConfig$().pipe(take(1)).toPromise(); const deprecations = await this.deprecations.pipe(take(1)).toPromise(); const deprecationMessages: string[] = []; const logger = (msg: string) => deprecationMessages.push(msg); applyDeprecations(rawConfig, deprecations, logger); - deprecationMessages.forEach(msg => { + deprecationMessages.forEach((msg) => { this.deprecationLog.warn(msg); }); } @@ -228,14 +221,14 @@ export class ConfigService { } private validateConfigAtPath(path: ConfigPath) { - return this.getDistinctConfig(path).pipe(map(config => this.validateAtPath(path, config))); + return this.getDistinctConfig(path).pipe(map((config) => this.validateAtPath(path, config))); } private getDistinctConfig(path: ConfigPath) { this.markAsHandled(path); return this.config$.pipe( - map(config => config.get(path)), + map((config) => config.get(path)), distinctUntilChanged(isEqual) ); } @@ -260,4 +253,4 @@ const pathToString = (path: ConfigPath) => (Array.isArray(path) ? path.join('.') * handled paths. */ const isPathHandled = (path: string, handledPaths: string[]) => - handledPaths.some(handledPath => hasConfigPathIntersection(path, handledPath)); + handledPaths.some((handledPath) => hasConfigPathIntersection(path, handledPath)); diff --git a/src/core/server/config/deprecation/apply_deprecations.test.ts b/src/core/server/config/deprecation/apply_deprecations.test.ts index 25cae80d8b5cb..d7cc85add4e76 100644 --- a/src/core/server/config/deprecation/apply_deprecations.test.ts +++ b/src/core/server/config/deprecation/apply_deprecations.test.ts @@ -36,7 +36,7 @@ describe('applyDeprecations', () => { const handlerC = jest.fn(); applyDeprecations( {}, - [handlerA, handlerB, handlerC].map(h => wrapHandler(h)) + [handlerA, handlerB, handlerC].map((h) => wrapHandler(h)) ); expect(handlerA).toHaveBeenCalledTimes(1); expect(handlerB).toHaveBeenCalledTimes(1); @@ -49,7 +49,7 @@ describe('applyDeprecations', () => { const alteredConfig = { foo: 'bar' }; const handlerA = jest.fn().mockReturnValue(alteredConfig); - const handlerB = jest.fn().mockImplementation(conf => conf); + const handlerB = jest.fn().mockImplementation((conf) => conf); applyDeprecations( initialConfig, diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index a91e128f62d2d..ebdb6f1c88b16 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -28,11 +28,11 @@ const applyCoreDeprecations = (settings: Record = {}) => { const deprecationMessages: string[] = []; const migrated = applyDeprecations( settings, - deprecations.map(deprecation => ({ + deprecations.map((deprecation) => ({ deprecation, path: '', })), - msg => deprecationMessages.push(msg) + (msg) => deprecationMessages.push(msg) ); return { messages: deprecationMessages, diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 9e098c06ba155..483534e0c145b 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -72,26 +72,26 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { const rules: string[] = get(settings, 'csp.rules'); if (rules) { const parsed = new Map( - rules.map(ruleStr => { + rules.map((ruleStr) => { const parts = ruleStr.split(/\s+/); return [parts[0], parts.slice(1)]; }) ); settings.csp.rules = [...parsed].map(([policy, sourceList]) => { - if (sourceList.find(source => source.includes(NONCE_STRING))) { + if (sourceList.find((source) => source.includes(NONCE_STRING))) { log(`csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`); - sourceList = sourceList.filter(source => !source.includes(NONCE_STRING)); + sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING)); // Add 'self' if not present - if (!sourceList.find(source => source.includes(SELF_STRING))) { + if (!sourceList.find((source) => source.includes(SELF_STRING))) { sourceList.push(SELF_STRING); } } if ( SELF_POLICIES.includes(policy) && - !sourceList.find(source => source.includes(SELF_STRING)) + !sourceList.find((source) => source.includes(SELF_STRING)) ) { log(`csp.rules must contain the 'self' source. Automatically adding to ${policy}.`); sourceList.push(SELF_STRING); diff --git a/src/core/server/config/deprecation/deprecation_factory.test.ts b/src/core/server/config/deprecation/deprecation_factory.test.ts index 2595fdd923dd5..3910ee3235caf 100644 --- a/src/core/server/config/deprecation/deprecation_factory.test.ts +++ b/src/core/server/config/deprecation/deprecation_factory.test.ts @@ -24,7 +24,7 @@ describe('DeprecationFactory', () => { const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory; let deprecationMessages: string[]; - const logger: ConfigDeprecationLogger = msg => deprecationMessages.push(msg); + const logger: ConfigDeprecationLogger = (msg) => deprecationMessages.push(msg); beforeEach(() => { deprecationMessages = []; diff --git a/src/core/server/config/ensure_deep_object.ts b/src/core/server/config/ensure_deep_object.ts index 58865d13c1afa..6eaaef983355c 100644 --- a/src/core/server/config/ensure_deep_object.ts +++ b/src/core/server/config/ensure_deep_object.ts @@ -31,7 +31,7 @@ export function ensureDeepObject(obj: any): any { } if (Array.isArray(obj)) { - return obj.map(item => ensureDeepObject(item)); + return obj.map((item) => ensureDeepObject(item)); } return Object.keys(obj).reduce((fullObject, propertyKey) => { diff --git a/src/core/server/config/integration_tests/config_deprecation.test.ts b/src/core/server/config/integration_tests/config_deprecation.test.ts index 5bc3887f05f93..3523b074ea5b4 100644 --- a/src/core/server/config/integration_tests/config_deprecation.test.ts +++ b/src/core/server/config/integration_tests/config_deprecation.test.ts @@ -36,7 +36,7 @@ describe('configuration deprecations', () => { await root.setup(); const logs = loggingServiceMock.collect(mockLoggingService); - const warnings = logs.warn.flatMap(i => i); + const warnings = logs.warn.flatMap((i) => i); expect(warnings).not.toContain( '"optimize.lazy" is deprecated and has been replaced by "optimize.watch"' ); @@ -56,7 +56,7 @@ describe('configuration deprecations', () => { await root.setup(); const logs = loggingServiceMock.collect(mockLoggingService); - const warnings = logs.warn.flatMap(i => i); + const warnings = logs.warn.flatMap((i) => i); expect(warnings).toContain( '"optimize.lazy" is deprecated and has been replaced by "optimize.watch"' ); diff --git a/src/core/server/config/raw_config_service.test.ts b/src/core/server/config/raw_config_service.test.ts index f02c31d4659ca..8846ea3847f79 100644 --- a/src/core/server/config/raw_config_service.test.ts +++ b/src/core/server/config/raw_config_service.test.ts @@ -83,10 +83,7 @@ test('returns config at path as observable', async () => { configService.loadConfig(); - const exampleConfig = await configService - .getConfig$() - .pipe(first()) - .toPromise(); + const exampleConfig = await configService.getConfig$().pipe(first()).toPromise(); expect(exampleConfig.key).toEqual('value'); expect(Object.keys(exampleConfig)).toEqual(['key']); @@ -100,7 +97,7 @@ test("pushes new configs when reloading even if config at path hasn't changed", configService.loadConfig(); const valuesReceived: any[] = []; - configService.getConfig$().subscribe(config => { + configService.getConfig$().subscribe((config) => { valuesReceived.push(config); }); @@ -129,7 +126,7 @@ test('pushes new config when reloading and config at path has changed', async () configService.loadConfig(); const valuesReceived: any[] = []; - configService.getConfig$().subscribe(config => { + configService.getConfig$().subscribe((config) => { valuesReceived.push(config); }); @@ -145,7 +142,7 @@ test('pushes new config when reloading and config at path has changed', async () expect(Object.keys(valuesReceived[1])).toEqual(['key']); }); -test('completes config observables when stopped', done => { +test('completes config observables when stopped', (done) => { expect.assertions(0); mockGetConfigFromFiles.mockImplementation(() => ({ key: 'value' })); diff --git a/src/core/server/config/raw_config_service.ts b/src/core/server/config/raw_config_service.ts index 728d793f494a9..257ec612f3249 100644 --- a/src/core/server/config/raw_config_service.ts +++ b/src/core/server/config/raw_config_service.ts @@ -41,10 +41,10 @@ export class RawConfigService { constructor( public readonly configFiles: readonly string[], - configAdapter: RawConfigAdapter = rawConfig => rawConfig + configAdapter: RawConfigAdapter = (rawConfig) => rawConfig ) { this.config$ = this.rawConfigFromFile$.pipe( - map(rawConfig => { + map((rawConfig) => { if (isPlainObject(rawConfig)) { // TODO Make config consistent, e.g. handle dots in keys return configAdapter(cloneDeep(rawConfig)); diff --git a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts index 2c7efe075152b..3284be5ba4750 100644 --- a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts +++ b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts @@ -44,7 +44,7 @@ describe('default route provider', () => { await root.shutdown(); }); - it('redirects to the configured default route respecting basePath', async function() { + it('redirects to the configured default route respecting basePath', async function () { const { status, header } = await kbnTestServer.request.get(root, '/'); expect(status).toEqual(302); @@ -53,7 +53,7 @@ describe('default route provider', () => { }); }); - it('ignores invalid values', async function() { + it('ignores invalid values', async function () { const invalidRoutes = [ 'http://not-your-kibana.com', '///example.com', @@ -75,7 +75,7 @@ describe('default route provider', () => { }); }); - it('consumes valid values', async function() { + it('consumes valid values', async function () { await kbnTestServer.request .post(root, '/api/kibana/settings/defaultRoute') .send({ value: '/valid' }) diff --git a/src/core/server/core_app/integration_tests/static_assets.test.ts b/src/core/server/core_app/integration_tests/static_assets.test.ts index aad2510ef8c0e..23125cb3a6704 100644 --- a/src/core/server/core_app/integration_tests/static_assets.test.ts +++ b/src/core/server/core_app/integration_tests/static_assets.test.ts @@ -19,17 +19,17 @@ import * as kbnTestServer from '../../../../test_utils/kbn_server'; import { Root } from '../../root'; -describe('Platform assets', function() { +describe('Platform assets', function () { let root: Root; - beforeAll(async function() { + beforeAll(async function () { root = kbnTestServer.createRoot(); await root.setup(); await root.start(); }); - afterAll(async function() { + afterAll(async function () { await root.shutdown(); }); @@ -37,15 +37,15 @@ describe('Platform assets', function() { await kbnTestServer.request.get(root, '/ui/favicons/favicon.ico').expect(200); }); - it('returns 404 if not found', async function() { + it('returns 404 if not found', async function () { await kbnTestServer.request.get(root, '/ui/favicons/not-a-favicon.ico').expect(404); }); - it('does not expose folder content', async function() { + it('does not expose folder content', async function () { await kbnTestServer.request.get(root, '/ui/favicons/').expect(403); }); - it('does not allow file tree traversing', async function() { + it('does not allow file tree traversing', async function () { await kbnTestServer.request.get(root, '/ui/../../../../../README.md').expect(404); }); }); diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index cb4501a51e849..ccd5fd0c7a571 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -35,11 +35,11 @@ const applyElasticsearchDeprecations = (settings: Record = {}) => { _config[CONFIG_PATH] = settings; const migrated = applyDeprecations( _config, - deprecations.map(deprecation => ({ + deprecations.map((deprecation) => ({ deprecation, path: CONFIG_PATH, })), - msg => deprecationMessages.push(msg) + (msg) => deprecationMessages.push(msg) ); return { messages: deprecationMessages, diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index c87c94bcd0b6a..cac8c75a04486 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -51,7 +51,7 @@ export const configSchema = schema.object({ schema.contextRef('dist'), false, schema.string({ - validate: rawConfig => { + validate: (rawConfig) => { if (rawConfig === 'elastic') { return ( 'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' + @@ -96,7 +96,7 @@ export const configSchema = schema.object({ alwaysPresentCertificate: schema.boolean({ defaultValue: false }), }, { - validate: rawConfig => { + validate: (rawConfig) => { if (rawConfig.key && rawConfig.keystore.path) { return 'cannot use [key] when [keystore.path] is specified'; } @@ -112,7 +112,7 @@ export const configSchema = schema.object({ schema.contextRef('dev'), false, schema.boolean({ - validate: rawValue => { + validate: (rawValue) => { if (rawValue === true) { return '"ignoreVersionMismatch" can only be set to true in development mode'; } diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index a7d78b56ff3fd..55e60f5987604 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -23,12 +23,7 @@ import { IClusterClient, ICustomClusterClient } from './cluster_client'; import { IScopedClusterClient } from './scoped_cluster_client'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; -import { - InternalElasticsearchServiceSetup, - ElasticsearchServiceSetup, - ElasticsearchServiceStart, - ElasticsearchStatusMeta, -} from './types'; +import { InternalElasticsearchServiceSetup, ElasticsearchStatusMeta } from './types'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; import { ServiceStatus, ServiceStatusLevels } from '../status'; @@ -51,32 +46,26 @@ function createClusterClientMock() { return client; } -type MockedElasticSearchServiceSetup = jest.Mocked< - ElasticsearchServiceSetup & { - adminClient: jest.Mocked; - dataClient: jest.Mocked; - } ->; +interface MockedElasticSearchServiceSetup { + legacy: { + createClient: jest.Mock; + client: jest.Mocked; + }; +} const createSetupContractMock = () => { const setupContract: MockedElasticSearchServiceSetup = { - createClient: jest.fn(), - adminClient: createClusterClientMock(), - dataClient: createClusterClientMock(), + legacy: { + createClient: jest.fn(), + client: createClusterClientMock(), + }, }; - setupContract.createClient.mockReturnValue(createCustomClusterClientMock()); - setupContract.adminClient.asScoped.mockReturnValue(createScopedClusterClientMock()); - setupContract.dataClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + setupContract.legacy.createClient.mockReturnValue(createCustomClusterClientMock()); + setupContract.legacy.client.asScoped.mockReturnValue(createScopedClusterClientMock()); return setupContract; }; -type MockedElasticSearchServiceStart = { - legacy: jest.Mocked; -} & { - legacy: { - client: jest.Mocked; - }; -}; +type MockedElasticSearchServiceStart = MockedElasticSearchServiceSetup; const createStartContractMock = () => { const startContract: MockedElasticSearchServiceStart = { @@ -92,13 +81,11 @@ const createStartContractMock = () => { type MockedInternalElasticSearchServiceSetup = jest.Mocked< InternalElasticsearchServiceSetup & { - adminClient: jest.Mocked; - dataClient: jest.Mocked; + legacy: { client: jest.Mocked }; } >; const createInternalSetupContractMock = () => { const setupContract: MockedInternalElasticSearchServiceSetup = { - ...createSetupContractMock(), esNodesCompatibility$: new BehaviorSubject({ isCompatible: true, incompatibleNodes: [], @@ -111,10 +98,10 @@ const createInternalSetupContractMock = () => { }), legacy: { config$: new BehaviorSubject({} as ElasticsearchConfig), + ...createSetupContractMock().legacy, }, }; - setupContract.adminClient.asScoped.mockReturnValue(createScopedClusterClientMock()); - setupContract.dataClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + setupContract.legacy.client.asScoped.mockReturnValue(createScopedClusterClientMock()); return setupContract; }; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 2667859f303d4..e7dab3807733a 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -34,7 +34,7 @@ import { elasticsearchServiceMock } from './elasticsearch_service.mock'; import { duration } from 'moment'; const delay = async (durationMs: number) => - await new Promise(resolve => setTimeout(resolve, durationMs)); + await new Promise((resolve) => setTimeout(resolve, durationMs)); let elasticsearchService: ElasticsearchService; const configService = configServiceMock.create(); @@ -74,25 +74,16 @@ describe('#setup', () => { ); }); - it('returns data and admin client as a part of the contract', async () => { - const mockAdminClusterClientInstance = elasticsearchServiceMock.createClusterClient(); - const mockDataClusterClientInstance = elasticsearchServiceMock.createClusterClient(); - MockClusterClient.mockImplementationOnce( - () => mockAdminClusterClientInstance - ).mockImplementationOnce(() => mockDataClusterClientInstance); + it('returns elasticsearch client as a part of the contract', async () => { + const mockClusterClientInstance = elasticsearchServiceMock.createClusterClient(); + MockClusterClient.mockImplementationOnce(() => mockClusterClientInstance); const setupContract = await elasticsearchService.setup(deps); + const client = setupContract.legacy.client; - const adminClient = setupContract.adminClient; - const dataClient = setupContract.dataClient; - - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); - await adminClient.callAsInternalUser('any'); - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); - - expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); - await dataClient.callAsInternalUser('any'); - expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + await client.callAsInternalUser('any'); + expect(mockClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); }); describe('#createClient', () => { @@ -103,7 +94,7 @@ describe('#setup', () => { MockClusterClient.mockImplementation(() => mockClusterClientInstance); const customConfig = { logQueries: true }; - const clusterClient = setupContract.createClient('some-custom-type', customConfig); + const clusterClient = setupContract.legacy.createClient('some-custom-type', customConfig); expect(clusterClient).toBe(mockClusterClientInstance); @@ -124,7 +115,7 @@ describe('#setup', () => { logQueries: true, ssl: { certificate: 'certificate-value' }, }; - setupContract.createClient('some-custom-type', customConfig); + setupContract.legacy.createClient('some-custom-type', customConfig); const config = MockClusterClient.mock.calls[0][0]; expect(config).toMatchInlineSnapshot(` @@ -149,7 +140,7 @@ describe('#setup', () => { // reset all mocks called during setup phase MockClusterClient.mockClear(); - setupContract.createClient('another-type'); + setupContract.legacy.createClient('another-type'); const config = MockClusterClient.mock.calls[0][0]; expect(config).toMatchInlineSnapshot(` @@ -195,7 +186,7 @@ describe('#setup', () => { logQueries: true, ssl: { certificate: 'certificate-value' }, }; - setupContract.createClient('some-custom-type', customConfig); + setupContract.legacy.createClient('some-custom-type', customConfig); const config = MockClusterClient.mock.calls[0][0]; expect(config).toMatchInlineSnapshot(` @@ -217,41 +208,35 @@ describe('#setup', () => { }); }); - it('esNodeVersionCompatibility$ only starts polling when subscribed to', async done => { - const mockAdminClusterClientInstance = elasticsearchServiceMock.createClusterClient(); - const mockDataClusterClientInstance = elasticsearchServiceMock.createClusterClient(); - MockClusterClient.mockImplementationOnce( - () => mockAdminClusterClientInstance - ).mockImplementationOnce(() => mockDataClusterClientInstance); + it('esNodeVersionCompatibility$ only starts polling when subscribed to', async (done) => { + const clusterClientInstance = elasticsearchServiceMock.createClusterClient(); + MockClusterClient.mockImplementationOnce(() => clusterClientInstance); - mockAdminClusterClientInstance.callAsInternalUser.mockRejectedValue(new Error()); + clusterClientInstance.callAsInternalUser.mockRejectedValue(new Error()); const setupContract = await elasticsearchService.setup(deps); await delay(10); - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + expect(clusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); setupContract.esNodesCompatibility$.subscribe(() => { - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(clusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); done(); }); }); - it('esNodeVersionCompatibility$ stops polling when unsubscribed from', async done => { - const mockAdminClusterClientInstance = elasticsearchServiceMock.createClusterClient(); - const mockDataClusterClientInstance = elasticsearchServiceMock.createClusterClient(); - MockClusterClient.mockImplementationOnce( - () => mockAdminClusterClientInstance - ).mockImplementationOnce(() => mockDataClusterClientInstance); + it('esNodeVersionCompatibility$ stops polling when unsubscribed from', async (done) => { + const mockClusterClientInstance = elasticsearchServiceMock.createClusterClient(); + MockClusterClient.mockImplementationOnce(() => mockClusterClientInstance); - mockAdminClusterClientInstance.callAsInternalUser.mockRejectedValue(new Error()); + mockClusterClientInstance.callAsInternalUser.mockRejectedValue(new Error()); const setupContract = await elasticsearchService.setup(deps); - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + expect(mockClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); const sub = setupContract.esNodesCompatibility$.subscribe(async () => { sub.unsubscribe(); await delay(100); - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); done(); }); }); @@ -259,38 +244,31 @@ describe('#setup', () => { describe('#stop', () => { it('stops both admin and data clients', async () => { - const mockAdminClusterClientInstance = { close: jest.fn() }; - const mockDataClusterClientInstance = { close: jest.fn() }; - MockClusterClient.mockImplementationOnce( - () => mockAdminClusterClientInstance - ).mockImplementationOnce(() => mockDataClusterClientInstance); + const mockClusterClientInstance = { close: jest.fn() }; + MockClusterClient.mockImplementationOnce(() => mockClusterClientInstance); await elasticsearchService.setup(deps); await elasticsearchService.stop(); - expect(mockAdminClusterClientInstance.close).toHaveBeenCalledTimes(1); - expect(mockDataClusterClientInstance.close).toHaveBeenCalledTimes(1); + expect(mockClusterClientInstance.close).toHaveBeenCalledTimes(1); }); - it('stops pollEsNodeVersions even if there are active subscriptions', async done => { + it('stops pollEsNodeVersions even if there are active subscriptions', async (done) => { expect.assertions(2); - const mockAdminClusterClientInstance = elasticsearchServiceMock.createCustomClusterClient(); - const mockDataClusterClientInstance = elasticsearchServiceMock.createCustomClusterClient(); + const mockClusterClientInstance = elasticsearchServiceMock.createCustomClusterClient(); - MockClusterClient.mockImplementationOnce( - () => mockAdminClusterClientInstance - ).mockImplementationOnce(() => mockDataClusterClientInstance); + MockClusterClient.mockImplementationOnce(() => mockClusterClientInstance); - mockAdminClusterClientInstance.callAsInternalUser.mockRejectedValue(new Error()); + mockClusterClientInstance.callAsInternalUser.mockRejectedValue(new Error()); const setupContract = await elasticsearchService.setup(deps); setupContract.esNodesCompatibility$.subscribe(async () => { - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); await elasticsearchService.stop(); await delay(100); - expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); done(); }); }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index 18725f04a05b5..26001bf83924f 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -50,8 +50,7 @@ import { calculateStatus$ } from './status'; /** @internal */ interface CoreClusterClients { config: ElasticsearchConfig; - adminClient: ClusterClient; - dataClient: ClusterClient; + client: ClusterClient; } interface SetupDeps { @@ -70,14 +69,14 @@ export class ElasticsearchService type: string, clientConfig?: Partial ) => ICustomClusterClient; - private adminClient?: IClusterClient; + private client?: IClusterClient; constructor(private readonly coreContext: CoreContext) { this.kibanaVersion = coreContext.env.packageInfo.version; this.log = coreContext.logger.get('elasticsearch-service'); this.config$ = coreContext.configService .atPath('elasticsearch') - .pipe(map(rawConfig => new ElasticsearchConfig(rawConfig))); + .pipe(map((rawConfig) => new ElasticsearchConfig(rawConfig))); } public async setup(deps: SetupDeps): Promise { @@ -93,23 +92,21 @@ export class ElasticsearchService return true; }), switchMap( - config => - new Observable(subscriber => { - this.log.debug(`Creating elasticsearch clients`); + (config) => + new Observable((subscriber) => { + this.log.debug('Creating elasticsearch client'); const coreClients = { config, - adminClient: this.createClusterClient('admin', config), - dataClient: this.createClusterClient('data', config, deps.http.getAuthHeaders), + client: this.createClusterClient('data', config, deps.http.getAuthHeaders), }; subscriber.next(coreClients); return () => { - this.log.debug(`Closing elasticsearch clients`); + this.log.debug('Closing elasticsearch client'); - coreClients.adminClient.close(); - coreClients.dataClient.close(); + coreClients.client.close(); }; }) ), @@ -120,54 +117,27 @@ export class ElasticsearchService const config = await this.config$.pipe(first()).toPromise(); - const adminClient$ = clients$.pipe(map(clients => clients.adminClient)); - const dataClient$ = clients$.pipe(map(clients => clients.dataClient)); + const client$ = clients$.pipe(map((clients) => clients.client)); - this.adminClient = { + const client = { async callAsInternalUser( endpoint: string, clientParams: Record = {}, options?: CallAPIOptions ) { - const client = await adminClient$.pipe(take(1)).toPromise(); - return await client.callAsInternalUser(endpoint, clientParams, options); - }, - asScoped: (request: ScopeableRequest) => { - return { - callAsInternalUser: this.adminClient!.callAsInternalUser, - async callAsCurrentUser( - endpoint: string, - clientParams: Record = {}, - options?: CallAPIOptions - ) { - const client = await adminClient$.pipe(take(1)).toPromise(); - return await client - .asScoped(request) - .callAsCurrentUser(endpoint, clientParams, options); - }, - }; - }, - }; - - const dataClient = { - async callAsInternalUser( - endpoint: string, - clientParams: Record = {}, - options?: CallAPIOptions - ) { - const client = await dataClient$.pipe(take(1)).toPromise(); - return await client.callAsInternalUser(endpoint, clientParams, options); + const _client = await client$.pipe(take(1)).toPromise(); + return await _client.callAsInternalUser(endpoint, clientParams, options); }, asScoped(request: ScopeableRequest) { return { - callAsInternalUser: dataClient.callAsInternalUser, + callAsInternalUser: client.callAsInternalUser, async callAsCurrentUser( endpoint: string, clientParams: Record = {}, options?: CallAPIOptions ) { - const client = await dataClient$.pipe(take(1)).toPromise(); - return await client + const _client = await client$.pipe(take(1)).toPromise(); + return await _client .asScoped(request) .callAsCurrentUser(endpoint, clientParams, options); }, @@ -175,8 +145,10 @@ export class ElasticsearchService }, }; + this.client = client; + const esNodesCompatibility$ = pollEsNodesVersion({ - callWithInternalUser: this.adminClient.callAsInternalUser, + callWithInternalUser: client.callAsInternalUser, log: this.log, ignoreVersionMismatch: config.ignoreVersionMismatch, esVersionCheckInterval: config.healthCheckDelay.asMilliseconds(), @@ -189,22 +161,22 @@ export class ElasticsearchService }; return { - legacy: { config$: clients$.pipe(map(clients => clients.config)) }, + legacy: { + config$: clients$.pipe(map((clients) => clients.config)), + client, + createClient: this.createClient, + }, esNodesCompatibility$, - adminClient: this.adminClient, - dataClient, - createClient: this.createClient, status$: calculateStatus$(esNodesCompatibility$), }; } - public async start() { - if (typeof this.adminClient === 'undefined' || typeof this.createClient === 'undefined') { + if (typeof this.client === 'undefined' || typeof this.createClient === 'undefined') { throw new Error('ElasticsearchService needs to be setup before calling start'); } else { return { legacy: { - client: this.adminClient, + client: this.client, createClient: this.createClient, }, }; diff --git a/src/core/server/elasticsearch/retry_call_cluster.test.ts b/src/core/server/elasticsearch/retry_call_cluster.test.ts index 4f391f0aba34b..8be138e6752d2 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.test.ts +++ b/src/core/server/elasticsearch/retry_call_cluster.test.ts @@ -75,7 +75,7 @@ describe('migrationsRetryCallCluster', () => { loggingServiceMock.clear(mockLogger); }); - errors.forEach(errorName => { + errors.forEach((errorName) => { it('retries ES API calls that rejects with ' + errorName, () => { expect.assertions(1); const callEsApi = jest.fn(); diff --git a/src/core/server/elasticsearch/retry_call_cluster.ts b/src/core/server/elasticsearch/retry_call_cluster.ts index 901b801159cb6..aa3e39d948593 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.ts +++ b/src/core/server/elasticsearch/retry_call_cluster.ts @@ -47,7 +47,7 @@ export function migrationsRetryCallCluster( return (endpoint: string, clientParams: Record = {}, options?: CallAPIOptions) => { return defer(() => apiCaller(endpoint, clientParams, options)) .pipe( - retryWhen(error$ => + retryWhen((error$) => error$.pipe( concatMap((error, i) => { if (!previousErrors.includes(error.message)) { @@ -90,7 +90,7 @@ export function retryCallCluster(apiCaller: APICaller) { return (endpoint: string, clientParams: Record = {}, options?: CallAPIOptions) => { return defer(() => apiCaller(endpoint, clientParams, options)) .pipe( - retryWhen(errors => + retryWhen((errors) => errors.pipe( concatMap((error, i) => iif( diff --git a/src/core/server/elasticsearch/scoped_cluster_client.ts b/src/core/server/elasticsearch/scoped_cluster_client.ts index 920d236935261..4b64bfba15190 100644 --- a/src/core/server/elasticsearch/scoped_cluster_client.ts +++ b/src/core/server/elasticsearch/scoped_cluster_client.ts @@ -89,7 +89,7 @@ export class ScopedClusterClient implements IScopedClusterClient { const customHeaders: any = clientParams.headers; if (isObject(customHeaders)) { const duplicates = intersection(Object.keys(defaultHeaders), Object.keys(customHeaders)); - duplicates.forEach(duplicate => { + duplicates.forEach((duplicate) => { if (defaultHeaders[duplicate] !== (customHeaders as any)[duplicate]) { throw Error(`Cannot override default header ${duplicate}.`); } diff --git a/src/core/server/elasticsearch/status.test.ts b/src/core/server/elasticsearch/status.test.ts index dd5fb04bfd1c6..ef7ca7cd04608 100644 --- a/src/core/server/elasticsearch/status.test.ts +++ b/src/core/server/elasticsearch/status.test.ts @@ -38,11 +38,7 @@ const nodeInfo = { describe('calculateStatus', () => { it('starts in unavailable', async () => { - expect( - await calculateStatus$(new Subject()) - .pipe(take(1)) - .toPromise() - ).toEqual({ + expect(await calculateStatus$(new Subject()).pipe(take(1)).toPromise()).toEqual({ level: ServiceStatusLevels.unavailable, summary: 'Waiting for Elasticsearch', meta: { @@ -123,7 +119,7 @@ describe('calculateStatus', () => { const nodeCompat$ = new Subject(); const statusUpdates: ServiceStatus[] = []; - const subscription = calculateStatus$(nodeCompat$).subscribe(status => + const subscription = calculateStatus$(nodeCompat$).subscribe((status) => statusUpdates.push(status) ); diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index 3d38935e9fbf0..6fef08fc298ff 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -30,62 +30,60 @@ import { ServiceStatus } from '../status'; export interface ElasticsearchServiceSetup { /** * @deprecated - * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.createClient} instead. + * Use {@link ElasticsearchServiceStart.legacy} instead. * - * Create application specific Elasticsearch cluster API client with customized config. See {@link IClusterClient}. - * - * @param type Unique identifier of the client - * @param clientConfig A config consists of Elasticsearch JS client options and - * valid sub-set of Elasticsearch service config. - * We fill all the missing properties in the `clientConfig` using the default - * Elasticsearch config so that we don't depend on default values set and - * controlled by underlying Elasticsearch JS client. - * We don't run validation against the passed config and expect it to be valid. - * - * @example - * ```js - * const client = elasticsearch.createCluster('my-app-name', config); - * const data = await client.callAsInternalUser(); - * ``` - */ - readonly createClient: ( - type: string, - clientConfig?: Partial - ) => ICustomClusterClient; - - /** - * @deprecated - * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.client} instead. - * - * A client for the `admin` cluster. All Elasticsearch config value changes are processed under the hood. - * See {@link IClusterClient}. - * - * @example - * ```js - * const client = core.elasticsearch.adminClient; - * ``` - */ - readonly adminClient: IClusterClient; + * */ + legacy: { + /** + * @deprecated + * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.createClient} instead. + * + * Create application specific Elasticsearch cluster API client with customized config. See {@link IClusterClient}. + * + * @param type Unique identifier of the client + * @param clientConfig A config consists of Elasticsearch JS client options and + * valid sub-set of Elasticsearch service config. + * We fill all the missing properties in the `clientConfig` using the default + * Elasticsearch config so that we don't depend on default values set and + * controlled by underlying Elasticsearch JS client. + * We don't run validation against the passed config and expect it to be valid. + * + * @example + * ```js + * const client = elasticsearch.createCluster('my-app-name', config); + * const data = await client.callAsInternalUser(); + * ``` + */ + readonly createClient: ( + type: string, + clientConfig?: Partial + ) => ICustomClusterClient; - /** - * @deprecated - * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.client} instead. - * - * A client for the `data` cluster. All Elasticsearch config value changes are processed under the hood. - * See {@link IClusterClient}. - * - * @example - * ```js - * const client = core.elasticsearch.dataClient; - * ``` - */ - readonly dataClient: IClusterClient; + /** + * @deprecated + * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.client} instead. + * + * All Elasticsearch config value changes are processed under the hood. + * See {@link IClusterClient}. + * + * @example + * ```js + * const client = core.elasticsearch.legacy.client; + * ``` + */ + readonly client: IClusterClient; + }; } /** * @public */ export interface ElasticsearchServiceStart { + /** + * @deprecated + * Provided for the backward compatibility. + * Switch to the new elasticsearch client as soon as https://github.com/elastic/kibana/issues/35508 done. + * */ legacy: { /** * Create application specific Elasticsearch cluster API client with customized config. See {@link IClusterClient}. @@ -123,9 +121,9 @@ export interface ElasticsearchServiceStart { } /** @internal */ -export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup { +export interface InternalElasticsearchServiceSetup { // Required for the BWC with the legacy Kibana only. - readonly legacy: { + readonly legacy: ElasticsearchServiceSetup['legacy'] & { readonly config$: Observable; }; esNodesCompatibility$: Observable; diff --git a/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts b/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts index 4989c4a31295c..a4cf0ffd58088 100644 --- a/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts +++ b/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts @@ -30,7 +30,7 @@ const KIBANA_VERSION = '5.1.0'; function createNodes(...versions: string[]): NodesInfo { const nodes = {} as any; versions - .map(version => { + .map((version) => { return { version, http: { @@ -121,7 +121,7 @@ describe('pollEsNodesVersion', () => { callWithInternalUser.mockClear(); }); - it('returns iscCompatible=false and keeps polling when a poll request throws', done => { + it('returns iscCompatible=false and keeps polling when a poll request throws', (done) => { expect.assertions(3); const expectedCompatibilityResults = [false, false, true]; jest.clearAllMocks(); @@ -137,7 +137,7 @@ describe('pollEsNodesVersion', () => { }) .pipe(take(3)) .subscribe({ - next: result => { + next: (result) => { expect(result.isCompatible).toBe(expectedCompatibilityResults.shift()); }, complete: done, @@ -145,7 +145,7 @@ describe('pollEsNodesVersion', () => { }); }); - it('returns compatibility results', done => { + it('returns compatibility results', (done) => { expect.assertions(1); const nodes = createNodes('5.1.0', '5.2.0', '5.0.0'); callWithInternalUser.mockResolvedValueOnce(nodes); @@ -158,7 +158,7 @@ describe('pollEsNodesVersion', () => { }) .pipe(take(1)) .subscribe({ - next: result => { + next: (result) => { expect(result).toEqual(mapNodesVersionCompatibility(nodes, KIBANA_VERSION, false)); }, complete: done, @@ -166,7 +166,7 @@ describe('pollEsNodesVersion', () => { }); }); - it('only emits if the node versions changed since the previous poll', done => { + it('only emits if the node versions changed since the previous poll', (done) => { expect.assertions(4); callWithInternalUser.mockResolvedValueOnce(createNodes('5.1.0', '5.2.0', '5.0.0')); // emit callWithInternalUser.mockResolvedValueOnce(createNodes('5.0.0', '5.1.0', '5.2.0')); // ignore, same versions, different ordering @@ -184,7 +184,7 @@ describe('pollEsNodesVersion', () => { }) .pipe(take(4)) .subscribe({ - next: result => expect(result).toBeDefined(), + next: (result) => expect(result).toBeDefined(), complete: done, error: done, }); diff --git a/src/core/server/elasticsearch/version_check/ensure_es_version.ts b/src/core/server/elasticsearch/version_check/ensure_es_version.ts index 7bd6331978d1d..776298e5869a0 100644 --- a/src/core/server/elasticsearch/version_check/ensure_es_version.ts +++ b/src/core/server/elasticsearch/version_check/ensure_es_version.ts @@ -83,32 +83,32 @@ export function mapNodesVersionCompatibility( } const nodes = Object.keys(nodesInfo.nodes) .sort() // Sorting ensures a stable node ordering for comparison - .map(key => nodesInfo.nodes[key]) - .map(node => Object.assign({}, node, { name: getHumanizedNodeName(node) })); + .map((key) => nodesInfo.nodes[key]) + .map((node) => Object.assign({}, node, { name: getHumanizedNodeName(node) })); // Aggregate incompatible ES nodes. const incompatibleNodes = nodes.filter( - node => !esVersionCompatibleWithKibana(node.version, kibanaVersion) + (node) => !esVersionCompatibleWithKibana(node.version, kibanaVersion) ); // Aggregate ES nodes which should prompt a Kibana upgrade. It's acceptable // if ES and Kibana versions are not the same as long as they are not // incompatible, but we should warn about it. // Ignore version qualifiers https://github.com/elastic/elasticsearch/issues/36859 - const warningNodes = nodes.filter(node => !esVersionEqualsKibana(node.version, kibanaVersion)); + const warningNodes = nodes.filter((node) => !esVersionEqualsKibana(node.version, kibanaVersion)); // Note: If incompatible and warning nodes are present `message` only contains // an incompatibility notice. let message; if (incompatibleNodes.length > 0) { - const incompatibleNodeNames = incompatibleNodes.map(node => node.name).join(', '); + const incompatibleNodeNames = incompatibleNodes.map((node) => node.name).join(', '); if (ignoreVersionMismatch) { message = `Ignoring version incompatibility between Kibana v${kibanaVersion} and the following Elasticsearch nodes: ${incompatibleNodeNames}`; } else { message = `This version of Kibana (v${kibanaVersion}) is incompatible with the following Elasticsearch nodes in your cluster: ${incompatibleNodeNames}`; } } else if (warningNodes.length > 0) { - const warningNodeNames = warningNodes.map(node => node.name).join(', '); + const warningNodeNames = warningNodes.map((node) => node.name).join(', '); message = `You're running Kibana ${kibanaVersion} with some different versions of ` + 'Elasticsearch. Update Kibana or Elasticsearch to the same ' + @@ -151,7 +151,7 @@ export const pollEsNodesVersion = ({ filterPath: ['nodes.*.version', 'nodes.*.http.publish_address', 'nodes.*.ip'], }) ).pipe( - catchError(_err => { + catchError((_err) => { return of({ nodes: {} }); }) ); diff --git a/src/core/server/http/auth_headers_storage.ts b/src/core/server/http/auth_headers_storage.ts index 469e194a61fed..f532afc8b4bc7 100644 --- a/src/core/server/http/auth_headers_storage.ts +++ b/src/core/server/http/auth_headers_storage.ts @@ -33,7 +33,7 @@ export class AuthHeadersStorage { public set = (request: KibanaRequest | LegacyRequest, headers: AuthHeaders) => { this.authHeadersCache.set(ensureRawRequest(request), headers); }; - public get: GetAuthHeaders = request => { + public get: GetAuthHeaders = (request) => { return this.authHeadersCache.get(ensureRawRequest(request)); }; } diff --git a/src/core/server/http/auth_state_storage.ts b/src/core/server/http/auth_state_storage.ts index 10c8ccca32401..1172f06d06ab3 100644 --- a/src/core/server/http/auth_state_storage.ts +++ b/src/core/server/http/auth_state_storage.ts @@ -71,7 +71,7 @@ export class AuthStateStorage { return { status, state }; }; - public isAuthenticated: IsAuthenticated = request => { + public isAuthenticated: IsAuthenticated = (request) => { return this.get(request).status === AuthStatus.authenticated; }; } diff --git a/src/core/server/http/base_path_proxy_server.ts b/src/core/server/http/base_path_proxy_server.ts index acefbd00ae2be..ffbdabadd03f7 100644 --- a/src/core/server/http/base_path_proxy_server.ts +++ b/src/core/server/http/base_path_proxy_server.ts @@ -180,9 +180,7 @@ export class BasePathProxyServer { // condition is met (e.g. until target listener is ready). async (request, responseToolkit) => { apm.setTransactionName(`${request.method.toUpperCase()} /{basePath}/{kbnPath*}`); - await delayUntil() - .pipe(take(1)) - .toPromise(); + await delayUntil().pipe(take(1)).toPromise(); return responseToolkit.continue; }, ], @@ -216,9 +214,7 @@ export class BasePathProxyServer { // Before we proxy request to a target port we may want to wait until some // condition is met (e.g. until target listener is ready). async (request, responseToolkit) => { - await delayUntil() - .pipe(take(1)) - .toPromise(); + await delayUntil().pipe(take(1)).toPromise(); return responseToolkit.continue; }, ], diff --git a/src/core/server/http/base_path_service.ts b/src/core/server/http/base_path_service.ts index 5b5901b0ad6fb..093d73b2da3bf 100644 --- a/src/core/server/http/base_path_service.ts +++ b/src/core/server/http/base_path_service.ts @@ -69,7 +69,7 @@ export class BasePath { */ public prepend = (path: string): string => { if (this.serverBasePath === '') return path; - return modifyUrl(path, parts => { + return modifyUrl(path, (parts) => { if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) { parts.pathname = `${this.serverBasePath}${parts.pathname}`; } diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index 0ca87eae6e235..a5612675c37de 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -101,7 +101,7 @@ const userData = { id: '42' }; const sessionDurationMs = 1000; const path = '/'; const sessVal = () => ({ value: userData, expires: Date.now() + sessionDurationMs, path }); -const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); +const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); const cookieOptions = { name: 'sid', encryptionKey: 'something_at_least_32_characters', @@ -135,9 +135,7 @@ describe('Cookie based SessionStorage', () => { ); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(200); + const response = await supertest(innerServer.listener).get('/').expect(200); const cookies = response.get('set-cookie'); expect(cookies).toBeDefined(); @@ -174,9 +172,7 @@ describe('Cookie based SessionStorage', () => { ); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(200); + const response = await supertest(innerServer.listener).get('/').expect(200); const cookies = response.get('set-cookie'); expect(cookies).toBeDefined(); @@ -207,9 +203,7 @@ describe('Cookie based SessionStorage', () => { ); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(200, { value: null }); + const response = await supertest(innerServer.listener).get('/').expect(200, { value: null }); const cookies = response.get('set-cookie'); expect(cookies).not.toBeDefined(); @@ -414,9 +408,7 @@ describe('Cookie based SessionStorage', () => { ); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(200); + const response = await supertest(innerServer.listener).get('/').expect(200); const cookies = response.get('set-cookie'); const sessionCookie = retrieveSessionCookie(cookies[0]); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 7c72e3270743e..289b6539fd762 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -103,7 +103,7 @@ export const config = { }), }, { - validate: rawConfig => { + validate: (rawConfig) => { if (!rawConfig.basePath && rawConfig.rewriteBasePath) { return 'cannot use [rewriteBasePath] when [basePath] is not specified'; } @@ -157,7 +157,7 @@ export class HttpConfig { (headers, [key, value]) => { return { ...headers, - [key]: Array.isArray(value) ? value.map(e => convertHeader(e)) : convertHeader(value), + [key]: Array.isArray(value) ? value.map((e) => convertHeader(e)) : convertHeader(value), }; }, {} diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 4fb433b5c77ba..1798c3a921da4 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -163,7 +163,7 @@ test('valid params', async () => { await supertest(innerServer.listener) .get('/foo/some-string') .expect(200) - .then(res => { + .then((res) => { expect(res.text).toBe('some-string'); }); }); @@ -193,7 +193,7 @@ test('invalid params', async () => { await supertest(innerServer.listener) .get('/foo/some-string') .expect(400) - .then(res => { + .then((res) => { expect(res.body).toEqual({ error: 'Bad Request', statusCode: 400, @@ -228,7 +228,7 @@ test('valid query', async () => { await supertest(innerServer.listener) .get('/foo/?bar=test&quux=123') .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', quux: 123 }); }); }); @@ -258,7 +258,7 @@ test('invalid query', async () => { await supertest(innerServer.listener) .get('/foo/?bar=test') .expect(400) - .then(res => { + .then((res) => { expect(res.body).toEqual({ error: 'Bad Request', statusCode: 400, @@ -297,7 +297,7 @@ test('valid body', async () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', baz: 123 }); }); }); @@ -335,7 +335,7 @@ test('valid body with validate function', async () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', baz: 123 }); }); }); @@ -378,7 +378,7 @@ test('not inline validation - specifying params', async () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', baz: 123 }); }); }); @@ -421,7 +421,7 @@ test('not inline validation - specifying validation handler', async () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', baz: 123 }); }); }); @@ -471,7 +471,7 @@ test('not inline handler - KibanaRequest', async () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'TEST', baz: '123' }); }); }); @@ -520,7 +520,7 @@ test('not inline handler - RequestHandler', async () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'TEST', baz: '123' }); }); }); @@ -551,7 +551,7 @@ test('invalid body', async () => { .post('/foo/') .send({ bar: 'test' }) .expect(400) - .then(res => { + .then((res) => { expect(res.body).toEqual({ error: 'Bad Request', statusCode: 400, @@ -586,7 +586,7 @@ test('handles putting', async () => { .put('/foo/') .send({ key: 'new value' }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ key: 'new value' }); }); }); @@ -616,7 +616,7 @@ test('handles deleting', async () => { await supertest(innerServer.listener) .delete('/foo/3') .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ key: 3 }); }); }); @@ -646,28 +646,22 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => { }); test('/bar => 404', async () => { - await supertest(innerServerListener) - .get('/bar') - .expect(404); + await supertest(innerServerListener).get('/bar').expect(404); }); test('/bar/ => 404', async () => { - await supertest(innerServerListener) - .get('/bar/') - .expect(404); + await supertest(innerServerListener).get('/bar/').expect(404); }); test('/bar/foo => 404', async () => { - await supertest(innerServerListener) - .get('/bar/foo') - .expect(404); + await supertest(innerServerListener).get('/bar/foo').expect(404); }); test('/ => /', async () => { await supertest(innerServerListener) .get('/') .expect(200) - .then(res => { + .then((res) => { expect(res.text).toBe('value:/'); }); }); @@ -676,7 +670,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => { await supertest(innerServerListener) .get('/foo') .expect(200) - .then(res => { + .then((res) => { expect(res.text).toBe('value:/foo'); }); }); @@ -710,7 +704,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => { await supertest(innerServerListener) .get('/bar') .expect(200) - .then(res => { + .then((res) => { expect(res.text).toBe('value:/'); }); }); @@ -719,7 +713,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => { await supertest(innerServerListener) .get('/bar/') .expect(200) - .then(res => { + .then((res) => { expect(res.text).toBe('value:/'); }); }); @@ -728,21 +722,17 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => { await supertest(innerServerListener) .get('/bar/foo') .expect(200) - .then(res => { + .then((res) => { expect(res.text).toBe('value:/foo'); }); }); test('/ => 404', async () => { - await supertest(innerServerListener) - .get('/') - .expect(404); + await supertest(innerServerListener).get('/').expect(404); }); test('/foo => 404', async () => { - await supertest(innerServerListener) - .get('/foo') - .expect(404); + await supertest(innerServerListener).get('/foo').expect(404); }); }); @@ -787,13 +777,9 @@ test('allows attaching metadata to attach meta-data tag strings to a route', asy registerRouter(router); await server.start(); - await supertest(innerServer.listener) - .get('/with-tags') - .expect(200, { tags }); + await supertest(innerServer.listener).get('/with-tags').expect(200, { tags }); - await supertest(innerServer.listener) - .get('/without-tags') - .expect(200, { tags: [] }); + await supertest(innerServer.listener).get('/without-tags').expect(200, { tags: [] }); }); test('exposes route details of incoming request to a route handler', async () => { @@ -835,9 +821,7 @@ describe('conditional compression', () => { test('with `compression.enabled: true`', async () => { const listener = await setupServer(config); - const response = await supertest(listener) - .get('/') - .set('accept-encoding', 'gzip'); + const response = await supertest(listener).get('/').set('accept-encoding', 'gzip'); expect(response.header).toHaveProperty('content-encoding', 'gzip'); }); @@ -848,9 +832,7 @@ describe('conditional compression', () => { compression: { enabled: false }, }); - const response = await supertest(listener) - .get('/') - .set('accept-encoding', 'gzip'); + const response = await supertest(listener).get('/').set('accept-encoding', 'gzip'); expect(response.header).not.toHaveProperty('content-encoding'); }); @@ -865,9 +847,7 @@ describe('conditional compression', () => { }); test('enables compression for no referer', async () => { - const response = await supertest(listener) - .get('/') - .set('accept-encoding', 'gzip'); + const response = await supertest(listener).get('/').set('accept-encoding', 'gzip'); expect(response.header).toHaveProperty('content-encoding', 'gzip'); }); @@ -952,14 +932,11 @@ describe('body options', () => { registerRouter(router); await server.start(); - await supertest(innerServer.listener) - .post('/') - .send({ test: 1 }) - .expect(415, { - statusCode: 415, - error: 'Unsupported Media Type', - message: 'Unsupported Media Type', - }); + await supertest(innerServer.listener).post('/').send({ test: 1 }).expect(415, { + statusCode: 415, + error: 'Unsupported Media Type', + message: 'Unsupported Media Type', + }); }); test('should reject the request because the payload is too large', async () => { @@ -977,14 +954,11 @@ describe('body options', () => { registerRouter(router); await server.start(); - await supertest(innerServer.listener) - .post('/') - .send({ test: 1 }) - .expect(413, { - statusCode: 413, - error: 'Request Entity Too Large', - message: 'Payload content length greater than maximum allowed: 1', - }); + await supertest(innerServer.listener).post('/').send({ test: 1 }).expect(413, { + statusCode: 413, + error: 'Request Entity Too Large', + message: 'Payload content length greater than maximum allowed: 1', + }); }); test('should not parse the content in the request', async () => { @@ -1010,14 +984,11 @@ describe('body options', () => { registerRouter(router); await server.start(); - await supertest(innerServer.listener) - .post('/') - .send({ test: 1 }) - .expect(200, { - parse: false, - maxBytes: 1024, // hapi populates the default - output: 'data', - }); + await supertest(innerServer.listener).post('/').send({ test: 1 }).expect(200, { + parse: false, + maxBytes: 1024, // hapi populates the default + output: 'data', + }); }); }); @@ -1043,14 +1014,11 @@ test('should return a stream in the body', async () => { registerRouter(router); await server.start(); - await supertest(innerServer.listener) - .put('/') - .send({ test: 1 }) - .expect(200, { - parse: true, - maxBytes: 1024, // hapi populates the default - output: 'stream', - }); + await supertest(innerServer.listener).put('/').send({ test: 1 }).expect(200, { + parse: true, + maxBytes: 1024, // hapi populates the default + output: 'stream', + }); }); describe('setup contract', () => { diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 92ac5220735a1..8089ee901fa65 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -176,7 +176,7 @@ export class HttpServer { // validation applied in ./http_tools#getServerOptions // (All NP routes are already required to specify their own validation in order to access the payload) validate, - payload: [allow, maxBytes, output, parse].some(v => typeof v !== 'undefined') + payload: [allow, maxBytes, output, parse].some((v) => typeof v !== 'undefined') ? { allow, maxBytes, output, parse } : undefined, }, diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index bdaab4f2999ed..79a1e32d1b51e 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -81,7 +81,7 @@ describe('timeouts', () => { test('closes sockets on timeout', async () => { const router = new Router('', logger.get(), enhanceWithContext); router.get({ path: '/a', validate: false }, async (context, req, res) => { - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise((resolve) => setTimeout(resolve, 2000)); return res.ok({}); }); router.get({ path: '/b', validate: false }, (context, req, res) => res.ok({})); @@ -98,9 +98,7 @@ describe('timeouts', () => { expect(supertest(innerServer.listener).get('/a')).rejects.toThrow('socket hang up'); - await supertest(innerServer.listener) - .get('/b') - .expect(200); + await supertest(innerServer.listener).get('/b').expect(200); }); afterAll(async () => { diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 6e785ae9f8f00..4e47cf492e287 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -53,7 +53,7 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = { // This is a default payload validation which applies to all LP routes which do not specify their own // `validate.payload` handler, in order to reduce the likelyhood of prototype pollution vulnerabilities. // (All NP routes are already required to specify their own validation in order to access the payload) - payload: value => Promise.resolve(validateObject(value)), + payload: (value) => Promise.resolve(validateObject(value)), }, }, state: { @@ -104,7 +104,7 @@ export function createServer(serverOptions: ServerOptions, listenerOptions: List server.listener.keepAliveTimeout = listenerOptions.keepaliveTimeout; server.listener.setTimeout(listenerOptions.socketTimeout); - server.listener.on('timeout', socket => { + server.listener.on('timeout', (socket) => { socket.destroy(); }); server.listener.on('clientError', (err, socket) => { @@ -155,7 +155,7 @@ export function defaultValidationErrorHandler( const validationError: HapiValidationError = err as HapiValidationError; const validationKeys: string[] = []; - validationError.details.forEach(detail => { + validationError.details.forEach((detail) => { if (detail.path.length > 0) { validationKeys.push(Hoek.escapeHtml(detail.path.join('.'))); } else { diff --git a/src/core/server/http/https_redirect_server.test.ts b/src/core/server/http/https_redirect_server.test.ts index e7cd653bb9eec..a7d3cbe41aa3d 100644 --- a/src/core/server/http/https_redirect_server.test.ts +++ b/src/core/server/http/https_redirect_server.test.ts @@ -100,7 +100,7 @@ test('forwards http requests to https', async () => { await supertest(getServerListener(server)) .get('/') .expect(302) - .then(res => { + .then((res) => { expect(res.header.location).toEqual(`https://${config.host}:${config.port}/`); }); }); diff --git a/src/core/server/http/integration_tests/core_service.test.mocks.ts b/src/core/server/http/integration_tests/core_service.test.mocks.ts index b3adda8dd22d1..8e782970e2a8e 100644 --- a/src/core/server/http/integration_tests/core_service.test.mocks.ts +++ b/src/core/server/http/integration_tests/core_service.test.mocks.ts @@ -20,7 +20,7 @@ import { elasticsearchServiceMock } from '../../elasticsearch/elasticsearch_serv export const clusterClientMock = jest.fn(); jest.doMock('../../elasticsearch/scoped_cluster_client', () => ({ - ScopedClusterClient: clusterClientMock.mockImplementation(function() { + ScopedClusterClient: clusterClientMock.mockImplementation(function () { return elasticsearchServiceMock.createScopedClusterClient(); }), })); @@ -30,7 +30,7 @@ jest.doMock('elasticsearch', () => { return { ...realES, // eslint-disable-next-line object-shorthand - Client: function() { + Client: function () { return elasticsearchServiceMock.createElasticsearchClient(); }, }; diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index c7925f5b6d821..ba39effa77016 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -373,8 +373,7 @@ describe('http service', () => { const router = createRouter('/new-platform'); router.get({ path: '/', validate: false }, async (context, req, res) => { // it forces client initialization since the core creates them lazily. - await context.core.elasticsearch.adminClient.callAsCurrentUser('ping'); - await context.core.elasticsearch.dataClient.callAsCurrentUser('ping'); + await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); return res.ok(); }); @@ -382,12 +381,10 @@ describe('http service', () => { await kbnTestServer.request.get(root, '/new-platform/').expect(200); - // admin client contains authHeaders for BWC with legacy platform. - const [adminClient, dataClient] = clusterClientMock.mock.calls; - const [, , adminClientHeaders] = adminClient; - expect(adminClientHeaders).toEqual(authHeaders); - const [, , dataClientHeaders] = dataClient; - expect(dataClientHeaders).toEqual(authHeaders); + // client contains authHeaders for BWC with legacy platform. + const [client] = clusterClientMock.mock.calls; + const [, , clientHeaders] = client; + expect(clientHeaders).toEqual(authHeaders); }); it('passes request authorization header to Elasticsearch if registerAuth was not set', async () => { @@ -398,8 +395,7 @@ describe('http service', () => { const router = createRouter('/new-platform'); router.get({ path: '/', validate: false }, async (context, req, res) => { // it forces client initialization since the core creates them lazily. - await context.core.elasticsearch.adminClient.callAsCurrentUser('ping'); - await context.core.elasticsearch.dataClient.callAsCurrentUser('ping'); + await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); return res.ok(); }); @@ -410,11 +406,9 @@ describe('http service', () => { .set('Authorization', authorizationHeader) .expect(200); - const [adminClient, dataClient] = clusterClientMock.mock.calls; - const [, , adminClientHeaders] = adminClient; - expect(adminClientHeaders).toEqual({ authorization: authorizationHeader }); - const [, , dataClientHeaders] = dataClient; - expect(dataClientHeaders).toEqual({ authorization: authorizationHeader }); + const [client] = clusterClientMock.mock.calls; + const [, , clientHeaders] = client; + expect(clientHeaders).toEqual({ authorization: authorizationHeader }); }); }); }); diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 0f0d54e88daca..73ed4e5de4b04 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -75,9 +75,7 @@ describe('OnPreAuth', () => { }); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, 'ok'); + await supertest(innerServer.listener).get('/').expect(200, 'ok'); expect(callingOrder).toEqual(['first', 'second']); }); @@ -108,9 +106,7 @@ describe('OnPreAuth', () => { await server.start(); - await supertest(innerServer.listener) - .get('/initial') - .expect(200, 'redirected'); + await supertest(innerServer.listener).get('/initial').expect(200, 'redirected'); expect(urlBeforeForwarding).toBe('/initial'); expect(urlAfterForwarding).toBe('/redirectUrl'); @@ -132,9 +128,7 @@ describe('OnPreAuth', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/initial') - .expect(302); + const result = await supertest(innerServer.listener).get('/initial').expect(302); expect(result.header.location).toBe(redirectUrl); }); @@ -154,9 +148,7 @@ describe('OnPreAuth', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.header['www-authenticate']).toBe('challenge'); }); @@ -172,9 +164,7 @@ describe('OnPreAuth', () => { }); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -195,9 +185,7 @@ describe('OnPreAuth', () => { registerOnPreAuth((req, res, t) => ({} as any)); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -232,9 +220,7 @@ describe('OnPreAuth', () => { await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { customField: 'undefined' }); + await supertest(innerServer.listener).get('/').expect(200, { customField: 'undefined' }); }); }); @@ -257,9 +243,7 @@ describe('OnPostAuth', () => { }); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, 'ok'); + await supertest(innerServer.listener).get('/').expect(200, 'ok'); expect(callingOrder).toEqual(['first', 'second']); }); @@ -280,9 +264,7 @@ describe('OnPostAuth', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/initial') - .expect(302); + const result = await supertest(innerServer.listener).get('/initial').expect(302); expect(result.header.location).toBe(redirectUrl); }); @@ -301,9 +283,7 @@ describe('OnPostAuth', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.header['www-authenticate']).toBe('challenge'); }); @@ -318,9 +298,7 @@ describe('OnPostAuth', () => { }); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -340,9 +318,7 @@ describe('OnPostAuth', () => { registerOnPostAuth((req, res, t) => ({} as any)); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -378,9 +354,7 @@ describe('OnPostAuth', () => { await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { customField: 'undefined' }); + await supertest(innerServer.listener).get('/').expect(200, { customField: 'undefined' }); }); }); @@ -410,9 +384,7 @@ describe('Auth', () => { registerAuth((req, res, t) => t.authenticated()); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { content: 'ok' }); + await supertest(innerServer.listener).get('/').expect(200, { content: 'ok' }); }); it('blocks access to a resource if credentials are not provided', async () => { @@ -425,9 +397,7 @@ describe('Auth', () => { registerAuth((req, res, t) => t.notHandled()); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.body.message).toBe('Unauthorized'); }); @@ -443,9 +413,7 @@ describe('Auth', () => { registerAuth(authenticate); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { authRequired: true }); + await supertest(innerServer.listener).get('/').expect(200, { authRequired: true }); expect(authenticate).toHaveBeenCalledTimes(1); }); @@ -463,9 +431,7 @@ describe('Auth', () => { registerAuth(authenticate); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { authRequired: false }); + await supertest(innerServer.listener).get('/').expect(200, { authRequired: false }); expect(authenticate).toHaveBeenCalledTimes(0); }); @@ -483,9 +449,7 @@ describe('Auth', () => { await registerAuth(authenticate); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { authRequired: true }); + await supertest(innerServer.listener).get('/').expect(200, { authRequired: true }); expect(authenticate).toHaveBeenCalledTimes(1); }); @@ -498,9 +462,7 @@ describe('Auth', () => { registerAuth((req, res) => res.unauthorized()); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(401); + await supertest(innerServer.listener).get('/').expect(401); }); it('supports redirecting', async () => { @@ -516,9 +478,7 @@ describe('Auth', () => { ); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(302); + const response = await supertest(innerServer.listener).get('/').expect(302); expect(response.header.location).toBe(redirectTo); }); @@ -530,9 +490,7 @@ describe('Auth', () => { registerAuth((req, res, t) => t.redirected({} as any)); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(500); + await supertest(innerServer.listener).get('/').expect(500); }); it(`doesn't expose internal error details`, async () => { @@ -545,9 +503,7 @@ describe('Auth', () => { }); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -582,9 +538,7 @@ describe('Auth', () => { await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(200); + const response = await supertest(innerServer.listener).get('/').expect(200); expect(response.header['set-cookie']).toBeDefined(); const cookies = response.header['set-cookie']; @@ -628,9 +582,7 @@ describe('Auth', () => { }); await server.start(); - const responseToSetCookie = await supertest(innerServer.listener) - .get('/') - .expect(200); + const responseToSetCookie = await supertest(innerServer.listener).get('/').expect(200); expect(responseToSetCookie.header['set-cookie']).toBeDefined(); @@ -680,10 +632,7 @@ describe('Auth', () => { await server.start(); const token = 'Basic: user:password'; - await supertest(innerServer.listener) - .get('/') - .set('Authorization', token) - .expect(200); + await supertest(innerServer.listener).get('/').set('Authorization', token).expect(200); expect(fromRegisterOnPreAuth).toEqual({}); expect(fromRegisterAuth).toEqual({ authorization: token }); @@ -705,9 +654,7 @@ describe('Auth', () => { router.get({ path: '/', validate: false }, (context, req, res) => res.ok()); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(200); + const response = await supertest(innerServer.listener).get('/').expect(200); expect(response.header['www-authenticate']).toBe(authResponseHeader['www-authenticate']); }); @@ -726,9 +673,7 @@ describe('Auth', () => { router.get({ path: '/', validate: false }, (context, req, res) => res.badRequest()); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(400); + const response = await supertest(innerServer.listener).get('/').expect(400); expect(response.header['www-authenticate']).toBe(authResponseHeader['www-authenticate']); }); @@ -755,9 +700,7 @@ describe('Auth', () => { ); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(200); + const response = await supertest(innerServer.listener).get('/').expect(200); expect(response.header['www-authenticate']).toBe('from auth interceptor'); expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` @@ -790,9 +733,7 @@ describe('Auth', () => { ); await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .expect(400); + const response = await supertest(innerServer.listener).get('/').expect(400); expect(response.header['www-authenticate']).toBe('from auth interceptor'); expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` @@ -819,9 +760,7 @@ describe('Auth', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/initial') - .expect(302); + const result = await supertest(innerServer.listener).get('/initial').expect(302); expect(result.header.location).toBe(redirectUrl); }); @@ -841,9 +780,7 @@ describe('Auth', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.header['www-authenticate']).toBe('challenge'); }); @@ -858,9 +795,7 @@ describe('Auth', () => { }); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -880,9 +815,7 @@ describe('Auth', () => { registerOnPostAuth((req, res, t) => ({} as any)); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -917,9 +850,7 @@ describe('Auth', () => { await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { customField: 'undefined' }); + await supertest(innerServer.listener).get('/').expect(200, { customField: 'undefined' }); }); }); @@ -944,9 +875,7 @@ describe('OnPreResponse', () => { }); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, 'ok'); + await supertest(innerServer.listener).get('/').expect(200, 'ok'); expect(callingOrder).toEqual(['first', 'second']); }); @@ -974,9 +903,7 @@ describe('OnPreResponse', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.header['x-kibana-header']).toBe('value'); expect(result.header['x-my-header']).toBe('foo'); @@ -1000,9 +927,7 @@ describe('OnPreResponse', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200); + await supertest(innerServer.listener).get('/').expect(200); expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ @@ -1025,9 +950,7 @@ describe('OnPreResponse', () => { }); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -1049,9 +972,7 @@ describe('OnPreResponse', () => { registerOnPreResponse((req, res, t) => ({} as any)); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -1078,8 +999,6 @@ describe('OnPreResponse', () => { await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200); + await supertest(innerServer.listener).get('/').expect(200); }); }); diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts index b5364c616f17c..2120fb5b881de 100644 --- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -94,9 +94,7 @@ describe('core lifecycle handlers', () => { }); it('accepts requests that do not include a version header', async () => { - await supertest(innerServer.listener) - .get(testRoute) - .expect(200, 'ok'); + await supertest(innerServer.listener).get(testRoute).expect(200, 'ok'); }); it('rejects requests with an incorrect version passed in the version header', async () => { @@ -122,9 +120,7 @@ describe('core lifecycle handlers', () => { }); it('adds the kbn-name header', async () => { - const result = await supertest(innerServer.listener) - .get(testRoute) - .expect(200, 'ok'); + const result = await supertest(innerServer.listener).get(testRoute).expect(200, 'ok'); const headers = result.header as Record; expect(headers).toEqual( expect.objectContaining({ @@ -134,9 +130,7 @@ describe('core lifecycle handlers', () => { }); it('adds the kbn-name header in case of error', async () => { - const result = await supertest(innerServer.listener) - .get(testErrorRoute) - .expect(400); + const result = await supertest(innerServer.listener).get(testErrorRoute).expect(400); const headers = result.header as Record; expect(headers).toEqual( expect.objectContaining({ @@ -146,17 +140,13 @@ describe('core lifecycle handlers', () => { }); it('adds the custom headers', async () => { - const result = await supertest(innerServer.listener) - .get(testRoute) - .expect(200, 'ok'); + const result = await supertest(innerServer.listener).get(testRoute).expect(200, 'ok'); const headers = result.header as Record; expect(headers).toEqual(expect.objectContaining({ 'some-header': 'some-value' })); }); it('adds the custom headers in case of error', async () => { - const result = await supertest(innerServer.listener) - .get(testErrorRoute) - .expect(400); + const result = await supertest(innerServer.listener).get(testErrorRoute).expect(400); const headers = result.header as Record; expect(headers).toEqual(expect.objectContaining({ 'some-header': 'some-value' })); }); @@ -176,7 +166,7 @@ describe('core lifecycle handlers', () => { return res.ok({ body: 'ok' }); }); - destructiveMethods.forEach(method => { + destructiveMethods.forEach((method) => { ((router as any)[method.toLowerCase()] as RouteRegistrar)( { path: testPath, validate: false }, (context, req, res) => { @@ -200,7 +190,7 @@ describe('core lifecycle handlers', () => { await server.start(); }); - nonDestructiveMethods.forEach(method => { + nonDestructiveMethods.forEach((method) => { describe(`When using non-destructive ${method} method`, () => { it('accepts requests without a token', async () => { await getSupertest(method.toLowerCase(), testPath).expect( @@ -217,7 +207,7 @@ describe('core lifecycle handlers', () => { }); }); - destructiveMethods.forEach(method => { + destructiveMethods.forEach((method) => { describe(`When using destructive ${method} method`, () => { it('accepts requests with the xsrf header', async () => { await getSupertest(method.toLowerCase(), testPath) diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts index 85270174fbc04..d33757273042b 100644 --- a/src/core/server/http/integration_tests/request.test.ts +++ b/src/core/server/http/integration_tests/request.test.ts @@ -43,7 +43,7 @@ afterEach(async () => { await server.stop(); }); -const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); describe('KibanaRequest', () => { describe('auth', () => { describe('isAuthenticated', () => { @@ -56,11 +56,9 @@ describe('KibanaRequest', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - isAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + isAuthenticated: false, + }); }); it('returns false if not authenticated on a route with authRequired: "optional"', async () => { const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); @@ -72,11 +70,9 @@ describe('KibanaRequest', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - isAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + isAuthenticated: false, + }); }); it('returns false if redirected on a route with authRequired: "optional"', async () => { const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); @@ -88,11 +84,9 @@ describe('KibanaRequest', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - isAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + isAuthenticated: false, + }); }); it('returns true if authenticated on a route with authRequired: "optional"', async () => { const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); @@ -104,11 +98,9 @@ describe('KibanaRequest', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - isAuthenticated: true, - }); + await supertest(innerServer.listener).get('/').expect(200, { + isAuthenticated: true, + }); }); it('returns true if authenticated', async () => { const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); @@ -120,17 +112,15 @@ describe('KibanaRequest', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - isAuthenticated: true, - }); + await supertest(innerServer.listener).get('/').expect(200, { + isAuthenticated: true, + }); }); }); }); describe('events', () => { describe('aborted$', () => { - it('emits once and completes when request aborted', async done => { + it('emits once and completes when request aborted', async (done) => { expect.assertions(1); const { server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter('/'); diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index af05229f86f20..8f3799b12eccb 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -65,12 +65,10 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - httpAuthIsAuthenticated: false, - requestIsAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: false, + requestIsAuthenticated: false, + }); }); it('Authenticated user has access to a route', async () => { @@ -94,12 +92,10 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - httpAuthIsAuthenticated: true, - requestIsAuthenticated: true, - }); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: true, + requestIsAuthenticated: true, + }); }); it('User with no credentials can access a route', async () => { @@ -122,12 +118,10 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - httpAuthIsAuthenticated: false, - requestIsAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: false, + requestIsAuthenticated: false, + }); }); it('User with invalid credentials cannot access a route', async () => { @@ -142,9 +136,7 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(401); + await supertest(innerServer.listener).get('/').expect(401); }); it('does not redirect user and allows access to a resource', async () => { @@ -171,12 +163,10 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - httpAuthIsAuthenticated: false, - requestIsAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: false, + requestIsAuthenticated: false, + }); }); }); @@ -197,12 +187,10 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - httpAuthIsAuthenticated: false, - requestIsAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: false, + requestIsAuthenticated: false, + }); }); it('Authenticated user has access to a route', async () => { @@ -226,12 +214,10 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - httpAuthIsAuthenticated: true, - requestIsAuthenticated: true, - }); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: true, + requestIsAuthenticated: true, + }); }); it('User with no credentials cannot access a route', async () => { @@ -245,9 +231,7 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(401); + await supertest(innerServer.listener).get('/').expect(401); }); it('User with invalid credentials cannot access a route', async () => { @@ -262,9 +246,7 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(401); + await supertest(innerServer.listener).get('/').expect(401); }); it('allows redirecting an user', async () => { @@ -284,9 +266,7 @@ describe('Options', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(302); + const result = await supertest(innerServer.listener).get('/').expect(302); expect(result.header.location).toBe(redirectUrl); }); @@ -313,12 +293,10 @@ describe('Options', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200, { - httpAuthIsAuthenticated: false, - requestIsAuthenticated: false, - }); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: false, + requestIsAuthenticated: false, + }); expect(authHook).toHaveBeenCalledTimes(0); }); @@ -352,9 +330,7 @@ describe('Cache-Control', () => { ); await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect('Cache-Control', 'public, max-age=1200'); + await supertest(innerServer.listener).get('/').expect('Cache-Control', 'public, max-age=1200'); }); }); @@ -368,9 +344,7 @@ describe('Handler', () => { }); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -391,9 +365,7 @@ describe('Handler', () => { }); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -412,9 +384,7 @@ describe('Handler', () => { router.get({ path: '/', validate: false }, (context, req, res) => 'ok' as any); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -501,11 +471,7 @@ describe('Handler', () => { ); await server.start(); - await supertest(innerServer.listener) - .post('/') - .type('json') - .send('12') - .expect(200); + await supertest(innerServer.listener).post('/').type('json').send('12').expect(200); expect(body).toEqual(12); }); @@ -524,9 +490,7 @@ describe('handleLegacyErrors', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(404); + const result = await supertest(innerServer.listener).get('/').expect(404); expect(result.body.message).toBe('Not Found'); }); @@ -546,9 +510,7 @@ describe('handleLegacyErrors', () => { ); await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body).toEqual({ error: 'Internal Server Error', @@ -570,9 +532,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.body).toEqual({ key: 'value' }); expect(result.header['content-type']).toBe('application/json; charset=utf-8'); @@ -588,9 +548,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.text).toBe('result'); expect(result.header['content-type']).toBe('text/html; charset=utf-8'); @@ -606,9 +564,7 @@ describe('Response factory', () => { await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(200); + await supertest(innerServer.listener).get('/').expect(200); }); it('supports answering with Stream', async () => { @@ -630,9 +586,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.text).toBe('abc'); expect(result.header['content-type']).toBe(undefined); @@ -646,7 +600,7 @@ describe('Response factory', () => { const stream = new Stream.PassThrough(); stream.write('a'); stream.write('b'); - setTimeout(function() { + setTimeout(function () { stream.write('c'); stream.end(); }, 100); @@ -656,9 +610,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.text).toBe('abc'); expect(result.header['transfer-encoding']).toBe('chunked'); @@ -681,10 +633,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200) - .buffer(true); + const result = await supertest(innerServer.listener).get('/').expect(200).buffer(true); expect(result.header['content-encoding']).toBe('binary'); expect(result.header['content-length']).toBe('1028'); @@ -708,10 +657,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200) - .buffer(true); + const result = await supertest(innerServer.listener).get('/').expect(200).buffer(true); expect(result.text).toBe('abc'); expect(result.header['content-length']).toBe('3'); @@ -733,9 +679,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.text).toEqual('value'); expect(result.header.etag).toBe('1234'); @@ -757,9 +701,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.text).toEqual('value'); expect(result.header.etag).toBe('1234'); @@ -781,9 +723,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.header.etag).toBe('1234'); }); @@ -803,9 +743,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.header['set-cookie']).toEqual(['foo', 'bar']); }); @@ -822,9 +760,7 @@ describe('Response factory', () => { await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(500); + await supertest(innerServer.listener).get('/').expect(500); // error happens within hapi when route handler already finished execution. expect(loggingServiceMock.collect(logger).error).toHaveLength(0); @@ -840,9 +776,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(200); + const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.body).toEqual({ key: 'value' }); expect(result.header['content-type']).toBe('application/json; charset=utf-8'); @@ -858,9 +792,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(202); + const result = await supertest(innerServer.listener).get('/').expect(202); expect(result.body).toEqual({ location: 'somewhere' }); expect(result.header['content-type']).toBe('application/json; charset=utf-8'); @@ -876,9 +808,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(204); + const result = await supertest(innerServer.listener).get('/').expect(204); expect(result.noContent).toBe(true); }); @@ -901,9 +831,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(302); + const result = await supertest(innerServer.listener).get('/').expect(302); expect(result.text).toBe('The document has moved'); expect(result.header.location).toBe('/new-url'); @@ -924,9 +852,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -951,9 +877,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(400); + const result = await supertest(innerServer.listener).get('/').expect(400); expect(result.body).toEqual({ error: 'Bad Request', @@ -972,9 +896,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(400); + const result = await supertest(innerServer.listener).get('/').expect(400); expect(result.body).toEqual({ error: 'Bad Request', @@ -995,9 +917,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(400); + const result = await supertest(innerServer.listener).get('/').expect(400); expect(result.body).toEqual({ error: 'Bad Request', @@ -1040,7 +960,7 @@ describe('Response factory', () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', baz: 123 }); }); @@ -1051,7 +971,7 @@ describe('Response factory', () => { baz: '123', }) .expect(400) - .then(res => { + .then((res) => { expect(res.body).toEqual({ error: 'Bad Request', message: '[request body.body]: Wrong payload', @@ -1088,7 +1008,7 @@ describe('Response factory', () => { baz: 123, }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', baz: 123 }); }); @@ -1099,7 +1019,7 @@ describe('Response factory', () => { baz: '123', // Automatic casting happens }) .expect(200) - .then(res => { + .then((res) => { expect(res.body).toEqual({ bar: 'test', baz: 123 }); }); @@ -1110,7 +1030,7 @@ describe('Response factory', () => { baz: 'test', // Can't cast it into number }) .expect(400) - .then(res => { + .then((res) => { expect(res.body).toEqual({ error: 'Bad Request', message: '[request body.baz]: expected value of type [number] but got [string]', @@ -1135,9 +1055,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.body.message).toBe('no access'); expect(result.header['www-authenticate']).toBe('challenge'); @@ -1153,9 +1071,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.body.message).toBe('Unauthorized'); }); @@ -1171,9 +1087,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(403); + const result = await supertest(innerServer.listener).get('/').expect(403); expect(result.body.message).toBe('reason'); }); @@ -1188,9 +1102,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(403); + const result = await supertest(innerServer.listener).get('/').expect(403); expect(result.body.message).toBe('Forbidden'); }); @@ -1206,9 +1118,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(404); + const result = await supertest(innerServer.listener).get('/').expect(404); expect(result.body.message).toBe('file is not found'); }); @@ -1223,9 +1133,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(404); + const result = await supertest(innerServer.listener).get('/').expect(404); expect(result.body.message).toBe('Not Found'); }); @@ -1241,9 +1149,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(409); + const result = await supertest(innerServer.listener).get('/').expect(409); expect(result.body.message).toBe('stale version'); }); @@ -1258,9 +1164,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(409); + const result = await supertest(innerServer.listener).get('/').expect(409); expect(result.body.message).toBe('Conflict'); }); @@ -1279,9 +1183,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(418); + const result = await supertest(innerServer.listener).get('/').expect(418); expect(result.body).toEqual({ error: "I'm a teapot", @@ -1305,9 +1207,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body).toEqual({ error: 'Internal Server Error', @@ -1331,9 +1231,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body).toEqual({ error: 'Internal Server Error', @@ -1356,9 +1254,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body).toEqual({ error: 'Internal Server Error', @@ -1392,9 +1288,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(201); + const result = await supertest(innerServer.listener).get('/').expect(201); expect(result.header.location).toBe('somewhere'); }); @@ -1415,9 +1309,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(301); + const result = await supertest(innerServer.listener).get('/').expect(301); expect(result.header.location).toBe('/new-url'); }); @@ -1436,9 +1328,7 @@ describe('Response factory', () => { await server.start(); - await supertest(innerServer.listener) - .get('/') - .expect(500); + await supertest(innerServer.listener).get('/').expect(500); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` Array [ @@ -1463,9 +1353,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.body.message).toBe('unauthorized'); }); @@ -1486,9 +1374,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.body).toEqual({ error: 'Unauthorized', @@ -1514,9 +1400,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.body).toEqual({ error: 'Unauthorized', @@ -1540,9 +1424,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(401); + const result = await supertest(innerServer.listener).get('/').expect(401); expect(result.body.message).toBe('Unauthorized'); }); @@ -1560,9 +1442,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('reason'); expect(loggingServiceMock.collect(logger).error).toHaveLength(0); @@ -1581,9 +1461,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -1607,9 +1485,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -1632,9 +1508,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` @@ -1657,9 +1531,7 @@ describe('Response factory', () => { await server.start(); - const result = await supertest(innerServer.listener) - .get('/') - .expect(500); + const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts index 050881472bc80..9c8c6fba690d1 100644 --- a/src/core/server/http/lifecycle/on_pre_response.ts +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -147,7 +147,7 @@ function findHeadersIntersection( headers: ResponseHeaders, log: Logger ) { - Object.keys(headers).forEach(headerName => { + Object.keys(headers).forEach((headerName) => { if (Reflect.has(responseHeaders, headerName)) { log.warn(`onPreResponseHandler rewrote a response header [${headerName}].`); } diff --git a/src/core/server/http/prototype_pollution/validate_object.test.ts b/src/core/server/http/prototype_pollution/validate_object.test.ts index 9e23d6cec6444..23d6c4ae3b49f 100644 --- a/src/core/server/http/prototype_pollution/validate_object.test.ts +++ b/src/core/server/http/prototype_pollution/validate_object.test.ts @@ -51,8 +51,8 @@ test(`fails on circular references`, () => { }, { constructor: { foo: { prototype: null } } }, { prototype: { foo: { constructor: null } } }, -].forEach(value => { - ['headers', 'payload', 'query', 'params'].forEach(property => { +].forEach((value) => { + ['headers', 'payload', 'query', 'params'].forEach((property) => { const obj = { [property]: value, }; @@ -72,7 +72,7 @@ test(`fails on circular references`, () => { JSON.parse(`{ "constructor": { "prototype" : null } }`), JSON.parse(`{ "foo": { "constructor": { "prototype" : null } } }`), JSON.parse(`{ "foo": { "bar": { "constructor": { "prototype" : null } } } }`), -].forEach(value => { +].forEach((value) => { test(`can't submit ${JSON.stringify(value)}`, () => { expect(() => validateObject(value)).toThrowErrorMatchingSnapshot(); }); diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index af99812eff4b3..75d0e0d630296 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -20,7 +20,7 @@ import Boom from 'boom'; import { RequestHandlerWrapper } from './router'; -export const wrapErrors: RequestHandlerWrapper = handler => { +export const wrapErrors: RequestHandlerWrapper = (handler) => { return async (context, request, response) => { try { return await handler(context, request, response); diff --git a/src/core/server/http/router/headers.ts b/src/core/server/http/router/headers.ts index b79cc0d325f1e..f27f5e937b2c0 100644 --- a/src/core/server/http/router/headers.ts +++ b/src/core/server/http/router/headers.ts @@ -71,7 +71,7 @@ export function filterHeaders( // Normalize list of headers we want to allow in upstream request const fieldsToKeepNormalized = fieldsToKeep .map(normalizeHeaderField) - .filter(name => !fieldsToExcludeNormalized.includes(name)); + .filter((name) => !fieldsToExcludeNormalized.includes(name)); return pick(headers, fieldsToKeepNormalized); } diff --git a/src/core/server/http/router/router.mock.ts b/src/core/server/http/router/router.mock.ts index b43db0ca7ed5a..651d1712100ee 100644 --- a/src/core/server/http/router/router.mock.ts +++ b/src/core/server/http/router/router.mock.ts @@ -30,7 +30,7 @@ function create({ routerPath = '' }: { routerPath?: string } = {}): RouterMock { put: jest.fn(), patch: jest.fn(), getRoutes: jest.fn(), - handleLegacyErrors: jest.fn().mockImplementation(handler => handler), + handleLegacyErrors: jest.fn().mockImplementation((handler) => handler), }; } diff --git a/src/core/server/http/router/validator/validator.test.ts b/src/core/server/http/router/validator/validator.test.ts index e972e2075e705..30f66f5d41fbe 100644 --- a/src/core/server/http/router/validator/validator.test.ts +++ b/src/core/server/http/router/validator/validator.test.ts @@ -46,7 +46,7 @@ describe('Router validator', () => { it('should validate and infer the type from a function that does not use the resolver', () => { const validator = RouteValidator.from({ - params: data => { + params: (data) => { if (typeof data.foo === 'string') { return { value: { foo: data.foo as string } }; } @@ -112,7 +112,7 @@ describe('Router validator', () => { it('should catch the errors thrown by the validate function', () => { const validator = RouteValidator.from({ - params: data => { + params: (data) => { throw new Error('Something went terribly wrong'); }, }); diff --git a/src/core/server/http/ssl_config.ts b/src/core/server/http/ssl_config.ts index 4eb0c50e72362..75dc05d1a801b 100644 --- a/src/core/server/http/ssl_config.ts +++ b/src/core/server/http/ssl_config.ts @@ -65,7 +65,7 @@ export const sslSchema = schema.object( ), }, { - validate: ssl => { + validate: (ssl) => { if (ssl.key && ssl.keystore.path) { return 'cannot use [key] when [keystore.path] is specified'; } diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 4be7e59acb7b9..77e0d6b61692d 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -234,7 +234,7 @@ export interface HttpServiceSetup { * 'myApp', * (context, req) => { * async function search (id: string) { - * return await context.elasticsearch.adminClient.callAsInternalUser('endpoint', id); + * return await context.elasticsearch.legacy.client.callAsInternalUser('endpoint', id); * } * return { search }; * } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index cf999875b18f8..658c24f835020 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -338,10 +338,8 @@ export { * which uses the credentials of the incoming request * - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing * all the registered types. - * - {@link ScopedClusterClient | elasticsearch.dataClient} - Elasticsearch + * - {@link ScopedClusterClient | elasticsearch.legacy.client} - Elasticsearch * data client which uses the credentials of the incoming request - * - {@link ScopedClusterClient | elasticsearch.adminClient} - Elasticsearch - * admin client which uses the credentials of the incoming request * - {@link IUiSettingsClient | uiSettings.client} - uiSettings client * which uses the credentials of the incoming request * @@ -354,8 +352,9 @@ export interface RequestHandlerContext { typeRegistry: ISavedObjectTypeRegistry; }; elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; + legacy: { + client: IScopedClusterClient; + }; }; uiSettings: { client: IUiSettingsClient; diff --git a/src/core/server/legacy/config/ensure_valid_configuration.ts b/src/core/server/legacy/config/ensure_valid_configuration.ts index a68d3df577a89..5cd1603ea65fb 100644 --- a/src/core/server/legacy/config/ensure_valid_configuration.ts +++ b/src/core/server/legacy/config/ensure_valid_configuration.ts @@ -36,7 +36,7 @@ export async function ensureValidConfiguration( if (unusedConfigKeys.length > 0) { const message = `Unknown configuration key(s): ${unusedConfigKeys - .map(key => `"${key}"`) + .map((key) => `"${key}"`) .join(', ')}. Check for spelling errors and ensure that expected plugins are installed.`; throw new InvalidConfigurationError(message); } diff --git a/src/core/server/legacy/config/get_unused_config_keys.ts b/src/core/server/legacy/config/get_unused_config_keys.ts index 20c9776f63c58..6cd193d896109 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.ts @@ -72,8 +72,8 @@ export async function getUnusedConfigKeys({ // Filter out keys that are marked as used in the core (e.g. by new core plugins). return difference(inputKeys, appliedKeys).filter( - unusedConfigKey => - !coreHandledConfigPaths.some(usedInCoreConfigKey => + (unusedConfigKey) => + !coreHandledConfigPaths.some((usedInCoreConfigKey) => hasConfigPathIntersection(unusedConfigKey, usedInCoreConfigKey) ) ); diff --git a/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts b/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts index 8651d05064492..b09f9d00b3bed 100644 --- a/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts +++ b/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts @@ -27,7 +27,7 @@ jest.spyOn(configDeprecationFactory, 'unusedFromRoot'); jest.spyOn(configDeprecationFactory, 'renameFromRoot'); const executeHandlers = (handlers: ConfigDeprecation[]) => { - handlers.forEach(handler => { + handlers.forEach((handler) => { handler({}, '', () => null); }); }; @@ -99,7 +99,7 @@ describe('convertLegacyDeprecationProvider', () => { const migrated = applyDeprecations( rawConfig, - handlers.map(handler => ({ deprecation: handler, path: '' })) + handlers.map((handler) => ({ deprecation: handler, path: '' })) ); expect(migrated).toEqual({ new: 'oldvalue', goodValue: 'good' }); }); diff --git a/src/core/server/legacy/legacy_internals.test.ts b/src/core/server/legacy/legacy_internals.test.ts index dcab62627442b..2ae5e3a3fd1e8 100644 --- a/src/core/server/legacy/legacy_internals.test.ts +++ b/src/core/server/legacy/legacy_internals.test.ts @@ -84,7 +84,7 @@ describe('LegacyInternals', () => { jest.fn().mockResolvedValue({ is: 'merged-core' }), ]; - injectors.forEach(injector => legacyInternals.injectUiAppVars('core', injector)); + injectors.forEach((injector) => legacyInternals.injectUiAppVars('core', injector)); await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(` Object { @@ -136,10 +136,10 @@ describe('LegacyInternals', () => { it('gets: no default injectors, with injected vars replacers, with ui app injectors, no inject arg', async () => { uiExports.injectedVarsReplacers = [ - jest.fn(async vars => ({ ...vars, added: 'key' })), - jest.fn(vars => vars), - jest.fn(vars => ({ replaced: 'all' })), - jest.fn(async vars => ({ ...vars, added: 'last-key' })), + jest.fn(async (vars) => ({ ...vars, added: 'key' })), + jest.fn((vars) => vars), + jest.fn((vars) => ({ replaced: 'all' })), + jest.fn(async (vars) => ({ ...vars, added: 'last-key' })), ]; const request = httpServerMock.createRawRequest(); @@ -186,7 +186,7 @@ describe('LegacyInternals', () => { varsProvider({ gamma: 'gamma' }), varsProvider({ alpha: 'beta' }), ]; - uiExports.injectedVarsReplacers = [jest.fn(async vars => ({ ...vars, gamma: 'delta' }))]; + uiExports.injectedVarsReplacers = [jest.fn(async (vars) => ({ ...vars, gamma: 'delta' }))]; legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' })); legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' })); diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index a75f7dda302c2..d9a0ac5e4ecff 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -353,7 +353,7 @@ describe('once LegacyService is set up without connection info', () => { describe('once LegacyService is set up in `devClusterMaster` mode', () => { beforeEach(() => { - configService.atPath.mockImplementation(path => { + configService.atPath.mockImplementation((path) => { return new BehaviorSubject( path === 'dev' ? { basePathProxyTargetPort: 100500 } : { basePath: '/abc' } ); @@ -447,7 +447,7 @@ describe('#discoverPlugins()', () => { it(`register legacy plugin's deprecation providers`, async () => { findLegacyPluginSpecsMock.mockImplementation( - settings => + (settings) => Promise.resolve({ pluginSpecs: [ { @@ -486,7 +486,7 @@ describe('#discoverPlugins()', () => { { getId: () => 'pluginB', getDeprecationsProvider: () => undefined }, ]; findLegacyPluginSpecsMock.mockImplementation( - settings => + (settings) => Promise.resolve({ pluginSpecs, pluginExtendedConfig: settings, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index b95362e1ea26e..2ced8b4762406 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -91,7 +91,7 @@ export class LegacyService implements CoreService { this.log = logger.get('legacy-service'); this.devConfig$ = configService .atPath(devConfig.path) - .pipe(map(rawConfig => new DevConfig(rawConfig))); + .pipe(map((rawConfig) => new DevConfig(rawConfig))); this.httpConfig$ = combineLatest( configService.atPath(httpConfig.path), configService.atPath(cspConfig.path) @@ -108,7 +108,7 @@ export class LegacyService implements CoreService { this.kbnServer.applyLoggingConfiguration(getLegacyRawConfig(config, pathConfig)); } }), - tap({ error: err => this.log.error(err) }), + tap({ error: (err) => this.log.error(err) }), publishReplay(1) ) as ConnectableObservable<[Config, PathConfigType]>; @@ -146,14 +146,14 @@ export class LegacyService implements CoreService { }; const deprecationProviders = await pluginSpecs - .map(spec => spec.getDeprecationsProvider()) + .map((spec) => spec.getDeprecationsProvider()) .reduce(async (providers, current) => { if (current) { return [...(await providers), await convertLegacyDeprecationProvider(current)]; } return providers; }, Promise.resolve([] as ConfigDeprecationProvider[])); - deprecationProviders.forEach(provider => + deprecationProviders.forEach((provider) => this.coreContext.configService.addDeprecationProvider('', provider) ); @@ -279,9 +279,10 @@ export class LegacyService implements CoreService { capabilities: setupDeps.core.capabilities, context: setupDeps.core.context, elasticsearch: { - adminClient: setupDeps.core.elasticsearch.adminClient, - dataClient: setupDeps.core.elasticsearch.dataClient, - createClient: setupDeps.core.elasticsearch.createClient, + legacy: { + client: setupDeps.core.elasticsearch.legacy.client, + createClient: setupDeps.core.elasticsearch.legacy.createClient, + }, }, http: { createCookieSessionStorageFactory: setupDeps.core.http.createCookieSessionStorageFactory, @@ -352,7 +353,6 @@ export class LegacyService implements CoreService { uiPlugins: setupDeps.uiPlugins, elasticsearch: setupDeps.core.elasticsearch, rendering: setupDeps.core.rendering, - uiSettings: setupDeps.core.uiSettings, savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider, legacy: this.legacyInternals, }, diff --git a/src/core/server/legacy/merge_vars.ts b/src/core/server/legacy/merge_vars.ts index a1d43af2f861d..5d1820988e57a 100644 --- a/src/core/server/legacy/merge_vars.ts +++ b/src/core/server/legacy/merge_vars.ts @@ -25,9 +25,9 @@ export function mergeVars(...sources: LegacyVars[]): LegacyVars { return Object.assign( {}, ...sources, - ...ELIGIBLE_FLAT_MERGE_KEYS.flatMap(key => - sources.some(source => key in source) - ? [{ [key]: Object.assign({}, ...sources.map(source => source[key] || {})) }] + ...ELIGIBLE_FLAT_MERGE_KEYS.flatMap((key) => + sources.some((source) => key in source) + ? [{ [key]: Object.assign({}, ...sources.map((source) => source[key] || {})) }] : [] ) ); diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts index 44f02f0c90d4e..5039b3a55cc58 100644 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts @@ -63,14 +63,14 @@ export async function findLegacyPluginSpecs( const log$ = merge( pack$.pipe( - tap(definition => { + tap((definition) => { const path = definition.getPath(); logger.debug(`Found plugin at ${path}`, { path }); }) ), invalidDirectoryError$.pipe( - tap(error => { + tap((error) => { logger.warn(`Unable to scan directory for plugins "${error.path}"`, { err: error, dir: error.path, @@ -79,7 +79,7 @@ export async function findLegacyPluginSpecs( ), invalidPackError$.pipe( - tap(error => { + tap((error) => { logger.warn(`Skipping non-plugin directory at ${error.path}`, { path: error.path, }); @@ -87,21 +87,21 @@ export async function findLegacyPluginSpecs( ), otherError$.pipe( - tap(error => { + tap((error) => { // rethrow unhandled errors, which will fail the server throw error; }) ), invalidVersionSpec$.pipe( - map(spec => { + map((spec) => { const name = spec.getId(); const pluginVersion = spec.getExpectedKibanaVersion(); const kibanaVersion = packageInfo.version; return `Plugin "${name}" was disabled because it expected Kibana version "${pluginVersion}", and found "${kibanaVersion}".`; }), distinct(), - tap(message => { + tap((message) => { logger.warn(message); }) ), diff --git a/src/core/server/legacy/plugins/get_nav_links.test.ts b/src/core/server/legacy/plugins/get_nav_links.test.ts index 5e84f27acabd5..af10706d0ea08 100644 --- a/src/core/server/legacy/plugins/get_nav_links.test.ts +++ b/src/core/server/legacy/plugins/get_nav_links.test.ts @@ -40,7 +40,7 @@ const createLegacyExports = ({ const createPluginSpecs = (...ids: string[]): LegacyPluginSpec[] => ids.map( - id => + (id) => ({ getId: () => id, } as LegacyPluginSpec) diff --git a/src/core/server/legacy/plugins/get_nav_links.ts b/src/core/server/legacy/plugins/get_nav_links.ts index 067fb204ca7f3..b1d22df41e345 100644 --- a/src/core/server/legacy/plugins/get_nav_links.ts +++ b/src/core/server/legacy/plugins/get_nav_links.ts @@ -66,11 +66,11 @@ function isHidden(app: LegacyAppSpec) { export function getNavLinks(uiExports: LegacyUiExports, pluginSpecs: LegacyPluginSpec[]) { const navLinkSpecs = uiExports.navLinkSpecs || []; const appSpecs = (uiExports.uiAppSpecs || []).filter( - app => app !== undefined && !isHidden(app) + (app) => app !== undefined && !isHidden(app) ) as LegacyAppSpec[]; - const pluginIds = (pluginSpecs || []).map(spec => spec.getId()); - appSpecs.forEach(spec => { + const pluginIds = (pluginSpecs || []).map((spec) => spec.getId()); + appSpecs.forEach((spec) => { if (spec.pluginId && !pluginIds.includes(spec.pluginId)) { throw new Error(`Unknown plugin id "${spec.pluginId}"`); } diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts index df86f5a2b4031..4a4a1b1b0e60b 100644 --- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts +++ b/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts @@ -36,7 +36,7 @@ export const logLegacyThirdPartyPluginDeprecationWarning = ({ }) => { const thirdPartySpecs = specs.filter(isThirdPartyPluginSpec); if (thirdPartySpecs.length > 0) { - const pluginIds = thirdPartySpecs.map(spec => spec.getId()); + const pluginIds = thirdPartySpecs.map((spec) => spec.getId()); log.warn( `Some installed third party plugin(s) [${pluginIds.join( ', ' @@ -49,5 +49,5 @@ export const logLegacyThirdPartyPluginDeprecationWarning = ({ const isThirdPartyPluginSpec = (spec: LegacyPluginSpec): boolean => { const pluginPath = spec.getPack().getPath(); - return !internalPaths.some(internalPath => pluginPath.indexOf(internalPath) > -1); + return !internalPaths.some((internalPath) => pluginPath.indexOf(internalPath) > -1); }; diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 2567ca790e04f..98f8d874c7088 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -151,7 +151,7 @@ export type LegacyAppSpec = Partial & { * @internal * @deprecated */ -export type LegacyNavLink = Omit & { +export type LegacyNavLink = Omit & { order: number; }; diff --git a/src/core/server/logging/appenders/file/file_appender.test.ts b/src/core/server/logging/appenders/file/file_appender.test.ts index 0483a06b199b6..bff60029faf11 100644 --- a/src/core/server/logging/appenders/file/file_appender.test.ts +++ b/src/core/server/logging/appenders/file/file_appender.test.ts @@ -23,7 +23,7 @@ import { LogLevel } from '../../log_level'; import { LogRecord } from '../../log_record'; import { FileAppender } from './file_appender'; -const tickMs = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +const tickMs = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); beforeEach(() => { mockCreateWriteStream.mockReset(); diff --git a/src/core/server/logging/appenders/file/file_appender.ts b/src/core/server/logging/appenders/file/file_appender.ts index 3aca59fb3f42c..728e82ebcec9a 100644 --- a/src/core/server/logging/appenders/file/file_appender.ts +++ b/src/core/server/logging/appenders/file/file_appender.ts @@ -66,7 +66,7 @@ export class FileAppender implements DisposableAppender { * Disposes `FileAppender`. Waits for the underlying file stream to be completely flushed and closed. */ public async dispose() { - await new Promise(resolve => { + await new Promise((resolve) => { if (this.outputStream === undefined) { return resolve(); } diff --git a/src/core/server/logging/integration_tests/utils.ts b/src/core/server/logging/integration_tests/utils.ts index 81a76ce76ad73..e4c2c8866cb92 100644 --- a/src/core/server/logging/integration_tests/utils.ts +++ b/src/core/server/logging/integration_tests/utils.ts @@ -55,7 +55,7 @@ export async function getPlatformLogsFromFile(path: string) { const fileContent = await readFile(path, 'utf-8'); return fileContent .split('\n') - .map(s => normalizePlatformLogging(s)) + .map((s) => normalizePlatformLogging(s)) .join('\n'); } @@ -63,6 +63,6 @@ export async function getLegacyPlatformLogsFromFile(path: string) { const fileContent = await readFile(path, 'utf-8'); return fileContent .split('\n') - .map(s => normalizeLegacyPlatformLogging(s)) + .map((s) => normalizeLegacyPlatformLogging(s)) .join('\n'); } diff --git a/src/core/server/logging/layouts/pattern_layout.ts b/src/core/server/logging/layouts/pattern_layout.ts index 9490db149cc0f..7839345a3703b 100644 --- a/src/core/server/logging/layouts/pattern_layout.ts +++ b/src/core/server/logging/layouts/pattern_layout.ts @@ -37,7 +37,7 @@ import { const DEFAULT_PATTERN = `[%date][%level][%logger]%meta %message`; export const patternSchema = schema.string({ - validate: string => { + validate: (string) => { DateConversion.validate!(string); }, }); diff --git a/src/core/server/logging/logging_config.ts b/src/core/server/logging/logging_config.ts index 8f80be7d79cb1..772909ce584e5 100644 --- a/src/core/server/logging/logging_config.ts +++ b/src/core/server/logging/logging_config.ts @@ -167,13 +167,13 @@ export class LoggingConfig { ]; const loggerConfigByContext = new Map( - loggers.map(loggerConfig => toTuple(loggerConfig.context, loggerConfig)) + loggers.map((loggerConfig) => toTuple(loggerConfig.context, loggerConfig)) ); for (const [loggerContext, loggerConfig] of loggerConfigByContext) { // Ensure logger config only contains valid appenders. const unsupportedAppenderKey = loggerConfig.appenders.find( - appenderKey => !this.appenders.has(appenderKey) + (appenderKey) => !this.appenders.has(appenderKey) ); if (unsupportedAppenderKey) { diff --git a/src/core/server/logging/logging_service.ts b/src/core/server/logging/logging_service.ts index f9535e6c8283e..2e6f895724122 100644 --- a/src/core/server/logging/logging_service.ts +++ b/src/core/server/logging/logging_service.ts @@ -108,7 +108,7 @@ export class LoggingService implements LoggerFactory { const { level, appenders } = this.getLoggerConfigByContext(config, context); const loggerLevel = LogLevel.fromId(level); - const loggerAppenders = appenders.map(appenderKey => this.appenders.get(appenderKey)!); + const loggerAppenders = appenders.map((appenderKey) => this.appenders.get(appenderKey)!); return new BaseLogger(context, loggerLevel, loggerAppenders, this.asLoggerFactory()); } diff --git a/src/core/server/metrics/collectors/process.ts b/src/core/server/metrics/collectors/process.ts index a3b59a7cc8b7c..b020eebcbbd0b 100644 --- a/src/core/server/metrics/collectors/process.ts +++ b/src/core/server/metrics/collectors/process.ts @@ -46,7 +46,7 @@ export class ProcessMetricsCollector implements MetricsCollector => { const bench = new Bench(); - return new Promise(resolve => { + return new Promise((resolve) => { setImmediate(() => { return resolve(bench.elapsed()); }); diff --git a/src/core/server/metrics/collectors/server.ts b/src/core/server/metrics/collectors/server.ts index 84204d0466ff3..036332c24c34f 100644 --- a/src/core/server/metrics/collectors/server.ts +++ b/src/core/server/metrics/collectors/server.ts @@ -45,7 +45,7 @@ export class ServerMetricsCollector implements MetricsCollector { + this.server.events.on('response', (request) => { const statusCode = (request.response as ResponseObject)?.statusCode; if (statusCode) { if (!this.requests.statusCodes[statusCode]) { @@ -62,7 +62,7 @@ export class ServerMetricsCollector implements MetricsCollector { - const connections = await new Promise(resolve => { + const connections = await new Promise((resolve) => { this.server.listener.getConnections((_, count) => { resolve(count); }); diff --git a/src/core/server/metrics/integration_tests/server_collector.test.ts b/src/core/server/metrics/integration_tests/server_collector.test.ts index 3b982a06cf06c..4476b3c26a2e1 100644 --- a/src/core/server/metrics/integration_tests/server_collector.test.ts +++ b/src/core/server/metrics/integration_tests/server_collector.test.ts @@ -34,7 +34,7 @@ describe('ServerMetricsCollector', () => { let hapiServer: HapiServer; let router: IRouter; - const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const sendGet = (path: string) => supertest(hapiServer.listener).get(path); beforeEach(async () => { @@ -81,7 +81,7 @@ describe('ServerMetricsCollector', () => { }); it('collect disconnects requests infos', async () => { - const never = new Promise(resolve => undefined); + const never = new Promise((resolve) => undefined); const hitSubject = new BehaviorSubject(0); router.get({ path: '/', validate: false }, async (ctx, req, res) => { @@ -100,7 +100,7 @@ describe('ServerMetricsCollector', () => { await hitSubject .pipe( - filter(count => count >= 2), + filter((count) => count >= 2), take(1) ) .toPromise(); @@ -177,7 +177,7 @@ describe('ServerMetricsCollector', () => { const waitForHits = (hits: number) => hitSubject .pipe( - filter(count => count >= hits), + filter((count) => count >= hits), take(1) ) .toPromise(); @@ -189,12 +189,12 @@ describe('ServerMetricsCollector', () => { // however in this test we need to send the request now and await for it later in the code. // also using `.end` is not possible as it would execute the request twice. // so the only option is this noop `.then`. - const res1 = sendGet('/').then(res => res); + const res1 = sendGet('/').then((res) => res); await waitForHits(1); metrics = await collector.collect(); expect(metrics.concurrent_connections).toEqual(1); - const res2 = sendGet('/').then(res => res); + const res2 = sendGet('/').then((res) => res); await waitForHits(2); metrics = await collector.collect(); expect(metrics.concurrent_connections).toEqual(2); diff --git a/src/core/server/metrics/metrics_service.test.ts b/src/core/server/metrics/metrics_service.test.ts index f6334cc5d3c0f..01a7429745cda 100644 --- a/src/core/server/metrics/metrics_service.test.ts +++ b/src/core/server/metrics/metrics_service.test.ts @@ -82,10 +82,7 @@ describe('MetricsService', () => { // however the `reset` call is executed after the async call to `collect` // meaning that we are going to miss the call if we don't wait for the // actual observable emission that is performed after - const waitForNextEmission = () => - getOpsMetrics$() - .pipe(take(1)) - .toPromise(); + const waitForNextEmission = () => getOpsMetrics$().pipe(take(1)).toPromise(); expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1); expect(mockOpsCollector.reset).toHaveBeenCalledTimes(1); diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c707fa2b479e4..b6e9ffef6f3f1 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -101,6 +101,7 @@ function pluginInitializerContextMock(config: T = {} as T) { } type CoreSetupMockType = MockedKeys & { + elasticsearch: ReturnType; getStartServices: jest.MockedFunction>; }; @@ -199,8 +200,9 @@ function createCoreRequestHandlerContextMock() { typeRegistry: savedObjectsTypeRegistryMock.create(), }, elasticsearch: { - adminClient: elasticsearchServiceMock.createScopedClusterClient(), - dataClient: elasticsearchServiceMock.createScopedClusterClient(), + legacy: { + client: elasticsearchServiceMock.createScopedClusterClient(), + }, }, uiSettings: { client: uiSettingsServiceMock.createClient(), diff --git a/src/core/server/path/index.ts b/src/core/server/path/index.ts index ef8a3caeefa2c..2e05e3856bd4c 100644 --- a/src/core/server/path/index.ts +++ b/src/core/server/path/index.ts @@ -28,7 +28,6 @@ const CONFIG_PATHS = [ process.env.KIBANA_PATH_CONF && join(process.env.KIBANA_PATH_CONF, 'kibana.yml'), process.env.CONFIG_PATH, // deprecated fromRoot('config/kibana.yml'), - '/etc/kibana/kibana.yml', ].filter(isString); const DATA_PATHS = [ @@ -38,7 +37,7 @@ const DATA_PATHS = [ ].filter(isString); function findFile(paths: string[]) { - const availablePath = paths.find(configPath => { + const availablePath = paths.find((configPath) => { try { accessSync(configPath, constants.R_OK); return true; diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index 573109c9db35a..27c3ca5a71e16 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -154,7 +154,9 @@ export async function parseManifest(pluginPath: string, packageInfo: PackageInfo ); } - const unknownManifestKeys = Object.keys(manifest).filter(key => !KNOWN_MANIFEST_FIELDS.has(key)); + const unknownManifestKeys = Object.keys(manifest).filter( + (key) => !KNOWN_MANIFEST_FIELDS.has(key) + ); if (unknownManifestKeys.length > 0) { throw PluginDiscoveryError.invalidManifest( manifestPath, diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index 2902aafdbf146..73f274957cbc4 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -143,7 +143,7 @@ test('properly iterates through plugin search locations', async () => { resolve(TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, '6'), TEST_EXTRA_PLUGIN_PATH, ]) { - const discoveredPlugin = plugins.find(plugin => plugin.path === path)!; + const discoveredPlugin = plugins.find((plugin) => plugin.path === path)!; expect(discoveredPlugin).toBeInstanceOf(PluginWrapper); expect(discoveredPlugin.configPath).toEqual(['core', 'config']); expect(discoveredPlugin.requiredPlugins).toEqual(['a', 'b']); @@ -153,7 +153,7 @@ test('properly iterates through plugin search locations', async () => { await expect( error$ .pipe( - map(error => error.toString()), + map((error) => error.toString()), toArray() ) .toPromise() diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/src/core/server/plugins/discovery/plugins_discovery.ts index e7f82c9dc15ad..1910483211e34 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.ts @@ -56,7 +56,7 @@ export function discover(config: PluginsConfig, coreContext: CoreContext) { from(config.additionalPluginPaths), processPluginSearchPaths$(config.pluginSearchPaths, log) ).pipe( - mergeMap(pluginPathOrError => { + mergeMap((pluginPathOrError) => { return typeof pluginPathOrError === 'string' ? createPlugin$(pluginPathOrError, log, coreContext) : [pluginPathOrError]; @@ -83,21 +83,21 @@ export function discover(config: PluginsConfig, coreContext: CoreContext) { */ function processPluginSearchPaths$(pluginDirs: readonly string[], log: Logger) { return from(pluginDirs).pipe( - mergeMap(dir => { + mergeMap((dir) => { log.debug(`Scanning "${dir}" for plugin sub-directories...`); return fsReadDir$(dir).pipe( - mergeMap((subDirs: string[]) => subDirs.map(subDir => resolve(dir, subDir))), - mergeMap(path => + mergeMap((subDirs: string[]) => subDirs.map((subDir) => resolve(dir, subDir))), + mergeMap((path) => fsStat$(path).pipe( // Filter out non-directory entries from target directories, it's expected that // these directories may contain files (e.g. `README.md` or `package.json`). // We shouldn't silently ignore the entries we couldn't get stat for though. - mergeMap(pathStat => (pathStat.isDirectory() ? [path] : [])), - catchError(err => [PluginDiscoveryError.invalidPluginPath(path, err)]) + mergeMap((pathStat) => (pathStat.isDirectory() ? [path] : [])), + catchError((err) => [PluginDiscoveryError.invalidPluginPath(path, err)]) ) ), - catchError(err => [PluginDiscoveryError.invalidSearchPath(dir, err)]) + catchError((err) => [PluginDiscoveryError.invalidSearchPath(dir, err)]) ); }) ); @@ -113,7 +113,7 @@ function processPluginSearchPaths$(pluginDirs: readonly string[], log: Logger) { */ function createPlugin$(path: string, log: Logger, coreContext: CoreContext) { return from(parseManifest(path, coreContext.env.packageInfo, log)).pipe( - map(manifest => { + map((manifest) => { log.debug(`Successfully discovered plugin "${manifest.id}" at "${path}"`); const opaqueId = Symbol(manifest.id); return new PluginWrapper({ @@ -123,6 +123,6 @@ function createPlugin$(path: string, log: Logger, coreContext: CoreContext) { initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest), }); }), - catchError(err => [err]) + catchError((err) => [err]) ); } diff --git a/src/core/server/plugins/integration_tests/plugins_service.test.ts b/src/core/server/plugins/integration_tests/plugins_service.test.ts index 1521fc332bcdb..04f570cca489b 100644 --- a/src/core/server/plugins/integration_tests/plugins_service.test.ts +++ b/src/core/server/plugins/integration_tests/plugins_service.test.ts @@ -139,7 +139,7 @@ describe('PluginsService', () => { }, start: async (core, plugins) => { contextFromStart = { core, plugins }; - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise((resolve) => setTimeout(resolve, 10)); expect(startDependenciesResolved).toBe(false); return pluginStartContract; }, diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 1e4d94dd00d0d..8d82d96f949c7 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -260,7 +260,7 @@ test("`start` resolves `startDependencies` Promise after plugin's start", async setup: jest.fn(), start: async () => { // delay to ensure startDependencies is not resolved until after the plugin instance's start resolves. - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise((resolve) => setTimeout(resolve, 10)); expect(startDependenciesResolved).toBe(false); return pluginStartContract; }, @@ -269,7 +269,7 @@ test("`start` resolves `startDependencies` Promise after plugin's start", async await plugin.setup({} as any, {} as any); - const startDependenciesCheck = plugin.startDependencies.then(resolvedStartDeps => { + const startDependenciesCheck = plugin.startDependencies.then((resolvedStartDeps) => { startDependenciesResolved = true; expect(resolvedStartDeps).toEqual([startContext, pluginDeps, pluginStartContract]); }); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index ab18a9cbbc062..7afb607192cae 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -147,9 +147,7 @@ export function createPluginSetupContext( createContextContainer: deps.context.createContextContainer, }, elasticsearch: { - adminClient: deps.elasticsearch.adminClient, - dataClient: deps.elasticsearch.dataClient, - createClient: deps.elasticsearch.createClient, + legacy: deps.elasticsearch.legacy, }, http: { createCookieSessionStorageFactory: deps.http.createCookieSessionStorageFactory, diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 38fda12bd290f..6f8d15838641f 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -51,7 +51,7 @@ const logger = loggingServiceMock.create(); expect.addSnapshotSerializer(createAbsolutePathSerializer()); -['path-1', 'path-2', 'path-3', 'path-4', 'path-5'].forEach(path => { +['path-1', 'path-2', 'path-3', 'path-4', 'path-5'].forEach((path) => { jest.doMock(join(path, 'server'), () => ({}), { virtual: true, }); @@ -200,7 +200,7 @@ describe('PluginsService', () => { it('properly detects plugins that should be disabled.', async () => { jest .spyOn(configService, 'isEnabledAtPath') - .mockImplementation(path => Promise.resolve(!path.includes('disabled'))); + .mockImplementation((path) => Promise.resolve(!path.includes('disabled'))); mockPluginSystem.setupPlugins.mockResolvedValue(new Map()); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index d7a348affe94f..7441e753efa6a 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -87,7 +87,7 @@ export class PluginsService implements CoreService('plugins') - .pipe(map(rawConfig => new PluginsConfig(rawConfig, coreContext.env))); + .pipe(map((rawConfig) => new PluginsConfig(rawConfig, coreContext.env))); } public async discover() { @@ -153,7 +153,7 @@ export class PluginsService implements CoreService exposed) + Object.values(configDescriptor?.exposeToBrowser).some((exposed) => exposed) ); }) .map(([pluginId, plugin]) => { @@ -186,14 +186,14 @@ export class PluginsService implements CoreService errorTypesToReport.includes(error.type)), - tap(pluginError => this.log.error(pluginError)), + filter((error) => errorTypesToReport.includes(error.type)), + tap((pluginError) => this.log.error(pluginError)), toArray() ) .toPromise(); if (errors.length > 0) { throw new Error( - `Failed to initialize plugins:${errors.map(err => `\n\t${err.message}`).join('')}` + `Failed to initialize plugins:${errors.map((err) => `\n\t${err.message}`).join('')}` ); } } @@ -205,7 +205,7 @@ export class PluginsService implements CoreService(); await plugin$ .pipe( - mergeMap(async plugin => { + mergeMap(async (plugin) => { const configDescriptor = plugin.getConfigDescriptor(); if (configDescriptor) { this.pluginConfigDescriptors.set(plugin.name, configDescriptor); @@ -263,8 +263,8 @@ export class PluginsService implements CoreService !parents.includes(dep)) - .every(dependencyName => + .filter((dep) => !parents.includes(dep)) + .every((dependencyName) => this.shouldEnablePlugin(dependencyName, pluginEnableStatuses, [...parents, pluginName]) ) ); diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 22dfbeecbaedd..8b318ad1b735e 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -342,7 +342,7 @@ test('`uiPlugins` returns ordered Maps of all plugin manifests', async () => { ], ] as Array<[PluginWrapper, Record]>); - [...plugins.keys()].forEach(plugin => { + [...plugins.keys()].forEach((plugin) => { pluginsSystem.addPlugin(plugin); }); @@ -371,7 +371,7 @@ test('`uiPlugins` returns only ui plugin dependencies', async () => { createPlugin('opt-no-ui', { ui: false, server: true }), ]; - plugins.forEach(plugin => { + plugins.forEach((plugin) => { pluginsSystem.addPlugin(plugin); }); @@ -424,7 +424,7 @@ describe('setup', () => { }); it('throws timeout error if "setup" was not completed in 30 sec.', async () => { const plugin: PluginWrapper = createPlugin('timeout-setup'); - jest.spyOn(plugin, 'setup').mockImplementation(() => new Promise(i => i)); + jest.spyOn(plugin, 'setup').mockImplementation(() => new Promise((i) => i)); pluginsSystem.addPlugin(plugin); mockCreatePluginSetupContext.mockImplementation(() => ({})); @@ -447,7 +447,7 @@ describe('start', () => { it('throws timeout error if "start" was not completed in 30 sec.', async () => { const plugin: PluginWrapper = createPlugin('timeout-start'); jest.spyOn(plugin, 'setup').mockResolvedValue({}); - jest.spyOn(plugin, 'start').mockImplementation(() => new Promise(i => i)); + jest.spyOn(plugin, 'start').mockImplementation(() => new Promise((i) => i)); pluginsSystem.addPlugin(plugin); mockCreatePluginSetupContext.mockImplementation(() => ({})); diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index dd2df7c8e01d1..e0401006ffac9 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -53,9 +53,9 @@ export class PluginsSystem { [ ...new Set([ ...plugin.requiredPlugins, - ...plugin.optionalPlugins.filter(optPlugin => this.plugins.has(optPlugin)), + ...plugin.optionalPlugins.filter((optPlugin) => this.plugins.has(optPlugin)), ]), - ].map(depId => this.plugins.get(depId)!.opaqueId), + ].map((depId) => this.plugins.get(depId)!.opaqueId), ]) ); } @@ -161,18 +161,22 @@ export class PluginsSystem { */ public uiPlugins() { const uiPluginNames = [...this.getTopologicallySortedPluginNames().keys()].filter( - pluginName => this.plugins.get(pluginName)!.includesUiPlugin + (pluginName) => this.plugins.get(pluginName)!.includesUiPlugin ); const publicPlugins = new Map( - uiPluginNames.map(pluginName => { + uiPluginNames.map((pluginName) => { const plugin = this.plugins.get(pluginName)!; return [ pluginName, { id: pluginName, configPath: plugin.manifest.configPath, - requiredPlugins: plugin.manifest.requiredPlugins.filter(p => uiPluginNames.includes(p)), - optionalPlugins: plugin.manifest.optionalPlugins.filter(p => uiPluginNames.includes(p)), + requiredPlugins: plugin.manifest.requiredPlugins.filter((p) => + uiPluginNames.includes(p) + ), + optionalPlugins: plugin.manifest.optionalPlugins.filter((p) => + uiPluginNames.includes(p) + ), }, ]; }) @@ -200,7 +204,7 @@ export class PluginsSystem { pluginName, new Set([ ...plugin.requiredPlugins, - ...plugin.optionalPlugins.filter(dependency => this.plugins.has(dependency)), + ...plugin.optionalPlugins.filter((dependency) => this.plugins.has(dependency)), ]), ] as [PluginName, Set]; }) @@ -209,7 +213,7 @@ export class PluginsSystem { // First, find a list of "start nodes" which have no outgoing edges. At least // one such node must exist in a non-empty acyclic graph. const pluginsWithAllDependenciesSorted = [...pluginsDependenciesGraph.keys()].filter( - pluginName => pluginsDependenciesGraph.get(pluginName)!.size === 0 + (pluginName) => pluginsDependenciesGraph.get(pluginName)!.size === 0 ); const sortedPluginNames = new Set(); diff --git a/src/core/server/rendering/views/fonts.tsx b/src/core/server/rendering/views/fonts.tsx index e87179920202a..8e460a150439d 100644 --- a/src/core/server/rendering/views/fonts.tsx +++ b/src/core/server/rendering/views/fonts.tsx @@ -249,7 +249,7 @@ export const Fonts: FunctionComponent = ({ url }) => { .flatMap(({ family, variants }) => variants.map(({ style, weight, format, sources, unicodeRange }) => { const src = sources - .map(source => + .map((source) => source.startsWith(url) ? `url('${source}') format('${format || source.split('.').pop()}')` : `local('${source}')` diff --git a/src/core/server/root/index.test.ts b/src/core/server/root/index.test.ts index 3b187aac022c3..5b853903ea4be 100644 --- a/src/core/server/root/index.test.ts +++ b/src/core/server/root/index.test.ts @@ -183,7 +183,7 @@ test('stops services if consequent logger upgrade fails', async () => { // Wait for shutdown to be called. await onShutdown .pipe( - filter(e => e !== null), + filter((e) => e !== null), first() ) .toPromise(); diff --git a/src/core/server/root/index.ts b/src/core/server/root/index.ts index eecc6399366dc..d6d0c641e00b0 100644 --- a/src/core/server/root/index.ts +++ b/src/core/server/root/index.ts @@ -99,10 +99,10 @@ export class Root { const update$ = configService.getConfig$().pipe( // always read the logging config when the underlying config object is re-read switchMap(() => configService.atPath('logging')), - map(config => this.loggingService.upgrade(config)), + map((config) => this.loggingService.upgrade(config)), // This specifically console.logs because we were not able to configure the logger. // eslint-disable-next-line no-console - tap({ error: err => console.error('Configuring logger failed:', err) }), + tap({ error: (err) => console.error('Configuring logger failed:', err) }), publishReplay(1) ) as ConnectableObservable; @@ -112,7 +112,7 @@ export class Root { // Send subsequent update failures to this.shutdown(), stopped via loggingConfigSubscription. this.loggingConfigSubscription = update$.subscribe({ - error: err => this.shutdown(err), + error: (err) => this.shutdown(err), }); // Add subscription we got from `connect` so that we can dispose both of them diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts index a703c9f9fbd96..cafaa5a3147db 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -95,7 +95,7 @@ async function fetchObjectsToExport({ throw Boom.badRequest(`Can't specify both "search" and "objects" properties when exporting`); } const bulkGetResult = await savedObjectsClient.bulkGet(objects, { namespace }); - const erroredObjects = bulkGetResult.saved_objects.filter(obj => !!obj.error); + const erroredObjects = bulkGetResult.saved_objects.filter((obj) => !!obj.error); if (erroredObjects.length) { const err = Boom.badRequest(); err.output.payload.attributes = { diff --git a/src/core/server/saved_objects/export/inject_nested_depdendencies.ts b/src/core/server/saved_objects/export/inject_nested_depdendencies.ts index d00650926e57a..1f8c645340dbf 100644 --- a/src/core/server/saved_objects/export/inject_nested_depdendencies.ts +++ b/src/core/server/saved_objects/export/inject_nested_depdendencies.ts @@ -51,8 +51,10 @@ export async function fetchNestedDependencies( } const allObjects = [...savedObjectsMap.values()]; return { - objects: allObjects.filter(obj => !obj.error), - missingRefs: allObjects.filter(obj => !!obj.error).map(obj => ({ type: obj.type, id: obj.id })), + objects: allObjects.filter((obj) => !obj.error), + missingRefs: allObjects + .filter((obj) => !!obj.error) + .map((obj) => ({ type: obj.type, id: obj.id })), }; } diff --git a/src/core/server/saved_objects/export/sort_objects.ts b/src/core/server/saved_objects/export/sort_objects.ts index 522146737d9cf..64bab9f43bf14 100644 --- a/src/core/server/saved_objects/export/sort_objects.ts +++ b/src/core/server/saved_objects/export/sort_objects.ts @@ -24,7 +24,7 @@ export function sortObjects(savedObjects: SavedObject[]): SavedObject[] { const path = new Set(); const sorted = new Set(); const objectsByTypeId = new Map( - savedObjects.map(object => [`${object.type}:${object.id}`, object] as [string, SavedObject]) + savedObjects.map((object) => [`${object.type}:${object.id}`, object] as [string, SavedObject]) ); function includeObjects(objects: SavedObject[]) { @@ -32,13 +32,13 @@ export function sortObjects(savedObjects: SavedObject[]): SavedObject[] { if (path.has(object)) { throw Boom.badRequest( `circular reference: ${[...path, object] - .map(obj => `[${obj.type}:${obj.id}]`) + .map((obj) => `[${obj.type}:${obj.id}]`) .join(' ref-> ')}` ); } const refdObjects = object.references - .map(ref => objectsByTypeId.get(`${ref.type}:${ref.id}`)) + .map((ref) => objectsByTypeId.get(`${ref.type}:${ref.id}`)) .filter((ref): ref is SavedObject => !!ref); if (refdObjects.length) { diff --git a/src/core/server/saved_objects/import/collect_saved_objects.ts b/src/core/server/saved_objects/import/collect_saved_objects.ts index 1a8ede41d0b2c..1b787c7d9dc10 100644 --- a/src/core/server/saved_objects/import/collect_saved_objects.ts +++ b/src/core/server/saved_objects/import/collect_saved_objects.ts @@ -45,7 +45,7 @@ export async function collectSavedObjects({ const collectedObjects: Array> = await createPromiseFromStreams([ readStream, createLimitStream(objectLimit), - createFilterStream>(obj => { + createFilterStream>((obj) => { if (supportedTypes.includes(obj.type)) { return true; } @@ -59,7 +59,7 @@ export async function collectSavedObjects({ }); return false; }), - createFilterStream(obj => (filter ? filter(obj) : true)), + createFilterStream((obj) => (filter ? filter(obj) : true)), createMapStream((obj: SavedObject) => { // Ensure migrations execute on every saved object return Object.assign({ migrationVersion: {} }, obj); diff --git a/src/core/server/saved_objects/import/create_objects_filter.ts b/src/core/server/saved_objects/import/create_objects_filter.ts index c48cded00f1ec..55b8ab128d753 100644 --- a/src/core/server/saved_objects/import/create_objects_filter.ts +++ b/src/core/server/saved_objects/import/create_objects_filter.ts @@ -21,7 +21,7 @@ import { SavedObject } from '../types'; import { SavedObjectsImportRetry } from './types'; export function createObjectsFilter(retries: SavedObjectsImportRetry[]) { - const retryKeys = new Set(retries.map(retry => `${retry.type}:${retry.id}`)); + const retryKeys = new Set(retries.map((retry) => `${retry.type}:${retry.id}`)); return (obj: SavedObject) => { return retryKeys.has(`${obj.type}:${obj.id}`); }; diff --git a/src/core/server/saved_objects/import/import_saved_objects.test.ts b/src/core/server/saved_objects/import/import_saved_objects.test.ts index b43e5063c13e1..e204cd7bddfc7 100644 --- a/src/core/server/saved_objects/import/import_saved_objects.test.ts +++ b/src/core/server/saved_objects/import/import_saved_objects.test.ts @@ -95,7 +95,7 @@ describe('importSavedObjects()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); @@ -178,7 +178,7 @@ describe('importSavedObjects()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); @@ -262,7 +262,7 @@ describe('importSavedObjects()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); @@ -345,13 +345,13 @@ describe('importSavedObjects()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); savedObjectsClient.find.mockResolvedValueOnce(emptyResponse); savedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: savedObjects.map(savedObject => ({ + saved_objects: savedObjects.map((savedObject) => ({ type: savedObject.type, id: savedObject.id, error: { @@ -527,7 +527,7 @@ describe('importSavedObjects()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push({ id: '1', type: 'wigwags', attributes: { title: 'my title' }, references: [] }); this.push(null); }, diff --git a/src/core/server/saved_objects/import/import_saved_objects.ts b/src/core/server/saved_objects/import/import_saved_objects.ts index cb1d70e5c8dc4..6065e03fb1628 100644 --- a/src/core/server/saved_objects/import/import_saved_objects.ts +++ b/src/core/server/saved_objects/import/import_saved_objects.ts @@ -78,7 +78,7 @@ export async function importSavedObjectsFromStream({ return { success: errorAccumulator.length === 0, - successCount: bulkCreateResult.saved_objects.filter(obj => !obj.error).length, + successCount: bulkCreateResult.saved_objects.filter((obj) => !obj.error).length, ...(errorAccumulator.length ? { errors: errorAccumulator } : {}), }; } diff --git a/src/core/server/saved_objects/import/resolve_import_errors.test.ts b/src/core/server/saved_objects/import/resolve_import_errors.test.ts index 2c6d89e0a0a47..54ebecc7dca70 100644 --- a/src/core/server/saved_objects/import/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/import/resolve_import_errors.test.ts @@ -73,7 +73,7 @@ describe('resolveImportErrors()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); @@ -100,12 +100,12 @@ describe('resolveImportErrors()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); savedObjectsClient.bulkCreate.mockResolvedValueOnce({ - saved_objects: savedObjects.filter(obj => obj.type === 'visualization' && obj.id === '3'), + saved_objects: savedObjects.filter((obj) => obj.type === 'visualization' && obj.id === '3'), }); const result = await resolveSavedObjectsImportErrors({ readStream, @@ -161,12 +161,12 @@ describe('resolveImportErrors()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); savedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: savedObjects.filter(obj => obj.type === 'index-pattern' && obj.id === '1'), + saved_objects: savedObjects.filter((obj) => obj.type === 'index-pattern' && obj.id === '1'), }); const result = await resolveSavedObjectsImportErrors({ readStream, @@ -223,12 +223,12 @@ describe('resolveImportErrors()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); savedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: savedObjects.filter(obj => obj.type === 'dashboard' && obj.id === '4'), + saved_objects: savedObjects.filter((obj) => obj.type === 'dashboard' && obj.id === '4'), }); const result = await resolveSavedObjectsImportErrors({ readStream, @@ -296,12 +296,12 @@ describe('resolveImportErrors()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); savedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: savedObjects.map(savedObject => ({ + saved_objects: savedObjects.map((savedObject) => ({ type: savedObject.type, id: savedObject.id, error: { @@ -315,7 +315,7 @@ describe('resolveImportErrors()', () => { const result = await resolveSavedObjectsImportErrors({ readStream, objectLimit: 4, - retries: savedObjects.map(obj => ({ + retries: savedObjects.map((obj) => ({ type: obj.type, id: obj.id, overwrite: false, @@ -495,7 +495,7 @@ describe('resolveImportErrors()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push({ id: '1', type: 'wigwags', attributes: { title: 'my title' }, references: [] }); this.push(null); }, @@ -540,12 +540,12 @@ describe('resolveImportErrors()', () => { const readStream = new Readable({ objectMode: true, read() { - savedObjects.forEach(obj => this.push(obj)); + savedObjects.forEach((obj) => this.push(obj)); this.push(null); }, }); savedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: savedObjects.filter(obj => obj.type === 'index-pattern' && obj.id === '1'), + saved_objects: savedObjects.filter((obj) => obj.type === 'index-pattern' && obj.id === '1'), }); const result = await resolveSavedObjectsImportErrors({ readStream, diff --git a/src/core/server/saved_objects/import/resolve_import_errors.ts b/src/core/server/saved_objects/import/resolve_import_errors.ts index d9ac567882573..a5175aa080598 100644 --- a/src/core/server/saved_objects/import/resolve_import_errors.ts +++ b/src/core/server/saved_objects/import/resolve_import_errors.ts @@ -99,7 +99,7 @@ export async function resolveSavedObjectsImportErrors({ ...errorAccumulator, ...extractErrors(bulkCreateResult.saved_objects, objectsToOverwrite), ]; - successCount += bulkCreateResult.saved_objects.filter(obj => !obj.error).length; + successCount += bulkCreateResult.saved_objects.filter((obj) => !obj.error).length; } if (objectsToNotOverwrite.length) { const bulkCreateResult = await savedObjectsClient.bulkCreate(objectsToNotOverwrite, { @@ -109,7 +109,7 @@ export async function resolveSavedObjectsImportErrors({ ...errorAccumulator, ...extractErrors(bulkCreateResult.saved_objects, objectsToNotOverwrite), ]; - successCount += bulkCreateResult.saved_objects.filter(obj => !obj.error).length; + successCount += bulkCreateResult.saved_objects.filter((obj) => !obj.error).length; } return { diff --git a/src/core/server/saved_objects/import/split_overwrites.ts b/src/core/server/saved_objects/import/split_overwrites.ts index 192d43e9edb24..be55e049a2bfc 100644 --- a/src/core/server/saved_objects/import/split_overwrites.ts +++ b/src/core/server/saved_objects/import/split_overwrites.ts @@ -24,8 +24,8 @@ export function splitOverwrites(savedObjects: SavedObject[], retries: SavedObjec const objectsToOverwrite: SavedObject[] = []; const objectsToNotOverwrite: SavedObject[] = []; const overwrites = retries - .filter(retry => retry.overwrite) - .map(retry => `${retry.type}:${retry.id}`); + .filter((retry) => retry.overwrite) + .map((retry) => `${retry.type}:${retry.id}`); for (const savedObject of savedObjects) { if (overwrites.includes(`${savedObject.type}:${savedObject.id}`)) { diff --git a/src/core/server/saved_objects/import/validate_references.ts b/src/core/server/saved_objects/import/validate_references.ts index f0c033c1d00b4..2a30dcc96c08a 100644 --- a/src/core/server/saved_objects/import/validate_references.ts +++ b/src/core/server/saved_objects/import/validate_references.ts @@ -50,12 +50,12 @@ export async function getNonExistingReferenceAsKeys( } // Fetch references to see if they exist - const bulkGetOpts = Array.from(collector.values()).map(obj => ({ ...obj, fields: ['id'] })); + const bulkGetOpts = Array.from(collector.values()).map((obj) => ({ ...obj, fields: ['id'] })); const bulkGetResponse = await savedObjectsClient.bulkGet(bulkGetOpts, { namespace }); // Error handling const erroredObjects = bulkGetResponse.saved_objects.filter( - obj => obj.error && obj.error.statusCode !== 404 + (obj) => obj.error && obj.error.statusCode !== 404 ); if (erroredObjects.length) { const err = Boom.badRequest(); @@ -89,7 +89,7 @@ export async function validateReferences( ); // Filter out objects with missing references, add to error object - let filteredObjects = savedObjects.filter(savedObject => { + let filteredObjects = savedObjects.filter((savedObject) => { const missingReferences = []; const enforcedTypeReferences = (savedObject.references || []).filter( filterReferencesToValidate @@ -117,7 +117,7 @@ export async function validateReferences( // Filter out objects that reference objects within the import but are missing_references // For example: visualization referencing a search that is missing an index pattern needs to be filtered out - filteredObjects = filteredObjects.filter(savedObject => { + filteredObjects = filteredObjects.filter((savedObject) => { let isBlocked = false; for (const reference of savedObject.references || []) { const referencedObjectError = errorMap[`${reference.type}:${reference.id}`]; diff --git a/src/core/server/saved_objects/mappings/lib/get_types.ts b/src/core/server/saved_objects/mappings/lib/get_types.ts index d77d48f410ae0..66d54aede365b 100644 --- a/src/core/server/saved_objects/mappings/lib/get_types.ts +++ b/src/core/server/saved_objects/mappings/lib/get_types.ts @@ -23,5 +23,5 @@ import { IndexMapping } from '../types'; * Get the names of the types defined in the EsMappingsDsl */ export function getTypes(mappings: IndexMapping) { - return Object.keys(mappings).filter(type => type !== '_default_'); + return Object.keys(mappings).filter((type) => type !== '_default_'); } diff --git a/src/core/server/saved_objects/migrations/core/build_active_mappings.ts b/src/core/server/saved_objects/migrations/core/build_active_mappings.ts index 418ed95f14e05..c2a7b11e057cd 100644 --- a/src/core/server/saved_objects/migrations/core/build_active_mappings.ts +++ b/src/core/server/saved_objects/migrations/core/build_active_mappings.ts @@ -76,10 +76,7 @@ export function diffMappings(actual: IndexMapping, expected: IndexMapping) { // Convert an object to an md5 hash string, using a stable serialization (canonicalStringify) function md5Object(obj: any) { - return crypto - .createHash('md5') - .update(canonicalStringify(obj)) - .digest('hex'); + return crypto.createHash('md5').update(canonicalStringify(obj)).digest('hex'); } // JSON.stringify is non-canonical, meaning the same object may produce slightly @@ -106,7 +103,7 @@ function canonicalStringify(obj: any): string { const sortedObj = keys .sort((a, b) => a.localeCompare(b)) - .map(k => `${k}: ${canonicalStringify(obj[k])}`); + .map((k) => `${k}: ${canonicalStringify(obj[k])}`); return `{${sortedObj}}`; } @@ -120,7 +117,7 @@ function md5Values(obj: any) { // care, as it could be a disabled plugin, etc, and keeping stale stuff // around is better than migrating unecessesarily. function findChangedProp(actual: any, expected: any) { - return Object.keys(expected).find(k => actual[k] !== expected[k]); + return Object.keys(expected).find((k) => actual[k] !== expected[k]); } /** @@ -170,7 +167,7 @@ function validateAndMerge( dest: SavedObjectsMappingProperties, source: SavedObjectsTypeMappingDefinitions | SavedObjectsMappingProperties ) { - Object.keys(source).forEach(k => { + Object.keys(source).forEach((k) => { if (k.startsWith('_')) { throw new Error(`Invalid mapping "${k}". Mappings cannot start with _.`); } diff --git a/src/core/server/saved_objects/migrations/core/build_index_map.test.ts b/src/core/server/saved_objects/migrations/core/build_index_map.test.ts index 2c710d4eaa079..031d63a565e88 100644 --- a/src/core/server/saved_objects/migrations/core/build_index_map.test.ts +++ b/src/core/server/saved_objects/migrations/core/build_index_map.test.ts @@ -23,7 +23,7 @@ import { SavedObjectsType } from '../../types'; const createRegistry = (...types: Array>) => { const registry = new SavedObjectTypeRegistry(); - types.forEach(type => + types.forEach((type) => registry.registerType({ name: 'unknown', namespaceType: 'single', diff --git a/src/core/server/saved_objects/migrations/core/build_index_map.ts b/src/core/server/saved_objects/migrations/core/build_index_map.ts index 8f7fe2f8eac5b..0848fcf56d9fb 100644 --- a/src/core/server/saved_objects/migrations/core/build_index_map.ts +++ b/src/core/server/saved_objects/migrations/core/build_index_map.ts @@ -38,7 +38,7 @@ export interface IndexMap { */ export function createIndexMap({ kibanaIndexName, registry, indexMap }: CreateIndexMapOptions) { const map: IndexMap = {}; - Object.keys(indexMap).forEach(type => { + Object.keys(indexMap).forEach((type) => { const typeDef = registry.getType(type); const script = typeDef?.convertToAliasScript; // Defaults to kibanaIndexName if indexPattern isn't defined diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index bd10520ca1c57..a364710322524 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -29,7 +29,7 @@ const mockLogger = mockLoggerFactory.get('mock logger'); const createRegistry = (...types: Array>) => { const registry = new SavedObjectTypeRegistry(); - types.forEach(type => + types.forEach((type) => registry.registerType({ name: 'unknown', namespaceType: 'single', @@ -73,7 +73,7 @@ describe('DocumentMigrator', () => { typeRegistry: createRegistry({ name: 'foo', migrations: { - bar: doc => doc, + bar: (doc) => doc, }, }), validateDoc: _.noop, @@ -131,7 +131,7 @@ describe('DocumentMigrator', () => { typeRegistry: createRegistry({ name: 'user', migrations: { - '1.2.3': doc => { + '1.2.3': (doc) => { _.set(doc, 'attributes.name', 'Mike'); return doc; }, @@ -639,10 +639,10 @@ describe('DocumentMigrator', () => { typeRegistry: createRegistry({ name: 'aaa', migrations: { - '2.3.4': d => _.set(d, 'attributes.counter', 42), + '2.3.4': (d) => _.set(d, 'attributes.counter', 42), }, }), - validateDoc: d => { + validateDoc: (d) => { if ((d.attributes as any).counter === 42) { throw new Error('Meaningful!'); } diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index 07c1da5586107..376f823267ebe 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -183,7 +183,7 @@ function validateMigrationDefinition(registry: ISavedObjectTypeRegistry) { } } - registry.getAllTypes().forEach(type => { + registry.getAllTypes().forEach((type) => { if (type.migrations) { assertObject( type.migrations, @@ -209,7 +209,7 @@ function buildActiveMigrations( ): ActiveMigrations { return typeRegistry .getAllTypes() - .filter(type => type.migrations && Object.keys(type.migrations).length > 0) + .filter((type) => type.migrations && Object.keys(type.migrations).length > 0) .reduce((migrations, type) => { const transforms = Object.entries(type.migrations!) .map(([version, transform]) => ({ @@ -334,7 +334,7 @@ function wrapWithTry( * Finds the first unmigrated property in the specified document. */ function nextUnmigratedProp(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) { - return props(doc).find(p => { + return props(doc).find((p) => { const latestVersion = propVersion(migrations, p); const docVersion = propVersion(doc, p); @@ -431,7 +431,7 @@ function assertNoDowngrades( } const downgrade = Object.keys(migrationVersion).find( - k => !docVersion.hasOwnProperty(k) || Semver.lt(docVersion[k], migrationVersion[k]) + (k) => !docVersion.hasOwnProperty(k) || Semver.lt(docVersion[k], migrationVersion[k]) ); if (downgrade) { diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index e7621d88f78ee..e87c3e3ff0d64 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -194,7 +194,7 @@ export async function migrationsUpToDate( throw e; } - await new Promise(r => setTimeout(r, 1000)); + await new Promise((r) => setTimeout(r, 1000)); return await migrationsUpToDate(callCluster, index, migrationVersion, retryCount - 1); } @@ -259,7 +259,7 @@ export async function claimAlias( ) { const result = await callCluster('indices.getAlias', { ignore: [404], name: alias }); const aliasInfo = (result as NotFound).status === 404 ? {} : result; - const removeActions = Object.keys(aliasInfo).map(key => ({ remove: { index: key, alias } })); + const removeActions = Object.keys(aliasInfo).map((key) => ({ remove: { index: key, alias } })); await callCluster('indices.updateAliases', { body: { @@ -347,11 +347,11 @@ async function reindex( let completed = false; while (!completed) { - await new Promise(r => setTimeout(r, pollInterval)); + await new Promise((r) => setTimeout(r, pollInterval)); completed = await callCluster('tasks.get', { taskId: task, - }).then(result => { + }).then((result) => { if (result.error) { const e = result.error; throw new Error(`Re-index failed [${e.type}] ${e.reason} :: ${JSON.stringify(e)}`); diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts index 19208e6c83596..392089c69f5a0 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts @@ -341,7 +341,7 @@ function withIndex(callCluster: jest.Mock, opts: any = {}) { let scrollCallCounter = 1; - callCluster.mockImplementation(method => { + callCluster.mockImplementation((method) => { if (method === 'indices.get') { return Promise.resolve(index); } else if (method === 'indices.getAlias') { diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.ts b/src/core/server/saved_objects/migrations/core/index_migrator.ts index ef2a8870d78d0..b2ffe2ad04a88 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.ts @@ -153,11 +153,11 @@ async function deleteIndexTemplates({ callCluster, log, obsoleteIndexTemplatePat return; } - const templateNames = templates.map(t => t.name); + const templateNames = templates.map((t) => t.name); log.info(`Removing index templates: ${templateNames}`); - return Promise.all(templateNames.map(name => callCluster('indices.deleteTemplate', { name }))); + return Promise.all(templateNames.map((name) => callCluster('indices.deleteTemplate', { name }))); } /** @@ -190,7 +190,7 @@ async function migrateSourceToDest(context: Context) { return; } - log.debug(`Migrating saved objects ${docs.map(d => d._id).join(', ')}`); + log.debug(`Migrating saved objects ${docs.map((d) => d._id).join(', ')}`); await Index.write( callCluster, diff --git a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts index 49acea82e1c8a..a2b72ea76c1a2 100644 --- a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts +++ b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts @@ -39,7 +39,7 @@ export function migrateRawDocs( rawDocs: SavedObjectsRawDoc[], log: SavedObjectsMigrationLogger ): SavedObjectsRawDoc[] { - return rawDocs.map(raw => { + return rawDocs.map((raw) => { if (serializer.isRawSavedObject(raw)) { const savedObject = serializer.rawToSavedObject(raw); savedObject.migrationVersion = savedObject.migrationVersion || {}; diff --git a/src/core/server/saved_objects/migrations/core/migration_coordinator.ts b/src/core/server/saved_objects/migrations/core/migration_coordinator.ts index 5ba2d0afc692e..2e32763f4e637 100644 --- a/src/core/server/saved_objects/migrations/core/migration_coordinator.ts +++ b/src/core/server/saved_objects/migrations/core/migration_coordinator.ts @@ -123,5 +123,5 @@ async function waitForMigration( } function sleep(ms: number) { - return new Promise(r => setTimeout(r, ms)); + return new Promise((r) => setTimeout(r, ms)); } diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts index 3f5c0c3876615..fae33bc050dee 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts @@ -65,7 +65,7 @@ const createMigrator = ( }; mockMigrator.getActiveMappings.mockReturnValue(buildActiveMappings(mergeTypes(types))); - mockMigrator.migrateDocument.mockImplementation(doc => doc); + mockMigrator.migrateDocument.mockImplementation((doc) => doc); return mockMigrator; }; diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index cda0e86f15bdf..7a5c044924d0e 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -25,7 +25,7 @@ import { SavedObjectsType } from '../../types'; const createRegistry = (types: Array>) => { const registry = new SavedObjectTypeRegistry(); - types.forEach(type => + types.forEach((type) => registry.registerType({ name: 'unknown', hidden: false, @@ -77,7 +77,7 @@ describe('KibanaMigrator', () => { // and should only be done once const callClusterCommands = clusterStub.mock.calls .map(([callClusterPath]) => callClusterPath) - .filter(callClusterPath => callClusterPath === 'cat.templates'); + .filter((callClusterPath) => callClusterPath === 'cat.templates'); expect(callClusterCommands.length).toBe(1); }); @@ -87,10 +87,7 @@ describe('KibanaMigrator', () => { options.callCluster = clusterStub; const migrator = new KibanaMigrator(options); - const migratorStatus = migrator - .getStatus$() - .pipe(take(3)) - .toPromise(); + const migratorStatus = migrator.getStatus$().pipe(take(3)).toPromise(); await migrator.runMigrations(); const { status, result } = await migratorStatus; expect(status).toEqual('completed'); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index 7d9ff9bed6d72..4f69d45c192e9 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -125,7 +125,7 @@ export class KibanaMigrator { > { if (this.migrationResult === undefined || rerun) { this.status$.next({ status: 'running' }); - this.migrationResult = this.runMigrationsInternal().then(result => { + this.migrationResult = this.runMigrationsInternal().then((result) => { this.status$.next({ status: 'completed', result }); return result; }); @@ -146,7 +146,7 @@ export class KibanaMigrator { registry: this.typeRegistry, }); - const migrators = Object.keys(indexMap).map(index => { + const migrators = Object.keys(indexMap).map((index) => { return new IndexMigrator({ batchSize: this.savedObjectsConfig.batchSize, callCluster: this.callCluster, @@ -164,7 +164,7 @@ export class KibanaMigrator { }); }); - return Promise.all(migrators.map(migrator => migrator.migrate())); + return Promise.all(migrators.map((migrator) => migrator.migrate())); } /** diff --git a/src/core/server/saved_objects/migrations/mocks.ts b/src/core/server/saved_objects/migrations/mocks.ts index 50a7191393472..1c4e55566b61b 100644 --- a/src/core/server/saved_objects/migrations/mocks.ts +++ b/src/core/server/saved_objects/migrations/mocks.ts @@ -20,7 +20,9 @@ import { SavedObjectMigrationContext } from './types'; import { SavedObjectsMigrationLogger } from './core'; -export const createSavedObjectsMigrationLoggerMock = (): jest.Mocked => { +export const createSavedObjectsMigrationLoggerMock = (): jest.Mocked< + SavedObjectsMigrationLogger +> => { const mock = { debug: jest.fn(), info: jest.fn(), diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index 7205699ddc702..9445c144ecda4 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -61,7 +61,7 @@ export const registerExportRoute = (router: IRouter, config: SavedObjectConfig) // need to access the registry for type validation, can't use the schema for this const supportedTypes = context.core.savedObjects.typeRegistry .getImportableAndExportableTypes() - .map(t => t.name); + .map((t) => t.name); if (types) { const validationError = validateTypes(types, supportedTypes); if (validationError) { diff --git a/src/core/server/saved_objects/routes/import.ts b/src/core/server/saved_objects/routes/import.ts index 0731d4159356d..8fce6f49fb850 100644 --- a/src/core/server/saved_objects/routes/import.ts +++ b/src/core/server/saved_objects/routes/import.ts @@ -63,7 +63,7 @@ export const registerImportRoute = (router: IRouter, config: SavedObjectConfig) const supportedTypes = context.core.savedObjects.typeRegistry .getImportableAndExportableTypes() - .map(type => type.name); + .map((type) => type.name); const result = await importSavedObjectsFromStream({ supportedTypes, diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts index 5b52665b6268e..28afdefe1413f 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkCreateRoute } from '../bulk_create'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts index 845bae47b41f2..521e62e16b1d8 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkGetRoute } from '../bulk_get'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts index 6356fc787a8d8..9c888406b0c96 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkUpdateRoute } from '../bulk_update'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/create.test.ts b/src/core/server/saved_objects/routes/integration_tests/create.test.ts index 5a53a30209281..ba3d620f8fdb5 100644 --- a/src/core/server/saved_objects/routes/integration_tests/create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/create.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerCreateRoute } from '../create'; import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts index d4ce4d421dde1..652d267f08fe7 100644 --- a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerDeleteRoute } from '../delete'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/export.test.ts b/src/core/server/saved_objects/routes/integration_tests/export.test.ts index 858d34d5a93bf..7b342dde2febe 100644 --- a/src/core/server/saved_objects/routes/integration_tests/export.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/export.test.ts @@ -27,7 +27,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectConfig } from '../../saved_objects_config'; import { registerExportRoute } from '../export'; -import { setupServer, createExportableType } from './test_utils'; +import { setupServer, createExportableType } from '../test_utils'; type setupServerReturn = UnwrapPromise>; const exportSavedObjectsToStream = exportMock.exportSavedObjectsToStream as jest.Mock; @@ -98,7 +98,7 @@ describe('POST /api/saved_objects/_export', () => { }) ); - const objects = (result.text as string).split('\n').map(row => JSON.parse(row)); + const objects = (result.text as string).split('\n').map((row) => JSON.parse(row)); expect(objects).toEqual(sortedObjects); expect(exportSavedObjectsToStream.mock.calls[0][0]).toEqual( expect.objectContaining({ diff --git a/src/core/server/saved_objects/routes/integration_tests/find.test.ts b/src/core/server/saved_objects/routes/integration_tests/find.test.ts index 907bf44c7748f..31bda1d6b9cbd 100644 --- a/src/core/server/saved_objects/routes/integration_tests/find.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/find.test.ts @@ -23,7 +23,7 @@ import querystring from 'querystring'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerFindRoute } from '../find'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; @@ -129,9 +129,7 @@ describe('GET /api/saved_objects/_find', () => { }); it('accepts the optional query parameter has_reference', async () => { - await supertest(httpSetup.server.listener) - .get('/api/saved_objects/_find?type=foo') - .expect(200); + await supertest(httpSetup.server.listener).get('/api/saved_objects/_find?type=foo').expect(200); expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); diff --git a/src/core/server/saved_objects/routes/integration_tests/import.test.ts b/src/core/server/saved_objects/routes/integration_tests/import.test.ts index c4a03a0e2e7d2..c4e304a3f892f 100644 --- a/src/core/server/saved_objects/routes/integration_tests/import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/import.test.ts @@ -22,7 +22,7 @@ import { UnwrapPromise } from '@kbn/utility-types'; import { registerImportRoute } from '../import'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; import { SavedObjectConfig } from '../../saved_objects_config'; -import { setupServer, createExportableType } from './test_utils'; +import { setupServer, createExportableType } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts b/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts index 4bbe3271e0232..0fe07245dda20 100644 --- a/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerLogLegacyImportRoute } from '../log_legacy_import'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts b/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts index 928d17e7e5be2..5bc7d126ace03 100644 --- a/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts @@ -35,27 +35,18 @@ describe('SavedObjects /_migrate endpoint', () => { }); it('calls runMigrations on the migrator with rerun=true when accessed', async () => { - await kbnTestServer.request - .post(root, '/internal/saved_objects/_migrate') - .send({}) - .expect(200); + await kbnTestServer.request.post(root, '/internal/saved_objects/_migrate').send({}).expect(200); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledWith({ rerun: true }); }); it('calls runMigrations multiple time when multiple access', async () => { - await kbnTestServer.request - .post(root, '/internal/saved_objects/_migrate') - .send({}) - .expect(200); + await kbnTestServer.request.post(root, '/internal/saved_objects/_migrate').send({}).expect(200); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1); - await kbnTestServer.request - .post(root, '/internal/saved_objects/_migrate') - .send({}) - .expect(200); + await kbnTestServer.request.post(root, '/internal/saved_objects/_migrate').send({}).expect(200); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(2); }); diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts index a36f246f9dbc5..27750ec692e5a 100644 --- a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerResolveImportErrorsRoute } from '../resolve_import_errors'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { setupServer, createExportableType } from './test_utils'; +import { setupServer, createExportableType } from '../test_utils'; import { SavedObjectConfig } from '../../saved_objects_config'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/integration_tests/test_utils.ts b/src/core/server/saved_objects/routes/integration_tests/test_utils.ts deleted file mode 100644 index 23e0285201dc7..0000000000000 --- a/src/core/server/saved_objects/routes/integration_tests/test_utils.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ContextService } from '../../../context'; -import { createHttpServer, createCoreContext } from '../../../http/test_utils'; -import { coreMock } from '../../../mocks'; -import { SavedObjectsType } from '../../types'; - -const coreId = Symbol('core'); - -export const setupServer = async () => { - const coreContext = createCoreContext({ coreId }); - const contextService = new ContextService(coreContext); - - const server = createHttpServer(coreContext); - const httpSetup = await server.setup({ - context: contextService.setup({ pluginDependencies: new Map() }), - }); - const handlerContext = coreMock.createRequestHandlerContext(); - - httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { - return handlerContext; - }); - - return { - server, - httpSetup, - handlerContext, - }; -}; - -export const createExportableType = (name: string): SavedObjectsType => { - return { - name, - hidden: false, - namespaceType: 'single', - mappings: { - properties: {}, - }, - management: { - importableAndExportable: true, - }, - }; -}; diff --git a/src/core/server/saved_objects/routes/integration_tests/update.test.ts b/src/core/server/saved_objects/routes/integration_tests/update.test.ts index b0c3d68090db6..eb6eb1cdb6bd9 100644 --- a/src/core/server/saved_objects/routes/integration_tests/update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/update.test.ts @@ -21,7 +21,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerUpdateRoute } from '../update'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { setupServer } from './test_utils'; +import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; diff --git a/src/core/server/saved_objects/routes/resolve_import_errors.ts b/src/core/server/saved_objects/routes/resolve_import_errors.ts index 05bff871b3520..3458e601e0fe6 100644 --- a/src/core/server/saved_objects/routes/resolve_import_errors.ts +++ b/src/core/server/saved_objects/routes/resolve_import_errors.ts @@ -74,7 +74,7 @@ export const registerResolveImportErrorsRoute = (router: IRouter, config: SavedO const supportedTypes = context.core.savedObjects.typeRegistry .getImportableAndExportableTypes() - .map(type => type.name); + .map((type) => type.name); const result = await resolveSavedObjectsImportErrors({ supportedTypes, diff --git a/src/core/server/saved_objects/routes/test_utils.ts b/src/core/server/saved_objects/routes/test_utils.ts new file mode 100644 index 0000000000000..a2227a8033dbd --- /dev/null +++ b/src/core/server/saved_objects/routes/test_utils.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ContextService } from '../../context'; +import { createHttpServer, createCoreContext } from '../../http/test_utils'; +import { coreMock } from '../../mocks'; +import { SavedObjectsType } from '../types'; + +const defaultCoreId = Symbol('core'); + +export const setupServer = async (coreId: symbol = defaultCoreId) => { + const coreContext = createCoreContext({ coreId }); + const contextService = new ContextService(coreContext); + + const server = createHttpServer(coreContext); + const httpSetup = await server.setup({ + context: contextService.setup({ pluginDependencies: new Map() }), + }); + const handlerContext = coreMock.createRequestHandlerContext(); + + httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { + return handlerContext; + }); + + return { + server, + httpSetup, + handlerContext, + }; +}; + +export const createExportableType = (name: string): SavedObjectsType => { + return { + name, + hidden: false, + namespaceType: 'single', + mappings: { + properties: {}, + }, + management: { + importableAndExportable: true, + }, + }; +}; diff --git a/src/core/server/saved_objects/routes/utils.ts b/src/core/server/saved_objects/routes/utils.ts index 5f0db3c4d548c..3963833a9c718 100644 --- a/src/core/server/saved_objects/routes/utils.ts +++ b/src/core/server/saved_objects/routes/utils.ts @@ -37,13 +37,13 @@ export function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { ) .pipe( createFilterStream( - obj => !!obj && !(obj as SavedObjectsExportResultDetails).exportedCount + (obj) => !!obj && !(obj as SavedObjectsExportResultDetails).exportedCount ) ); } export function validateTypes(types: string[], supportedTypes: string[]): string | undefined { - const invalidTypes = types.filter(t => !supportedTypes.includes(t)); + const invalidTypes = types.filter((t) => !supportedTypes.includes(t)); if (invalidTypes.length) { return `Trying to export non-exportable type(s): ${invalidTypes.join(', ')}`; } @@ -53,10 +53,10 @@ export function validateObjects( objects: Array<{ id: string; type: string }>, supportedTypes: string[] ): string | undefined { - const invalidObjects = objects.filter(obj => !supportedTypes.includes(obj.type)); + const invalidObjects = objects.filter((obj) => !supportedTypes.includes(obj.type)); if (invalidObjects.length) { return `Trying to export object(s) with non-exportable types: ${invalidObjects - .map(obj => `${obj.type}:${obj.id}`) + .map((obj) => `${obj.type}:${obj.id}`) .join(', ')}`; } } diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index 7dea7a017a47d..9fba2728003d2 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -46,7 +46,7 @@ describe('SavedObjectsService', () => { env, }: { skipMigration?: boolean; env?: Env } = {}) => { const configService = configServiceMock.create({ atPath: { skip: true } }); - configService.atPath.mockImplementation(path => { + configService.atPath.mockImplementation((path) => { if (path === 'migrations') { return new BehaviorSubject({ skip: skipMigration }); } @@ -67,6 +67,13 @@ describe('SavedObjectsService', () => { }; }; + const createStartDeps = (pluginsInitialized: boolean = true) => { + return { + pluginsInitialized, + elasticsearch: elasticsearchServiceMock.createStart(), + }; + }; + afterEach(() => { jest.clearAllMocks(); }); @@ -83,7 +90,7 @@ describe('SavedObjectsService', () => { setup.setClientFactoryProvider(factoryProvider); - await soService.start({}); + await soService.start(createStartDeps()); expect(clientProviderInstanceMock.setClientFactory).toHaveBeenCalledWith(factory); }); @@ -117,7 +124,7 @@ describe('SavedObjectsService', () => { setup.addClientWrapper(1, 'A', wrapperA); setup.addClientWrapper(2, 'B', wrapperB); - await soService.start({}); + await soService.start(createStartDeps()); expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledTimes(2); expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledWith( @@ -159,9 +166,10 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = createSetupDeps(); + const coreStart = createStartDeps(); let i = 0; - coreSetup.elasticsearch.adminClient.callAsInternalUser = jest + coreStart.elasticsearch.legacy.client.callAsInternalUser = jest .fn() .mockImplementation(() => i++ <= 2 @@ -170,7 +178,7 @@ describe('SavedObjectsService', () => { ); await soService.setup(coreSetup); - await soService.start({}, 1); + await soService.start(coreStart, 1); return expect(KibanaMigratorMock.mock.calls[0][0].callCluster()).resolves.toMatch('success'); }); @@ -180,7 +188,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); await soService.setup(createSetupDeps()); - await soService.start({ pluginsInitialized: false }); + await soService.start(createStartDeps(false)); expect(migratorInstanceMock.runMigrations).not.toHaveBeenCalled(); }); @@ -188,11 +196,11 @@ describe('SavedObjectsService', () => { const coreContext = createCoreContext({ skipMigration: true }); const soService = new SavedObjectsService(coreContext); await soService.setup(createSetupDeps()); - await soService.start({}); + await soService.start(createStartDeps()); expect(migratorInstanceMock.runMigrations).not.toHaveBeenCalled(); }); - it('waits for all es nodes to be compatible before running migrations', async done => { + it('waits for all es nodes to be compatible before running migrations', async (done) => { expect.assertions(2); const coreContext = createCoreContext({ skipMigration: false }); const soService = new SavedObjectsService(coreContext); @@ -206,7 +214,7 @@ describe('SavedObjectsService', () => { kibanaVersion: '8.0.0', }); await soService.setup(setupDeps); - soService.start({}); + soService.start(createStartDeps()); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(0); ((setupDeps.elasticsearch.esNodesCompatibility$ as any) as BehaviorSubject< NodesVersionCompatibility @@ -228,7 +236,7 @@ describe('SavedObjectsService', () => { await soService.setup(createSetupDeps()); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(0); - const startContract = await soService.start({}); + const startContract = await soService.start(createStartDeps()); expect(startContract.migrator).toBe(migratorInstanceMock); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1); }); @@ -237,7 +245,7 @@ describe('SavedObjectsService', () => { const coreContext = createCoreContext({ skipMigration: false }); const soService = new SavedObjectsService(coreContext); const setup = await soService.setup(createSetupDeps()); - await soService.start({}); + await soService.start(createStartDeps()); expect(() => { setup.setClientFactoryProvider(jest.fn()); @@ -268,7 +276,7 @@ describe('SavedObjectsService', () => { const coreContext = createCoreContext({ skipMigration: false }); const soService = new SavedObjectsService(coreContext); await soService.setup(createSetupDeps()); - const { getTypeRegistry } = await soService.start({}); + const { getTypeRegistry } = await soService.start(createStartDeps()); expect(getTypeRegistry()).toBe(typeRegistryInstanceMock); }); @@ -280,18 +288,19 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = createSetupDeps(); await soService.setup(coreSetup); - const { createScopedRepository } = await soService.start({}); + const coreStart = createStartDeps(); + const { createScopedRepository } = await soService.start(coreStart); const req = {} as KibanaRequest; createScopedRepository(req); - expect(coreSetup.elasticsearch.adminClient.asScoped).toHaveBeenCalledWith(req); + expect(coreStart.elasticsearch.legacy.client.asScoped).toHaveBeenCalledWith(req); const [ { value: { callAsCurrentUser }, }, - ] = coreSetup.elasticsearch.adminClient.asScoped.mock.results; + ] = coreStart.elasticsearch.legacy.client.asScoped.mock.results; const [ [, , , callCluster, includedHiddenTypes], @@ -306,7 +315,8 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = createSetupDeps(); await soService.setup(coreSetup); - const { createScopedRepository } = await soService.start({}); + const coreStart = createStartDeps(); + const { createScopedRepository } = await soService.start(coreStart); const req = {} as KibanaRequest; createScopedRepository(req, ['someHiddenType']); @@ -325,7 +335,8 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = createSetupDeps(); await soService.setup(coreSetup); - const { createInternalRepository } = await soService.start({}); + const coreStart = createStartDeps(); + const { createInternalRepository } = await soService.start(coreStart); createInternalRepository(); @@ -333,8 +344,8 @@ describe('SavedObjectsService', () => { [, , , callCluster, includedHiddenTypes], ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; - expect(coreSetup.elasticsearch.adminClient.callAsInternalUser).toBe(callCluster); - expect(callCluster).toBe(coreSetup.elasticsearch.adminClient.callAsInternalUser); + expect(coreStart.elasticsearch.legacy.client.callAsInternalUser).toBe(callCluster); + expect(callCluster).toBe(coreStart.elasticsearch.legacy.client.callAsInternalUser); expect(includedHiddenTypes).toEqual([]); }); @@ -343,7 +354,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = createSetupDeps(); await soService.setup(coreSetup); - const { createInternalRepository } = await soService.start({}); + const { createInternalRepository } = await soService.start(createStartDeps()); createInternalRepository(['someHiddenType']); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 373b8bd1d2bc6..48b1e12fc187e 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -29,7 +29,12 @@ import { import { KibanaMigrator, IKibanaMigrator } from './migrations'; import { CoreContext } from '../core_context'; import { LegacyServiceDiscoverPlugins } from '../legacy'; -import { InternalElasticsearchServiceSetup, APICaller } from '../elasticsearch'; +import { + APICaller, + ElasticsearchServiceStart, + IClusterClient, + InternalElasticsearchServiceSetup, +} from '../elasticsearch'; import { KibanaConfigType } from '../kibana_config'; import { migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster'; import { @@ -278,8 +283,8 @@ interface WrappedClientFactoryWrapper { } /** @internal */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SavedObjectsStartDeps { + elasticsearch: ElasticsearchServiceStart; pluginsInitialized?: boolean; } @@ -310,7 +315,7 @@ export class SavedObjectsService setupDeps.legacyPlugins.uiExports, setupDeps.legacyPlugins.pluginExtendedConfig ); - legacyTypes.forEach(type => this.typeRegistry.registerType(type)); + legacyTypes.forEach((type) => this.typeRegistry.registerType(type)); this.validations = setupDeps.legacyPlugins.uiExports.savedObjectValidations || {}; const savedObjectsConfig = await this.coreContext.configService @@ -332,10 +337,10 @@ export class SavedObjectsService return { status$: calculateStatus$( - this.migrator$.pipe(switchMap(migrator => migrator.getStatus$())), + this.migrator$.pipe(switchMap((migrator) => migrator.getStatus$())), setupDeps.elasticsearch.status$ ), - setClientFactoryProvider: provider => { + setClientFactoryProvider: (provider) => { if (this.started) { throw new Error('cannot call `setClientFactoryProvider` after service startup.'); } @@ -354,7 +359,7 @@ export class SavedObjectsService factory, }); }, - registerType: type => { + registerType: (type) => { if (this.started) { throw new Error('cannot call `registerType` after service startup.'); } @@ -365,7 +370,7 @@ export class SavedObjectsService } public async start( - { pluginsInitialized = true }: SavedObjectsStartDeps, + { elasticsearch, pluginsInitialized = true }: SavedObjectsStartDeps, migrationsRetryDelay?: number ): Promise { if (!this.setupDeps || !this.config) { @@ -378,8 +383,14 @@ export class SavedObjectsService .atPath('kibana') .pipe(first()) .toPromise(); - const adminClient = this.setupDeps!.elasticsearch.adminClient; - const migrator = this.createMigrator(kibanaConfig, this.config.migration, migrationsRetryDelay); + const client = elasticsearch.legacy.client; + + const migrator = this.createMigrator( + kibanaConfig, + this.config.migration, + client, + migrationsRetryDelay + ); this.migrator$.next(migrator); @@ -415,7 +426,7 @@ export class SavedObjectsService }); await this.setupDeps!.elasticsearch.esNodesCompatibility$.pipe( - filter(nodes => nodes.isCompatible), + filter((nodes) => nodes.isCompatible), take(1) ).toPromise(); @@ -435,9 +446,9 @@ export class SavedObjectsService const repositoryFactory: SavedObjectsRepositoryFactory = { createInternalRepository: (includedHiddenTypes?: string[]) => - createRepository(adminClient.callAsInternalUser, includedHiddenTypes), + createRepository(client.callAsInternalUser, includedHiddenTypes), createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => - createRepository(adminClient.asScoped(req).callAsCurrentUser, includedHiddenTypes), + createRepository(client.asScoped(req).callAsCurrentUser, includedHiddenTypes), }; const clientProvider = new SavedObjectsClientProvider({ @@ -473,10 +484,9 @@ export class SavedObjectsService private createMigrator( kibanaConfig: KibanaConfigType, savedObjectsConfig: SavedObjectsMigrationConfigType, + esClient: IClusterClient, migrationsRetryDelay?: number ): KibanaMigrator { - const adminClient = this.setupDeps!.elasticsearch.adminClient; - return new KibanaMigrator({ typeRegistry: this.typeRegistry, logger: this.logger, @@ -485,7 +495,7 @@ export class SavedObjectsService savedObjectValidations: this.validations, kibanaConfig, callCluster: migrationsRetryCallCluster( - adminClient.callAsInternalUser, + esClient.callAsInternalUser, this.logger, migrationsRetryDelay ), diff --git a/src/core/server/saved_objects/saved_objects_type_registry.mock.ts b/src/core/server/saved_objects/saved_objects_type_registry.mock.ts index 8bb66859feca2..5636dcadb444e 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.mock.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.mock.ts @@ -19,8 +19,9 @@ import { ISavedObjectTypeRegistry, SavedObjectTypeRegistry } from './saved_objects_type_registry'; -const createRegistryMock = (): jest.Mocked> => { +const createRegistryMock = (): jest.Mocked< + ISavedObjectTypeRegistry & Pick +> => { const mock = { registerType: jest.fn(), getType: jest.fn(), diff --git a/src/core/server/saved_objects/saved_objects_type_registry.test.ts b/src/core/server/saved_objects/saved_objects_type_registry.test.ts index f82822f90f489..e0f4d6fa28e50 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.test.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.test.ts @@ -44,7 +44,7 @@ describe('SavedObjectTypeRegistry', () => { expect( registry .getAllTypes() - .map(type => type.name) + .map((type) => type.name) .sort() ).toEqual(['typeA', 'typeB', 'typeC']); }); @@ -300,7 +300,7 @@ describe('SavedObjectTypeRegistry', () => { const types = registry.getImportableAndExportableTypes(); expect(types.length).toEqual(2); - expect(types.map(t => t.name)).toEqual(['typeA', 'typeD']); + expect(types.map((t) => t.name)).toEqual(['typeA', 'typeD']); }); }); }); diff --git a/src/core/server/saved_objects/saved_objects_type_registry.ts b/src/core/server/saved_objects/saved_objects_type_registry.ts index 740313a53d1e2..99262d7a31e21 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.ts @@ -64,7 +64,7 @@ export class SavedObjectTypeRegistry { * Return all {@link SavedObjectsType | types} currently registered that are importable/exportable. */ public getImportableAndExportableTypes() { - return this.getAllTypes().filter(type => this.isImportableAndExportable(type.name)); + return this.getAllTypes().filter((type) => this.isImportableAndExportable(type.name)); } /** diff --git a/src/core/server/saved_objects/service/lib/filter_utils.ts b/src/core/server/saved_objects/service/lib/filter_utils.ts index 55859f7108b26..4c31f37f63dad 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.ts @@ -48,22 +48,22 @@ export const validateConvertFilterToKueryNode = ( ); } - if (validationFilterKuery.some(obj => obj.error != null)) { + if (validationFilterKuery.some((obj) => obj.error != null)) { throw SavedObjectsErrorHelpers.createBadRequestError( validationFilterKuery - .filter(obj => obj.error != null) - .map(obj => obj.error) + .filter((obj) => obj.error != null) + .map((obj) => obj.error) .join('\n') ); } - validationFilterKuery.forEach(item => { + validationFilterKuery.forEach((item) => { const path: string[] = item.astPath.length === 0 ? [] : item.astPath.split('.'); const existingKueryNode: KueryNode = path.length === 0 ? filterKueryNode : get(filterKueryNode, path); if (item.isSavedObjectAttr) { existingKueryNode.arguments[0].value = existingKueryNode.arguments[0].value.split('.')[1]; - const itemType = allowedTypes.filter(t => t === item.type); + const itemType = allowedTypes.filter((t) => t === item.type); if (itemType.length === 1) { set( filterKueryNode, diff --git a/src/core/server/saved_objects/service/lib/included_fields.ts b/src/core/server/saved_objects/service/lib/included_fields.ts index c50ac22594008..33bca49e3fc58 100644 --- a/src/core/server/saved_objects/service/lib/included_fields.ts +++ b/src/core/server/saved_objects/service/lib/included_fields.ts @@ -34,7 +34,7 @@ export function includedFields(type: string | string[] = '*', fields?: string[] return sourceType .reduce((acc: string[], t) => { - return [...acc, ...sourceFields.map(f => `${t}.${f}`)]; + return [...acc, ...sourceFields.map((f) => `${t}.${f}`)]; }, []) .concat('namespace') .concat('namespaces') diff --git a/src/core/server/saved_objects/service/lib/priority_collection.test.ts b/src/core/server/saved_objects/service/lib/priority_collection.test.ts index 13180b912e7d7..62f9415927db9 100644 --- a/src/core/server/saved_objects/service/lib/priority_collection.test.ts +++ b/src/core/server/saved_objects/service/lib/priority_collection.test.ts @@ -64,6 +64,6 @@ test(`#has when empty returns false`, () => { test(`#has returns result of predicate`, () => { const priorityCollection = new PriorityCollection(); priorityCollection.add(1, 'foo'); - expect(priorityCollection.has(val => val === 'foo')).toEqual(true); - expect(priorityCollection.has(val => val === 'bar')).toEqual(false); + expect(priorityCollection.has((val) => val === 'foo')).toEqual(true); + expect(priorityCollection.has((val) => val === 'bar')).toEqual(false); }); diff --git a/src/core/server/saved_objects/service/lib/priority_collection.ts b/src/core/server/saved_objects/service/lib/priority_collection.ts index a2fe13b933347..350d549b7e8f2 100644 --- a/src/core/server/saved_objects/service/lib/priority_collection.ts +++ b/src/core/server/saved_objects/service/lib/priority_collection.ts @@ -26,7 +26,7 @@ export class PriorityCollection { private readonly array: Array> = []; public add(priority: number, value: T) { - const foundIndex = this.array.findIndex(current => { + const foundIndex = this.array.findIndex((current) => { if (priority === current.priority) { throw new Error('Already have entry with this priority'); } @@ -39,10 +39,10 @@ export class PriorityCollection { } public has(predicate: (value: T) => boolean): boolean { - return this.array.some(entry => predicate(entry.value)); + return this.array.some((entry) => predicate(entry.value)); } public toPrioritizedArray(): T[] { - return this.array.map(entry => entry.value); + return this.array.map((entry) => entry.value); } } diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index c46fcfbc6dbd7..83e037fb2da66 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -113,10 +113,10 @@ describe('SavedObjectsRepository', () => { }, }; - const createType = type => ({ + const createType = (type) => ({ name: type, mappings: { properties: mappings.properties[type].properties }, - migrations: { '1.1.1': doc => doc }, + migrations: { '1.1.1': (doc) => doc }, }); const registry = new SavedObjectTypeRegistry(); @@ -171,7 +171,7 @@ describe('SavedObjectsRepository', () => { const getMockMgetResponse = (objects, namespace) => ({ status: 200, - docs: objects.map(obj => + docs: objects.map((obj) => obj.found === false ? obj : getMockGetResponse({ ...obj, namespace }) ), }); @@ -202,10 +202,11 @@ describe('SavedObjectsRepository', () => { const expectSuccess = ({ type, id }) => expect.toBeDocumentWithoutError(type, id); const expectError = ({ type, id }) => ({ type, id, error: expect.any(Object) }); const expectErrorResult = ({ type, id }, error) => ({ type, id, error }); - const expectErrorNotFound = obj => + const expectErrorNotFound = (obj) => expectErrorResult(obj, createGenericNotFoundError(obj.type, obj.id)); - const expectErrorConflict = obj => expectErrorResult(obj, createConflictError(obj.type, obj.id)); - const expectErrorInvalidType = obj => + const expectErrorConflict = (obj) => + expectErrorResult(obj, createConflictError(obj.type, obj.id)); + const expectErrorInvalidType = (obj) => expectErrorResult(obj, createUnsupportedTypeError(obj.type, obj.id)); const expectMigrationArgs = (args, contains = true, n = 1) => { @@ -229,12 +230,12 @@ describe('SavedObjectsRepository', () => { trimIdPrefix: jest.fn(), }; const _serializer = new SavedObjectsSerializer(registry); - Object.keys(serializer).forEach(key => { + Object.keys(serializer).forEach((key) => { serializer[key].mockImplementation((...args) => _serializer[key](...args)); }); - const allTypes = registry.getAllTypes().map(type => type.name); - const allowedTypes = [...new Set(allTypes.filter(type => !registry.isHidden(type)))]; + const allTypes = registry.getAllTypes().map((type) => type.name); + const allowedTypes = [...new Set(allTypes.filter((type) => !registry.isHidden(type)))]; savedObjectsRepository = new SavedObjectsRepository({ index: '.kibana-test', @@ -251,7 +252,7 @@ describe('SavedObjectsRepository', () => { }); const mockMigrationVersion = { foo: '2.3.4' }; - const mockMigrateDocument = doc => ({ + const mockMigrateDocument = (doc) => ({ ...doc, attributes: { ...doc.attributes, @@ -345,7 +346,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when type is not multi-namespace`, async () => { - const test = async type => { + const test = async (type) => { const message = `${type} doesn't support multiple namespaces`; await expectBadRequestError(type, id, [newNs1, newNs2], message); expect(callAdminCluster).not.toHaveBeenCalled(); @@ -355,7 +356,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when namespaces is an empty array`, async () => { - const test = async namespaces => { + const test = async (namespaces) => { const message = 'namespaces must be a non-empty array of strings'; await expectBadRequestError(type, id, namespaces, message); expect(callAdminCluster).not.toHaveBeenCalled(); @@ -489,7 +490,7 @@ describe('SavedObjectsRepository', () => { }), ]; - const expectSuccessResult = obj => ({ + const expectSuccessResult = (obj) => ({ ...obj, migrationVersion: { [obj.type]: '1.1.1' }, version: mockVersion, @@ -511,13 +512,13 @@ describe('SavedObjectsRepository', () => { }); it(`should use the ES create method if ID is undefined and overwrite=true`, async () => { - const objects = [obj1, obj2].map(obj => ({ ...obj, id: undefined })); + const objects = [obj1, obj2].map((obj) => ({ ...obj, id: undefined })); await bulkCreateSuccess(objects, { overwrite: true }); expectClusterCallArgsAction(objects, { method: 'create' }); }); it(`should use the ES create method if ID is undefined and overwrite=false`, async () => { - const objects = [obj1, obj2].map(obj => ({ ...obj, id: undefined })); + const objects = [obj1, obj2].map((obj) => ({ ...obj, id: undefined })); await bulkCreateSuccess(objects); expectClusterCallArgsAction(objects, { method: 'create' }); }); @@ -557,8 +558,8 @@ describe('SavedObjectsRepository', () => { }); it(`adds namespaces to request body for any types that are multi-namespace`, async () => { - const test = async namespace => { - const objects = [obj1, obj2].map(x => ({ ...x, type: MULTI_NAMESPACE_TYPE })); + const test = async (namespace) => { + const objects = [obj1, obj2].map((x) => ({ ...x, type: MULTI_NAMESPACE_TYPE })); const namespaces = [namespace ?? 'default']; await bulkCreateSuccess(objects, { namespace, overwrite: true }); const expected = expect.objectContaining({ namespaces }); @@ -571,7 +572,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't add namespaces to request body for any types that are not multi-namespace`, async () => { - const test = async namespace => { + const test = async (namespace) => { const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; await bulkCreateSuccess(objects, { namespace, overwrite: true }); const expected = expect.not.objectContaining({ namespaces: expect.anything() }); @@ -600,7 +601,7 @@ describe('SavedObjectsRepository', () => { }); it(`should use custom index`, async () => { - await bulkCreateSuccess([obj1, obj2].map(x => ({ ...x, type: CUSTOM_INDEX_TYPE }))); + await bulkCreateSuccess([obj1, obj2].map((x) => ({ ...x, type: CUSTOM_INDEX_TYPE }))); expectClusterCallArgsAction([obj1, obj2], { method: 'create', _index: 'custom' }); }); @@ -730,11 +731,11 @@ describe('SavedObjectsRepository', () => { it(`migrates the docs and serializes the migrated docs`, async () => { migrator.migrateDocument.mockImplementation(mockMigrateDocument); await bulkCreateSuccess([obj1, obj2]); - const docs = [obj1, obj2].map(x => ({ ...x, ...mockTimestampFields })); + const docs = [obj1, obj2].map((x) => ({ ...x, ...mockTimestampFields })); expectMigrationArgs(docs[0], true, 1); expectMigrationArgs(docs[1], true, 2); - const migratedDocs = docs.map(x => migrator.migrateDocument(x)); + const migratedDocs = docs.map((x) => migrator.migrateDocument(x)); expect(serializer.savedObjectToRaw).toHaveBeenNthCalledWith(1, migratedDocs[0]); expect(serializer.savedObjectToRaw).toHaveBeenNthCalledWith(2, migratedDocs[1]); }); @@ -762,14 +763,14 @@ describe('SavedObjectsRepository', () => { }); it(`adds namespaces to body when providing namespace for multi-namespace type`, async () => { - const objects = [obj1, obj2].map(obj => ({ ...obj, type: MULTI_NAMESPACE_TYPE })); + const objects = [obj1, obj2].map((obj) => ({ ...obj, type: MULTI_NAMESPACE_TYPE })); await bulkCreateSuccess(objects, { namespace }); expectMigrationArgs({ namespaces: [namespace] }, true, 1); expectMigrationArgs({ namespaces: [namespace] }, true, 2); }); it(`adds default namespaces to body when providing no namespace for multi-namespace type`, async () => { - const objects = [obj1, obj2].map(obj => ({ ...obj, type: MULTI_NAMESPACE_TYPE })); + const objects = [obj1, obj2].map((obj) => ({ ...obj, type: MULTI_NAMESPACE_TYPE })); await bulkCreateSuccess(objects); expectMigrationArgs({ namespaces: ['default'] }, true, 1); expectMigrationArgs({ namespaces: ['default'] }, true, 2); @@ -787,7 +788,7 @@ describe('SavedObjectsRepository', () => { it(`formats the ES response`, async () => { const result = await bulkCreateSuccess([obj1, obj2]); expect(result).toEqual({ - saved_objects: [obj1, obj2].map(x => expectSuccessResult(x)), + saved_objects: [obj1, obj2].map((x) => expectSuccessResult(x)), }); }); @@ -909,12 +910,12 @@ describe('SavedObjectsRepository', () => { it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { const getId = (type, id) => `${type}:${id}`; - let objects = [obj1, obj2].map(obj => ({ ...obj, type: NAMESPACE_AGNOSTIC_TYPE })); + let objects = [obj1, obj2].map((obj) => ({ ...obj, type: NAMESPACE_AGNOSTIC_TYPE })); await bulkGetSuccess(objects, { namespace }); _expectClusterCallArgs(objects, { getId }); callAdminCluster.mockReset(); - objects = [obj1, obj2].map(obj => ({ ...obj, type: MULTI_NAMESPACE_TYPE })); + objects = [obj1, obj2].map((obj) => ({ ...obj, type: MULTI_NAMESPACE_TYPE })); await bulkGetSuccess(objects, { namespace }); _expectClusterCallArgs(objects, { getId }); }); @@ -1136,7 +1137,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesnt call Elasticsearch if there are no valid objects to update`, async () => { - const objects = [obj1, obj2].map(x => ({ ...x, type: 'unknownType' })); + const objects = [obj1, obj2].map((x) => ({ ...x, type: 'unknownType' })); await savedObjectsRepository.bulkUpdate(objects); expect(callAdminCluster).toHaveBeenCalledTimes(0); }); @@ -1149,8 +1150,8 @@ describe('SavedObjectsRepository', () => { }); it(`accepts custom references array`, async () => { - const test = async references => { - const objects = [obj1, obj2].map(obj => ({ ...obj, references })); + const test = async (references) => { + const objects = [obj1, obj2].map((obj) => ({ ...obj, references })); await bulkUpdateSuccess(objects); const expected = { doc: expect.objectContaining({ references }) }; const body = [expect.any(Object), expected, expect.any(Object), expected]; @@ -1163,8 +1164,8 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't accept custom references if not an array`, async () => { - const test = async references => { - const objects = [obj1, obj2].map(obj => ({ ...obj, references })); + const test = async (references) => { + const objects = [obj1, obj2].map((obj) => ({ ...obj, references })); await bulkUpdateSuccess(objects); const expected = { doc: expect.not.objectContaining({ references: expect.anything() }) }; const body = [expect.any(Object), expected, expect.any(Object), expected]; @@ -1366,7 +1367,7 @@ describe('SavedObjectsRepository', () => { }); it(`includes references`, async () => { - const objects = [obj1, obj2].map(obj => ({ ...obj, references })); + const objects = [obj1, obj2].map((obj) => ({ ...obj, references })); const response = await bulkUpdateSuccess(objects); expect(response).toEqual({ saved_objects: objects.map(expectSuccessResult), @@ -1463,7 +1464,7 @@ describe('SavedObjectsRepository', () => { }); it(`accepts custom references array`, async () => { - const test = async references => { + const test = async (references) => { await createSuccess(type, attributes, { id, references }); expectClusterCallArgs({ body: expect.objectContaining({ references }), @@ -1476,7 +1477,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't accept custom references if not an array`, async () => { - const test = async references => { + const test = async (references) => { await createSuccess(type, attributes, { id, references }); expectClusterCallArgs({ body: expect.not.objectContaining({ references: expect.anything() }), @@ -1871,7 +1872,7 @@ describe('SavedObjectsRepository', () => { describe('errors', () => { it(`throws when namespace is not a string`, async () => { - const test = async namespace => { + const test = async (namespace) => { await expect(savedObjectsRepository.deleteByNamespace(namespace)).rejects.toThrowError( `namespace is required, and must be a string` ); @@ -1904,17 +1905,17 @@ describe('SavedObjectsRepository', () => { describe('search dsl', () => { it(`constructs a query using all multi-namespace types, and another using all single-namespace types`, async () => { await deleteByNamespaceSuccess(namespace); - const allTypes = registry.getAllTypes().map(type => type.name); + const allTypes = registry.getAllTypes().map((type) => type.name); expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, { namespace, - type: allTypes.filter(type => !registry.isNamespaceAgnostic(type)), + type: allTypes.filter((type) => !registry.isNamespaceAgnostic(type)), }); }); }); }); describe('#find', () => { - const generateSearchResults = namespace => { + const generateSearchResults = (namespace) => { return { hits: { total: 4, @@ -2038,7 +2039,7 @@ describe('SavedObjectsRepository', () => { }); it(`should not make a cluster call when attempting to find only invalid or hidden types`, async () => { - const test = async types => { + const test = async (types) => { await savedObjectsRepository.find({ type: types }); expect(callAdminCluster).not.toHaveBeenCalled(); }; @@ -2154,7 +2155,7 @@ describe('SavedObjectsRepository', () => { }); it(`should return empty results when attempting to find only invalid or hidden types`, async () => { - const test = async types => { + const test = async (types) => { const result = await savedObjectsRepository.find({ type: types }); expect(result).toEqual(expect.objectContaining({ saved_objects: [] })); }; @@ -2468,7 +2469,7 @@ describe('SavedObjectsRepository', () => { }; it(`throws when type is not a string`, async () => { - const test = async type => { + const test = async (type) => { await expect( savedObjectsRepository.incrementCounter(type, id, field) ).rejects.toThrowError(`"type" argument must be a string`); @@ -2482,7 +2483,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when counterFieldName is not a string`, async () => { - const test = async field => { + const test = async (field) => { await expect( savedObjectsRepository.incrementCounter(type, id, field) ).rejects.toThrowError(`"counterFieldName" argument must be a string`); @@ -2610,7 +2611,7 @@ describe('SavedObjectsRepository', () => { options ) => { mockGetResponse(type, id, currentNamespaces); // this._callCluster('get', ...) - const isDelete = currentNamespaces.every(namespace => namespaces.includes(namespace)); + const isDelete = currentNamespaces.every((namespace) => namespaces.includes(namespace)); callAdminCluster.mockResolvedValue({ _id: `${type}:${id}`, ...mockVersionProps, @@ -2629,7 +2630,7 @@ describe('SavedObjectsRepository', () => { describe('cluster calls', () => { describe('delete action', () => { const deleteFromNamespacesSuccessDelete = async (expectFn, options, _type = type) => { - const test = async namespaces => { + const test = async (namespaces) => { await deleteFromNamespacesSuccess(_type, id, namespaces, namespaces, options); expectFn(); callAdminCluster.mockReset(); @@ -2680,7 +2681,7 @@ describe('SavedObjectsRepository', () => { describe('update action', () => { const deleteFromNamespacesSuccessUpdate = async (expectFn, options, _type = type) => { - const test = async remaining => { + const test = async (remaining) => { const currentNamespaces = [namespace1].concat(remaining); await deleteFromNamespacesSuccess(_type, id, [namespace1], currentNamespaces, options); expectFn(); @@ -2761,7 +2762,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when type is not namespace-agnostic`, async () => { - const test = async type => { + const test = async (type) => { const message = `${type} doesn't support multiple namespaces`; await expectBadRequestError(type, id, [namespace1, namespace2], message); expect(callAdminCluster).not.toHaveBeenCalled(); @@ -2771,7 +2772,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when namespaces is an empty array`, async () => { - const test = async namespaces => { + const test = async (namespaces) => { const message = 'namespaces must be a non-empty array of strings'; await expectBadRequestError(type, id, namespaces, message); expect(callAdminCluster).not.toHaveBeenCalled(); @@ -2844,7 +2845,7 @@ describe('SavedObjectsRepository', () => { describe('returns', () => { it(`returns an empty object on success (delete)`, async () => { - const test = async namespaces => { + const test = async (namespaces) => { const result = await deleteFromNamespacesSuccess(type, id, namespaces, namespaces); expect(result).toEqual({}); callAdminCluster.mockReset(); @@ -2854,7 +2855,7 @@ describe('SavedObjectsRepository', () => { }); it(`returns an empty object on success (update)`, async () => { - const test = async remaining => { + const test = async (remaining) => { const currentNamespaces = [namespace1].concat(remaining); const result = await deleteFromNamespacesSuccess( type, @@ -2929,7 +2930,7 @@ describe('SavedObjectsRepository', () => { }); it(`accepts custom references array`, async () => { - const test = async references => { + const test = async (references) => { await updateSuccess(type, id, attributes, { references }); expectClusterCallArgs({ body: { doc: expect.objectContaining({ references }) }, @@ -2942,7 +2943,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't accept custom references if not an array`, async () => { - const test = async references => { + const test = async (references) => { await updateSuccess(type, id, attributes, { references }); expectClusterCallArgs({ body: { doc: expect.not.objectContaining({ references: expect.anything() }) }, diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index f76b05c4af1b9..e23f8dec5927c 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -138,9 +138,9 @@ export class SavedObjectsRepository { const mappings = migrator.getActiveMappings(); const allTypes = Object.keys(getRootPropertiesObjects(mappings)); const serializer = new SavedObjectsSerializer(typeRegistry); - const visibleTypes = allTypes.filter(type => !typeRegistry.isHidden(type)); + const visibleTypes = allTypes.filter((type) => !typeRegistry.isHidden(type)); - const missingTypeMappings = includedHiddenTypes.filter(type => !allTypes.includes(type)); + const missingTypeMappings = includedHiddenTypes.filter((type) => !allTypes.includes(type)); if (missingTypeMappings.length > 0) { throw new Error( `Missing mappings for saved objects types: '${missingTypeMappings.join(', ')}'` @@ -284,7 +284,7 @@ export class SavedObjectsRepository { const time = this._getCurrentTime(); let bulkGetRequestIndexCounter = 0; - const expectedResults: Either[] = objects.map(object => { + const expectedResults: Either[] = objects.map((object) => { if (!this._allowedTypes.includes(object.type)) { return { tag: 'Left' as 'Left', @@ -331,7 +331,7 @@ export class SavedObjectsRepository { let bulkRequestIndexCounter = 0; const bulkCreateParams: object[] = []; - const expectedBulkResults: Either[] = expectedResults.map(expectedBulkGetResult => { + const expectedBulkResults: Either[] = expectedResults.map((expectedBulkGetResult) => { if (isLeft(expectedBulkGetResult)) { return expectedBulkGetResult; } @@ -401,7 +401,7 @@ export class SavedObjectsRepository { : undefined; return { - saved_objects: expectedBulkResults.map(expectedResult => { + saved_objects: expectedBulkResults.map((expectedResult) => { if (isLeft(expectedResult)) { return expectedResult.error as any; } @@ -453,7 +453,7 @@ export class SavedObjectsRepository { preflightResult = await this.preflightCheckIncludesNamespace(type, id, namespace); const existingNamespaces = getSavedObjectNamespaces(undefined, preflightResult); const remainingNamespaces = existingNamespaces?.filter( - x => x !== getNamespaceString(namespace) + (x) => x !== getNamespaceString(namespace) ); if (remainingNamespaces?.length) { @@ -530,7 +530,7 @@ export class SavedObjectsRepository { const { refresh = DEFAULT_REFRESH_SETTING } = options; const allTypes = Object.keys(getRootPropertiesObjects(this._mappings)); - const typesToUpdate = allTypes.filter(type => !this._registry.isNamespaceAgnostic(type)); + const typesToUpdate = allTypes.filter((type) => !this._registry.isNamespaceAgnostic(type)); const updateOptions = { index: this.getIndicesForTypes(typesToUpdate), @@ -599,7 +599,7 @@ export class SavedObjectsRepository { } const types = Array.isArray(type) ? type : [type]; - const allowedTypes = types.filter(t => this._allowedTypes.includes(t)); + const allowedTypes = types.filter((t) => this._allowedTypes.includes(t)); if (allowedTypes.length === 0) { return { page, @@ -702,7 +702,7 @@ export class SavedObjectsRepository { } let bulkGetRequestIndexCounter = 0; - const expectedBulkGetResults: Either[] = objects.map(object => { + const expectedBulkGetResults: Either[] = objects.map((object) => { const { type, id, fields } = object; if (!this._allowedTypes.includes(type)) { @@ -744,7 +744,7 @@ export class SavedObjectsRepository { : undefined; return { - saved_objects: expectedBulkGetResults.map(expectedResult => { + saved_objects: expectedBulkGetResults.map((expectedResult) => { if (isLeft(expectedResult)) { return expectedResult.error as any; } @@ -981,7 +981,7 @@ export class SavedObjectsRepository { const preflightResult = await this.preflightCheckIncludesNamespace(type, id, namespace); const existingNamespaces = getSavedObjectNamespaces(undefined, preflightResult); // if there are somehow no existing namespaces, allow the operation to proceed and delete this saved object - const remainingNamespaces = existingNamespaces?.filter(x => !namespaces.includes(x)); + const remainingNamespaces = existingNamespaces?.filter((x) => !namespaces.includes(x)); if (remainingNamespaces?.length) { // if there is 1 or more namespace remaining, update the saved object @@ -1057,7 +1057,7 @@ export class SavedObjectsRepository { const { namespace } = options; let bulkGetRequestIndexCounter = 0; - const expectedBulkGetResults: Either[] = objects.map(object => { + const expectedBulkGetResults: Either[] = objects.map((object) => { const { type, id } = object; if (!this._allowedTypes.includes(type)) { @@ -1113,7 +1113,7 @@ export class SavedObjectsRepository { let bulkUpdateRequestIndexCounter = 0; const bulkUpdateParams: object[] = []; const expectedBulkUpdateResults: Either[] = expectedBulkGetResults.map( - expectedBulkGetResult => { + (expectedBulkGetResult) => { if (isLeft(expectedBulkGetResult)) { return expectedBulkGetResult; } @@ -1173,7 +1173,7 @@ export class SavedObjectsRepository { : {}; return { - saved_objects: expectedBulkUpdateResults.map(expectedResult => { + saved_objects: expectedBulkUpdateResults.map((expectedResult) => { if (isLeft(expectedResult)) { return expectedResult.error as any; } @@ -1326,7 +1326,7 @@ export class SavedObjectsRepository { * @param types The types whose indices should be retrieved */ private getIndicesForTypes(types: string[]) { - return unique(types.map(t => this.getIndexForType(t))); + return unique(types.map((t) => this.getIndexForType(t))); } private _getCurrentTime() { diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 3250737e1287d..ab80f37e6652f 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -107,7 +107,7 @@ export class SavedObjectsClientProvider { id: string, factory: SavedObjectsClientWrapperFactory ): void { - if (this._wrapperFactories.has(entry => entry.id === id)) { + if (this._wrapperFactories.has((entry) => entry.id === id)) { throw new Error(`wrapper factory with id ${id} is already defined`); } diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index a72c21dd5eee4..a0ffa91f53671 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -43,11 +43,11 @@ const MAPPINGS = { const ALL_TYPES = Object.keys(MAPPINGS.properties); // get all possible subsets (combination) of all types const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( - (subsets, value) => subsets.concat(subsets.map(set => [...set, value])), + (subsets, value) => subsets.concat(subsets.map((set) => [...set, value])), [[] as string[]] ) - .filter(x => x.length) // exclude empty set - .map(x => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it + .filter((x) => x.length) // exclude empty set + .map((x) => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it /** * Note: these tests cases are defined in the order they appear in the source code, for readability's sake @@ -163,7 +163,7 @@ describe('#getQueryParams', () => { expect.arrayContaining([ { bool: expect.objectContaining({ - should: types.map(type => ({ + should: types.map((type) => ({ bool: expect.objectContaining({ must: expect.arrayContaining([{ term: { type } }]), }), @@ -233,11 +233,11 @@ describe('#getQueryParams', () => { for (const typeOrTypes of ALL_TYPE_SUBSETS) { const result = getQueryParams({ mappings, registry, type: typeOrTypes, namespace }); const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; - expectResult(result, ...types.map(x => createTypeClause(x, namespace))); + expectResult(result, ...types.map((x) => createTypeClause(x, namespace))); } // also test with no specified type/s const result = getQueryParams({ mappings, registry, type: undefined, namespace }); - expectResult(result, ...ALL_TYPES.map(x => createTypeClause(x, namespace))); + expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespace))); }; it('filters results with "namespace" field when `namespace` is not specified', () => { @@ -280,7 +280,7 @@ describe('#getQueryParams', () => { describe('`searchFields` parameter', () => { const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => { const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; - return searchFields.map(x => types.map(y => `${y}.${x}`)).flat(); + return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat(); }; const test = (searchFields: string[]) => { diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 967ce8bceaf84..40485564176a6 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -51,7 +51,7 @@ function getFieldsForTypes(types: string[], searchFields?: string[]) { let fields: string[] = []; for (const field of searchFields) { - fields = fields.concat(types.map(prefix => `${prefix}.${field}`)); + fields = fields.concat(types.map((prefix) => `${prefix}.${field}`)); } return { fields }; @@ -152,7 +152,7 @@ export function getQueryParams({ }, ] : undefined, - should: types.map(shouldType => getClauseForType(registry, namespace, shouldType)), + should: types.map((shouldType) => getClauseForType(registry, namespace, shouldType)), minimum_should_match: 1, }, }, diff --git a/src/core/server/saved_objects/status.ts b/src/core/server/saved_objects/status.ts index 66a6e2baa17a7..1823fbe95b8ba 100644 --- a/src/core/server/saved_objects/status.ts +++ b/src/core/server/saved_objects/status.ts @@ -28,7 +28,7 @@ export const calculateStatus$ = ( elasticsearchStatus$: Observable ): Observable> => { const migratorStatus$: Observable> = rawMigratorStatus$.pipe( - map(migrationStatus => { + map((migrationStatus) => { if (migrationStatus.status === 'waiting') { return { level: ServiceStatusLevels.unavailable, diff --git a/src/core/server/saved_objects/utils.test.ts b/src/core/server/saved_objects/utils.test.ts index 033aeea7c018d..21229bee489c2 100644 --- a/src/core/server/saved_objects/utils.test.ts +++ b/src/core/server/saved_objects/utils.test.ts @@ -142,7 +142,7 @@ describe('convertLegacyTypes', () => { }); it('invokes indexPattern to retrieve the index when it is a function', () => { - const indexPatternAccessor: (config: LegacyConfig) => string = jest.fn(config => { + const indexPatternAccessor: (config: LegacyConfig) => string = jest.fn((config) => { config.get('foo.bar'); return 'myIndex'; }); @@ -301,13 +301,13 @@ describe('convertLegacyTypes', () => { isImportableAndExportable: true, icon: 'iconA', defaultSearchField: 'searchFieldA', - getTitle: savedObject => savedObject.id, + getTitle: (savedObject) => savedObject.id, }, typeB: { isImportableAndExportable: false, icon: 'iconB', - getEditUrl: savedObject => `/some-url/${savedObject.id}`, - getInAppUrl: savedObject => ({ path: 'path', uiCapabilitiesPath: 'ui-path' }), + getEditUrl: (savedObject) => `/some-url/${savedObject.id}`, + getInAppUrl: (savedObject) => ({ path: 'path', uiCapabilitiesPath: 'ui-path' }), }, }, savedObjectMigrations: {}, @@ -377,7 +377,7 @@ describe('convertLegacyTypes', () => { }, savedObjectSchemas: { typeA: { - indexPattern: jest.fn(config => { + indexPattern: jest.fn((config) => { config.get('foo.bar'); return 'myIndex'; }), diff --git a/src/core/server/saved_objects/validation/index.ts b/src/core/server/saved_objects/validation/index.ts index 98dc6254178c5..b1b33f91d3fd4 100644 --- a/src/core/server/saved_objects/validation/index.ts +++ b/src/core/server/saved_objects/validation/index.ts @@ -57,7 +57,7 @@ export function docValidator(validators: PropertyValidators = {}): ValidateDoc { return function validateDoc(doc: SavedObjectDoc) { Object.keys(doc) .concat(doc.type) - .forEach(prop => { + .forEach((prop) => { const validator = validators[prop]; if (validator) { validator(doc); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index fcf9a9e2dedc2..eef071e9488bf 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -823,16 +823,15 @@ export class ElasticsearchErrorHelpers { // @public (undocumented) export interface ElasticsearchServiceSetup { // @deprecated (undocumented) - readonly adminClient: IClusterClient; - // @deprecated (undocumented) - readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; - // @deprecated (undocumented) - readonly dataClient: IClusterClient; + legacy: { + readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; + readonly client: IClusterClient; + }; } // @public (undocumented) export interface ElasticsearchServiceStart { - // (undocumented) + // @deprecated (undocumented) legacy: { readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; readonly client: IClusterClient; @@ -1576,8 +1575,9 @@ export interface RequestHandlerContext { typeRegistry: ISavedObjectTypeRegistry; }; elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; + legacy: { + client: IScopedClusterClient; + }; }; uiSettings: { client: IUiSettingsClient; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index d4c0ebcfb7cf2..ef12379c199e8 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -195,12 +195,13 @@ export class Server { public async start() { this.log.debug('starting server'); + const elasticsearchStart = await this.elasticsearch.start(); const savedObjectsStart = await this.savedObjects.start({ + elasticsearch: elasticsearchStart, pluginsInitialized: this.pluginsInitialized, }); const capabilitiesStart = this.capabilities.start(); const uiSettingsStart = await this.uiSettings.start(); - const elasticsearchStart = await this.elasticsearch.start(); this.coreStart = { capabilities: capabilitiesStart, @@ -247,20 +248,21 @@ export class Server { coreId, 'core', async (context, req, res): Promise => { - const savedObjectsClient = this.coreStart!.savedObjects.getScopedClient(req); - const uiSettingsClient = coreSetup.uiSettings.asScopedToClient(savedObjectsClient); + const coreStart = this.coreStart!; + const savedObjectsClient = coreStart.savedObjects.getScopedClient(req); return { savedObjects: { client: savedObjectsClient, - typeRegistry: this.coreStart!.savedObjects.getTypeRegistry(), + typeRegistry: coreStart.savedObjects.getTypeRegistry(), }, elasticsearch: { - adminClient: coreSetup.elasticsearch.adminClient.asScoped(req), - dataClient: coreSetup.elasticsearch.dataClient.asScoped(req), + legacy: { + client: coreStart.elasticsearch.legacy.client.asScoped(req), + }, }, uiSettings: { - client: uiSettingsClient, + client: coreStart.uiSettings.asScopedToClient(savedObjectsClient), }, }; } diff --git a/src/core/server/status/status_service.test.ts b/src/core/server/status/status_service.test.ts index 6d92a266369b9..b692cf3161901 100644 --- a/src/core/server/status/status_service.test.ts +++ b/src/core/server/status/status_service.test.ts @@ -93,7 +93,7 @@ describe('StatusService', () => { }); const statusUpdates: CoreStatus[] = []; - const subscription = setup.core$.subscribe(status => statusUpdates.push(status)); + const subscription = setup.core$.subscribe((status) => statusUpdates.push(status)); elasticsearch$.next(available); elasticsearch$.next(available); @@ -198,7 +198,7 @@ describe('StatusService', () => { }); const statusUpdates: ServiceStatus[] = []; - const subscription = setup.overall$.subscribe(status => statusUpdates.push(status)); + const subscription = setup.overall$.subscribe((status) => statusUpdates.push(status)); elasticsearch$.next(available); elasticsearch$.next(available); diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts index b6697d8221951..ef7bed9587245 100644 --- a/src/core/server/status/status_service.ts +++ b/src/core/server/status/status_service.ts @@ -47,7 +47,7 @@ export class StatusService implements CoreService { public setup(core: SetupDeps) { const core$ = this.setupCoreStatus(core); const overall$: Observable = core$.pipe( - map(coreStatus => { + map((coreStatus) => { const summary = getSummaryStatus(Object.entries(coreStatus)); this.logger.debug(`Recalculated overall status`, { status: summary }); return summary; diff --git a/src/core/server/test_utils.ts b/src/core/server/test_utils.ts index f7e6fbcd0c131..6b16fe3bdef61 100644 --- a/src/core/server/test_utils.ts +++ b/src/core/server/test_utils.ts @@ -19,3 +19,4 @@ export { createHttpServer } from './http/test_utils'; export { ServiceStatusLevelSnapshotSerializer } from './status/test_utils'; +export { setupServer } from './saved_objects/routes/test_utils'; diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index eab96cc80c883..9c5a0625e8fd0 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -27,7 +27,7 @@ import { getUpgradeableConfigMock } from './get_upgradeable_config.test.mock'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; const chance = new Chance(); -describe('uiSettings/createOrUpgradeSavedConfig', function() { +describe('uiSettings/createOrUpgradeSavedConfig', function () { afterEach(() => jest.resetAllMocks()); const version = '4.0.1'; @@ -73,7 +73,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }; } - describe('nothing is upgradeable', function() { + describe('nothing is upgradeable', function () { it('should create config with current version and buildNum', async () => { const { run, savedObjectsClient } = setup(); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts index a916d4c3317a6..abf33658db545 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts @@ -43,5 +43,5 @@ export async function getUpgradeableConfig({ }); // try to find a config that we can upgrade - return savedConfigs.find(savedConfig => isConfigVersionUpgradeable(savedConfig.id, version)); + return savedConfigs.find((savedConfig) => isConfigVersionUpgradeable(savedConfig.id, version)); } diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts index 218de8e7acb3a..49d8137a65d39 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts @@ -38,9 +38,9 @@ describe('createOrUpgradeSavedConfig()', () => { let kbnServer: TestKibanaUtils['kbnServer']; - beforeAll(async function() { + beforeAll(async function () { servers = createTestServers({ - adjustTimeout: t => { + adjustTimeout: (t) => { jest.setTimeout(t); }, }); @@ -86,7 +86,7 @@ describe('createOrUpgradeSavedConfig()', () => { await kbn.stop(); }, 30000); - it('upgrades the previous version on each increment', async function() { + it('upgrades the previous version on each increment', async function () { jest.setTimeout(30000); // ------------------------------------ // upgrade to 5.4.0 diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts index feb63817fe073..5d8c4b01d0664 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts @@ -19,7 +19,7 @@ import { isConfigVersionUpgradeable } from './is_config_version_upgradeable'; -describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { +describe('savedObjects/health_check/isConfigVersionUpgradeable', function () { function isUpgradeableTest(savedVersion: string, kibanaVersion: string, expected: boolean) { it(`should return ${expected} for config version ${savedVersion} and kibana version ${kibanaVersion}`, () => { expect(isConfigVersionUpgradeable(savedVersion, kibanaVersion)).toBe(expected); diff --git a/src/core/server/ui_settings/integration_tests/index.test.ts b/src/core/server/ui_settings/integration_tests/index.test.ts index db271761b39ea..e704532ee4cdf 100644 --- a/src/core/server/ui_settings/integration_tests/index.test.ts +++ b/src/core/server/ui_settings/integration_tests/index.test.ts @@ -23,7 +23,7 @@ import { docExistsSuite } from './doc_exists'; import { docMissingSuite } from './doc_missing'; import { docMissingAndIndexReadOnlySuite } from './doc_missing_and_index_read_only'; -describe('uiSettings/routes', function() { +describe('uiSettings/routes', function () { /** * The "doc missing" and "index missing" tests verify how the uiSettings * API behaves in between healthChecks, so they interact with the healthCheck diff --git a/src/core/server/ui_settings/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts index 57448541d68c5..486abd5a0c790 100644 --- a/src/core/server/ui_settings/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -46,7 +46,7 @@ let services: AllServices; export async function startServers() { servers = createTestServers({ - adjustTimeout: t => jest.setTimeout(t), + adjustTimeout: (t) => jest.setTimeout(t), settings: { kbn: { uiSettings: { diff --git a/src/core/server/ui_settings/saved_objects/ui_settings.ts b/src/core/server/ui_settings/saved_objects/ui_settings.ts index 1bea65ddee924..0eab40a7b3a5d 100644 --- a/src/core/server/ui_settings/saved_objects/ui_settings.ts +++ b/src/core/server/ui_settings/saved_objects/ui_settings.ts @@ -38,7 +38,7 @@ export const uiSettingsType: SavedObjectsType = { importableAndExportable: true, getInAppUrl() { return { - path: `/app/kibana#/management/kibana/settings`, + path: `/app/management/kibana/settings`, uiCapabilitiesPath: 'advancedSettings.show', }; }, diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index 076e1de4458d7..a29f16a90daeb 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -84,11 +84,6 @@ export interface InternalUiSettingsServiceSetup { * @param settings */ register(settings: Record): void; - /** - * Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient} - * @param savedObjectsClient - */ - asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; } /** @public */ diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index 76c8284175f11..f168784a93330 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -122,7 +122,7 @@ export class UiSettingsClient implements IUiSettingsClient { async removeMany(keys: string[]) { const changes: Record = {}; - keys.forEach(key => { + keys.forEach((key) => { changes[key] = null; }); await this.setMany(changes); diff --git a/src/core/server/ui_settings/ui_settings_service.mock.ts b/src/core/server/ui_settings/ui_settings_service.mock.ts index cd781e9759b07..83cea6d7ab3e2 100644 --- a/src/core/server/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -46,11 +46,8 @@ const createClientMock = () => { const createSetupMock = () => { const mocked: jest.Mocked = { register: jest.fn(), - asScopedToClient: jest.fn(), }; - mocked.asScopedToClient.mockReturnValue(createClientMock()); - return mocked; }; diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts index 08400f56ad281..ebcb0cf1d762f 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -67,34 +67,6 @@ describe('uiSettings', () => { expect(setupDeps.savedObjects.registerType).toHaveBeenCalledWith(uiSettingsType); }); - describe('#asScopedToClient', () => { - it('passes saved object type "config" to UiSettingsClient', async () => { - const setup = await service.setup(setupDeps); - setup.asScopedToClient(savedObjectsClient); - expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); - expect(MockUiSettingsClientConstructor.mock.calls[0][0].type).toBe('config'); - }); - - it('passes overrides to UiSettingsClient', async () => { - const setup = await service.setup(setupDeps); - setup.asScopedToClient(savedObjectsClient); - expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); - expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toBe(overrides); - expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual(overrides); - }); - - it('passes a copy of set defaults to UiSettingsClient', async () => { - const setup = await service.setup(setupDeps); - - setup.register(defaults); - setup.asScopedToClient(savedObjectsClient); - expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); - - expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).toEqual(defaults); - expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).not.toBe(defaults); - }); - }); - describe('#register', () => { it('throws if registers the same key twice', async () => { const setup = await service.setup(setupDeps); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index 83e66cf6dd06d..93593b29221da 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -65,7 +65,6 @@ export class UiSettingsService return { register: this.register.bind(this), - asScopedToClient: this.getScopedClientFactory(), }; } diff --git a/src/core/server/utils/crypto/pkcs12.test.ts b/src/core/server/utils/crypto/pkcs12.test.ts index e9eb72fe395ff..ccfeea27c79c6 100644 --- a/src/core/server/utils/crypto/pkcs12.test.ts +++ b/src/core/server/utils/crypto/pkcs12.test.ts @@ -65,7 +65,7 @@ describe('#readPkcs12Keystore', () => { }; const expectCA = (pkcs12ReadResult: Pkcs12ReadResult, ca = [pemCA]) => { - const result = pkcs12ReadResult.ca?.map(x => reformatPem(x)); + const result = pkcs12ReadResult.ca?.map((x) => reformatPem(x)); expect(result).toEqual(ca); }; @@ -199,7 +199,7 @@ describe('#readPkcs12Keystore', () => { describe('#readPkcs12Truststore', () => { it('reads all certificates into one CA array and discards any certificates that have keys', () => { const ca = readPkcs12Truststore(ES_P12_PATH, ES_P12_PASSWORD); - const result = ca?.map(x => reformatPem(x)); + const result = ca?.map((x) => reformatPem(x)); expect(result).toEqual([pemCA]); }); }); diff --git a/src/core/server/utils/crypto/pkcs12.ts b/src/core/server/utils/crypto/pkcs12.ts index 74f22bb685c2f..7de68a71ad72e 100644 --- a/src/core/server/utils/crypto/pkcs12.ts +++ b/src/core/server/utils/crypto/pkcs12.ts @@ -115,9 +115,9 @@ const getCerts = (p12: pkcs12.Pkcs12Pfx, pubKey?: PublicKeyData) => { let ca; let cert; if (bags && bags.length) { - const certs = bags.map(convertCert).filter(x => x !== undefined); - cert = certs.find(x => doesPubKeyMatch(x!.publicKeyData, pubKey))?.cert; - ca = certs.filter(x => !doesPubKeyMatch(x!.publicKeyData, pubKey)).map(x => x!.cert); + const certs = bags.map(convertCert).filter((x) => x !== undefined); + cert = certs.find((x) => doesPubKeyMatch(x!.publicKeyData, pubKey))?.cert; + ca = certs.filter((x) => !doesPubKeyMatch(x!.publicKeyData, pubKey)).map((x) => x!.cert); if (ca.length === 0) { ca = undefined; } diff --git a/src/core/server/uuid/resolve_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts index efc90c07c1fa6..eab027b532ddb 100644 --- a/src/core/server/uuid/resolve_uuid.test.ts +++ b/src/core/server/uuid/resolve_uuid.test.ts @@ -68,7 +68,7 @@ const mockWriteFile = (error?: object) => { const getConfigService = (serverUuid: string | undefined) => { const configService = configServiceMock.create(); - configService.atPath.mockImplementation(path => { + configService.atPath.mockImplementation((path) => { if (path === 'path') { return new BehaviorSubject({ data: 'data-folder', diff --git a/src/core/server/uuid/resolve_uuid.ts b/src/core/server/uuid/resolve_uuid.ts index 516357e10d3f7..c3e79cc519a1b 100644 --- a/src/core/server/uuid/resolve_uuid.ts +++ b/src/core/server/uuid/resolve_uuid.ts @@ -44,14 +44,8 @@ export async function resolveInstanceUuid({ logger: Logger; }): Promise { const [pathConfig, serverConfig] = await Promise.all([ - configService - .atPath(pathConfigDef.path) - .pipe(take(1)) - .toPromise(), - configService - .atPath(httpConfigDef.path) - .pipe(take(1)) - .toPromise(), + configService.atPath(pathConfigDef.path).pipe(take(1)).toPromise(), + configService.atPath(httpConfigDef.path).pipe(take(1)).toPromise(), ]); const uuidFilePath = join(pathConfig.data, FILE_NAME); diff --git a/src/core/types/app_category.ts b/src/core/types/app_category.ts index 8b39889b43a82..396cb07b437a1 100644 --- a/src/core/types/app_category.ts +++ b/src/core/types/app_category.ts @@ -30,7 +30,7 @@ export interface AppCategory { id: string; /** - * Label used for cateogry name. + * Label used for category name. * Also used as aria-label if one isn't set. */ label: string; diff --git a/src/core/types/index.ts b/src/core/types/index.ts index 346b4cfce70c1..07d7789d235ec 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -26,3 +26,4 @@ export * from './capabilities'; export * from './app_category'; export * from './ui_settings'; export * from './saved_objects'; +export * from './serializable'; diff --git a/src/core/types/serializable.ts b/src/core/types/serializable.ts new file mode 100644 index 0000000000000..9e8ea123bea91 --- /dev/null +++ b/src/core/types/serializable.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type Serializable = + | string + | number + | boolean + | null + | SerializableArray + | SerializableRecord; + +// we need interfaces instead of types here to allow cyclic references +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SerializableArray extends Array {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SerializableRecord extends Record {} diff --git a/src/core/utils/context.test.ts b/src/core/utils/context.test.ts index 4bfeddc2e08c9..dcc6c63bcf545 100644 --- a/src/core/utils/context.test.ts +++ b/src/core/utils/context.test.ts @@ -75,24 +75,24 @@ describe('ContextContainer', () => { coreId ); expect.assertions(8); - contextContainer.registerContext(coreId, 'core1', context => { + contextContainer.registerContext(coreId, 'core1', (context) => { expect(context).toEqual({}); return 'core'; }); - contextContainer.registerContext(pluginA, 'ctxFromA', context => { + contextContainer.registerContext(pluginA, 'ctxFromA', (context) => { expect(context).toEqual({ core1: 'core' }); return 'aString'; }); - contextContainer.registerContext(pluginB, 'ctxFromB', context => { + contextContainer.registerContext(pluginB, 'ctxFromB', (context) => { expect(context).toEqual({ core1: 'core', ctxFromA: 'aString' }); return 299; }); - contextContainer.registerContext(pluginC, 'ctxFromC', context => { + contextContainer.registerContext(pluginC, 'ctxFromC', (context) => { expect(context).toEqual({ core1: 'core', ctxFromA: 'aString', ctxFromB: 299 }); return false; }); - contextContainer.registerContext(pluginD, 'ctxFromD', context => { + contextContainer.registerContext(pluginD, 'ctxFromD', (context) => { expect(context).toEqual({ core1: 'core' }); return {}; }); @@ -129,13 +129,13 @@ describe('ContextContainer', () => { coreId ); contextContainer - .registerContext(pluginA, 'ctxFromA', context => { + .registerContext(pluginA, 'ctxFromA', (context) => { expect(context).toEqual({ core1: 'core', core2: 101 }); return `aString ${context.core1} ${context.core2}`; }) .registerContext(coreId, 'core1', () => 'core') .registerContext(coreId, 'core2', () => 101) - .registerContext(pluginB, 'ctxFromB', context => { + .registerContext(pluginB, 'ctxFromB', (context) => { expect(context).toEqual({ core1: 'core', core2: 101, ctxFromA: 'aString core 101' }); return 277; }); @@ -161,11 +161,11 @@ describe('ContextContainer', () => { ); contextContainer - .registerContext(coreId, 'core1', context => { + .registerContext(coreId, 'core1', (context) => { expect(context).toEqual({}); return 'core'; }) - .registerContext(coreId, 'core2', context => { + .registerContext(coreId, 'core2', (context) => { expect(context).toEqual({ core1: 'core' }); return 101; }); @@ -189,8 +189,8 @@ describe('ContextContainer', () => { ); contextContainer - .registerContext(coreId, 'core1', context => 'core') - .registerContext(pluginA, 'ctxFromA', context => 'aString'); + .registerContext(coreId, 'core1', (context) => 'core') + .registerContext(pluginA, 'ctxFromA', (context) => 'aString'); const rawHandler1 = jest.fn(() => 'handler1'); const handler1 = contextContainer.createHandler(coreId, rawHandler1); diff --git a/src/core/utils/context.ts b/src/core/utils/context.ts index de311f91d56fa..941bbceb0cd92 100644 --- a/src/core/utils/context.ts +++ b/src/core/utils/context.ts @@ -304,7 +304,7 @@ export class ContextContainer> // Contexts source created ...(this.contextNamesBySource.get(pluginId) || []), // Contexts sources's dependencies created - ...flatten(pluginDeps.map(p => this.contextNamesBySource.get(p) || [])), + ...flatten(pluginDeps.map((p) => this.contextNamesBySource.get(p) || [])), ]); } } diff --git a/src/core/utils/integration_tests/deep_freeze.test.ts b/src/core/utils/integration_tests/deep_freeze.test.ts index e6625542bc38a..f58e298fecfbf 100644 --- a/src/core/utils/integration_tests/deep_freeze.test.ts +++ b/src/core/utils/integration_tests/deep_freeze.test.ts @@ -30,7 +30,7 @@ it( execa('tsc', ['--noEmit'], { cwd: resolve(__dirname, '__fixtures__/frozen_object_mutation'), preferLocal: true, - }).catch(err => err.stdout) + }).catch((err) => err.stdout) ).resolves.toMatchInlineSnapshot(` "index.ts(28,12): error TS2540: Cannot assign to 'baz' because it is a read-only property. index.ts(36,11): error TS2540: Cannot assign to 'bar' because it is a read-only property." diff --git a/src/core/utils/map_utils.ts b/src/core/utils/map_utils.ts index 47a1d6b34b99f..2f6ec94da1283 100644 --- a/src/core/utils/map_utils.ts +++ b/src/core/utils/map_utils.ts @@ -27,7 +27,7 @@ export function mapValuesOfMap(map: Map, mapper: (item: G) => H): export function groupIntoMap(collection: T[], groupBy: (item: T) => G): Map { const map = new Map(); - collection.forEach(item => { + collection.forEach((item) => { const key = groupBy(item); const values = map.get(key) || []; values.push(item); diff --git a/src/core/utils/promise.test.ts b/src/core/utils/promise.test.ts index 97d16c5158c7b..b78b6bc5120f9 100644 --- a/src/core/utils/promise.test.ts +++ b/src/core/utils/promise.test.ts @@ -20,7 +20,7 @@ import { withTimeout } from './promise'; const delay = (ms: number, resolveValue?: any) => - new Promise(resolve => setTimeout(resolve, ms, resolveValue)); + new Promise((resolve) => setTimeout(resolve, ms, resolveValue)); describe('withTimeout', () => { it('resolves with a promise value if resolved in given timeout', async () => { @@ -44,7 +44,7 @@ describe('withTimeout', () => { await expect( withTimeout({ - promise: new Promise(i => i), + promise: new Promise((i) => i), timeout: 10, errorMessage: 'error-message', }) diff --git a/src/core/utils/unset.ts b/src/core/utils/unset.ts index 8008d4ee08ba3..88bf2503c6d69 100644 --- a/src/core/utils/unset.ts +++ b/src/core/utils/unset.ts @@ -32,8 +32,8 @@ import { get } from './get'; export function unset(obj: OBJ, atPath: string) { const paths = atPath .split('.') - .map(s => s.trim()) - .filter(v => v !== ''); + .map((s) => s.trim()) + .filter((v) => v !== ''); if (paths.length === 0) { return; } diff --git a/src/core/utils/url.test.ts b/src/core/utils/url.test.ts index 419c0cda2b8cb..7e9b6adfd3f49 100644 --- a/src/core/utils/url.test.ts +++ b/src/core/utils/url.test.ts @@ -32,7 +32,7 @@ describe('modifyUrl()', () => { test('supports modifying the passed object', () => { expect( - modifyUrl('http://localhost', parsed => { + modifyUrl('http://localhost', (parsed) => { parsed.port = '9999'; parsed.auth = 'foo:bar'; return parsed; @@ -42,7 +42,7 @@ describe('modifyUrl()', () => { test('supports changing pathname', () => { expect( - modifyUrl('http://localhost/some/path', parsed => { + modifyUrl('http://localhost/some/path', (parsed) => { parsed.pathname += '/subpath'; return parsed; }) @@ -51,7 +51,7 @@ describe('modifyUrl()', () => { test('supports changing port', () => { expect( - modifyUrl('http://localhost:5601', parsed => { + modifyUrl('http://localhost:5601', (parsed) => { parsed.port = (Number(parsed.port!) + 1).toString(); return parsed; }) @@ -60,7 +60,7 @@ describe('modifyUrl()', () => { test('supports changing protocol', () => { expect( - modifyUrl('http://localhost', parsed => { + modifyUrl('http://localhost', (parsed) => { parsed.protocol = 'mail'; parsed.slashes = false; parsed.pathname = null; diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index 5fd2a9ff93a8a..6a464eef209ec 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -46,6 +46,7 @@ Object { "createArchives": true, "createDebPackage": false, "createDockerPackage": false, + "createDockerUbiPackage": false, "createRpmPackage": false, "downloadFreshNode": true, "isRelease": false, @@ -68,6 +69,7 @@ Object { "createArchives": true, "createDebPackage": true, "createDockerPackage": true, + "createDockerUbiPackage": true, "createRpmPackage": true, "downloadFreshNode": true, "isRelease": false, @@ -90,6 +92,7 @@ Object { "createArchives": true, "createDebPackage": false, "createDockerPackage": false, + "createDockerUbiPackage": false, "createRpmPackage": true, "downloadFreshNode": true, "isRelease": false, @@ -112,6 +115,7 @@ Object { "createArchives": true, "createDebPackage": true, "createDockerPackage": false, + "createDockerUbiPackage": false, "createRpmPackage": false, "downloadFreshNode": true, "isRelease": false, @@ -134,6 +138,30 @@ Object { "createArchives": true, "createDebPackage": false, "createDockerPackage": true, + "createDockerUbiPackage": true, + "createRpmPackage": false, + "downloadFreshNode": true, + "isRelease": false, + "targetAllPlatforms": true, + "versionQualifier": "", + }, + "log": "", + "showHelp": false, + "unknownFlags": Array [], +} +`); +}); + +it('limits packages if --docker passed with --skip-docker-ubi and --all-platforms', () => { + expect(fn('--all-platforms', '--docker', '--skip-docker-ubi')).toMatchInlineSnapshot(` +Object { + "buildArgs": Object { + "buildDefaultDist": true, + "buildOssDist": true, + "createArchives": true, + "createDebPackage": false, + "createDockerPackage": true, + "createDockerUbiPackage": false, "createRpmPackage": false, "downloadFreshNode": true, "isRelease": false, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index 49bb8e150fcdc..1ff42d524c596 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -40,6 +40,7 @@ export function readCliArgs(argv: string[]): ParsedArgs { 'rpm', 'deb', 'docker', + 'skip-docker-ubi', 'release', 'skip-node-download', 'verbose', @@ -63,7 +64,7 @@ export function readCliArgs(argv: string[]): ParsedArgs { oss: null, 'version-qualifier': '', }, - unknown: flag => { + unknown: (flag) => { unknownFlags.push(flag); return false; }, @@ -116,6 +117,7 @@ export function readCliArgs(argv: string[]): ParsedArgs { createRpmPackage: isOsPackageDesired('rpm'), createDebPackage: isOsPackageDesired('deb'), createDockerPackage: isOsPackageDesired('docker'), + createDockerUbiPackage: isOsPackageDesired('docker') && !Boolean(flags['skip-docker-ubi']), targetAllPlatforms: Boolean(flags['all-platforms']), }, }; diff --git a/src/dev/build/build_distributables.js b/src/dev/build/build_distributables.js index 6c2efeebc60c3..66f0c0355c2d9 100644 --- a/src/dev/build/build_distributables.js +++ b/src/dev/build/build_distributables.js @@ -30,11 +30,13 @@ import { CleanTypescriptTask, CleanNodeBuildsTask, CleanTask, + CopyBinScriptsTask, CopySourceTask, CreateArchivesSourcesTask, CreateArchivesTask, CreateDebPackageTask, CreateDockerPackageTask, + CreateDockerUbiPackageTask, CreateEmptyDirsAndFilesTask, CreateNoticeFileTask, CreatePackageJsonTask, @@ -68,6 +70,7 @@ export async function buildDistributables(options) { createRpmPackage, createDebPackage, createDockerPackage, + createDockerUbiPackage, versionQualifier, targetAllPlatforms, } = options; @@ -108,6 +111,7 @@ export async function buildDistributables(options) { * run platform-generic build tasks */ await run(CopySourceTask); + await run(CopyBinScriptsTask); await run(CreateEmptyDirsAndFilesTask); await run(CreateReadmeTask); await run(TranspileBabelTask); @@ -156,8 +160,11 @@ export async function buildDistributables(options) { await run(CreateRpmPackageTask); } if (createDockerPackage) { - // control w/ --docker or --skip-os-packages + // control w/ --docker or --skip-docker-ubi or --skip-os-packages await run(CreateDockerPackageTask); + if (createDockerUbiPackage) { + await run(CreateDockerUbiPackageTask); + } } /** diff --git a/src/dev/build/cli.js b/src/dev/build/cli.js index f23832dfcdd56..9d23f92a3bafd 100644 --- a/src/dev/build/cli.js +++ b/src/dev/build/cli.js @@ -52,6 +52,7 @@ if (showHelp) { --rpm {dim Only build the rpm package} --deb {dim Only build the deb package} --docker {dim Only build the docker image} + --skip-docker-ubi {dim Don't build the docker ubi image} --release {dim Produce a release-ready distributable} --version-qualifier {dim Suffix version with a qualifier} --skip-node-download {dim Reuse existing downloads of node.js} @@ -62,7 +63,7 @@ if (showHelp) { process.exit(1); } -buildDistributables({ log, ...buildArgs }).catch(error => { +buildDistributables({ log, ...buildArgs }).catch((error) => { if (!isErrorLogged(error)) { log.error('Uncaught error'); log.error(error); diff --git a/src/dev/build/lib/__tests__/config.js b/src/dev/build/lib/__tests__/config.js index d9238ad1eef43..d2f408378da25 100644 --- a/src/dev/build/lib/__tests__/config.js +++ b/src/dev/build/lib/__tests__/config.js @@ -26,7 +26,7 @@ import { getConfig } from '../config'; import { getVersionInfo } from '../version_info'; describe('dev/build/lib/config', () => { - const setup = async function({ targetAllPlatforms = true } = {}) { + const setup = async function ({ targetAllPlatforms = true } = {}) { const isRelease = Boolean(Math.round(Math.random())); const config = await getConfig({ isRelease, @@ -78,7 +78,7 @@ describe('dev/build/lib/config', () => { expect( config .getTargetPlatforms() - .map(p => p.getName()) + .map((p) => p.getName()) .sort() ).to.eql(['darwin', 'linux', 'windows']); }); @@ -99,7 +99,7 @@ describe('dev/build/lib/config', () => { expect( config .getTargetPlatforms() - .map(p => p.getName()) + .map((p) => p.getName()) .sort() ).to.eql(['darwin', 'linux', 'windows']); }); diff --git a/src/dev/build/lib/__tests__/exec.js b/src/dev/build/lib/__tests__/exec.js index 4123c0e3e8ed1..8e122c65132ac 100644 --- a/src/dev/build/lib/__tests__/exec.js +++ b/src/dev/build/lib/__tests__/exec.js @@ -31,7 +31,7 @@ describe('dev/build/lib/exec', () => { const log = new ToolingLog({ level: 'verbose', writeTo: { - write: chunk => { + write: (chunk) => { onLogLine(stripAnsi(chunk)); }, }, diff --git a/src/dev/build/lib/__tests__/fs.js b/src/dev/build/lib/__tests__/fs.js index 9125ace28ce8a..0b2db4c538fb8 100644 --- a/src/dev/build/lib/__tests__/fs.js +++ b/src/dev/build/lib/__tests__/fs.js @@ -35,9 +35,7 @@ const isWindows = /^win/.test(process.platform); // get the mode of a file as a string, like 777, or 644, function getCommonMode(path) { - return statSync(path) - .mode.toString(8) - .slice(-3); + return statSync(path).mode.toString(8).slice(-3); } function assertNonAbsoluteError(error) { diff --git a/src/dev/build/lib/__tests__/runner.js b/src/dev/build/lib/__tests__/runner.js index 0cf68ed1152cd..314c2dd45d50f 100644 --- a/src/dev/build/lib/__tests__/runner.js +++ b/src/dev/build/lib/__tests__/runner.js @@ -46,8 +46,8 @@ describe('dev/build/lib/runner', () => { getLogTag: sinon.match.func, }); - const ossBuildMatcher = buildMatcher.and(sinon.match(b => b.isOss(), 'is oss build')); - const defaultBuildMatcher = buildMatcher.and(sinon.match(b => !b.isOss(), 'is not oss build')); + const ossBuildMatcher = buildMatcher.and(sinon.match((b) => b.isOss(), 'is oss build')); + const defaultBuildMatcher = buildMatcher.and(sinon.match((b) => !b.isOss(), 'is not oss build')); afterEach(() => sandbox.reset()); @@ -159,9 +159,7 @@ describe('dev/build/lib/runner', () => { }); throw new Error('expected run() to reject'); } catch (error) { - expect(error) - .to.have.property('message') - .be('FOO'); + expect(error).to.have.property('message').be('FOO'); sinon.assert.calledWith(onLogLine, sinon.match(/FOO/)); expect(isErrorLogged(error)).to.be(true); } @@ -177,9 +175,7 @@ describe('dev/build/lib/runner', () => { throw new Error('expected run() to reject'); } catch (error) { - expect(error) - .to.have.property('message') - .be('FOO'); + expect(error).to.have.property('message').be('FOO'); sinon.assert.neverCalledWith(onLogLine, sinon.match(/FOO/)); expect(isErrorLogged(error)).to.be(true); } diff --git a/src/dev/build/lib/__tests__/version_info.js b/src/dev/build/lib/__tests__/version_info.js index a4e9b49a570e6..a7329642e4f9a 100644 --- a/src/dev/build/lib/__tests__/version_info.js +++ b/src/dev/build/lib/__tests__/version_info.js @@ -34,10 +34,7 @@ describe('dev/build/lib/version_info', () => { expect(versionInfo) .to.have.property('buildSha') .match(/^[0-9a-f]{40}$/); - expect(versionInfo) - .to.have.property('buildNumber') - .a('number') - .greaterThan(1000); + expect(versionInfo).to.have.property('buildNumber').a('number').greaterThan(1000); }); }); describe('isRelease = false', () => { @@ -54,10 +51,7 @@ describe('dev/build/lib/version_info', () => { expect(versionInfo) .to.have.property('buildSha') .match(/^[0-9a-f]{40}$/); - expect(versionInfo) - .to.have.property('buildNumber') - .a('number') - .greaterThan(1000); + expect(versionInfo).to.have.property('buildNumber').a('number').greaterThan(1000); }); }); diff --git a/src/dev/build/lib/config.js b/src/dev/build/lib/config.js index aed47da421460..cd762d9bb1f20 100644 --- a/src/dev/build/lib/config.js +++ b/src/dev/build/lib/config.js @@ -112,7 +112,7 @@ export async function getConfig({ isRelease, targetAllPlatforms, versionQualifie * @return {Platform} */ getLinuxPlatform() { - return platforms.find(p => p.isLinux()); + return platforms.find((p) => p.isLinux()); } /** @@ -120,7 +120,7 @@ export async function getConfig({ isRelease, targetAllPlatforms, versionQualifie * @return {Platform} */ getWindowsPlatform() { - return platforms.find(p => p.isWindows()); + return platforms.find((p) => p.isWindows()); } /** @@ -128,7 +128,7 @@ export async function getConfig({ isRelease, targetAllPlatforms, versionQualifie * @return {Platform} */ getMacPlatform() { - return platforms.find(p => p.isMac()); + return platforms.find((p) => p.isMac()); } /** diff --git a/src/dev/build/lib/exec.js b/src/dev/build/lib/exec.js index c83bc13a44302..5e47500c72c5c 100644 --- a/src/dev/build/lib/exec.js +++ b/src/dev/build/lib/exec.js @@ -34,5 +34,5 @@ export async function exec(log, cmd, args, options = {}) { preferLocal: true, }); - await watchStdioForLine(proc, line => log[level](line), exitAfter); + await watchStdioForLine(proc, (line) => log[level](line), exitAfter); } diff --git a/src/dev/build/lib/fs.js b/src/dev/build/lib/fs.js index 278f6851f3421..864a07e837c3f 100644 --- a/src/dev/build/lib/fs.js +++ b/src/dev/build/lib/fs.js @@ -82,7 +82,7 @@ export async function read(path) { export async function getChildPaths(path) { assertAbsolute(path); const childNames = await readdirAsync(path); - return childNames.map(name => resolve(path, name)); + return childNames.map((name) => resolve(path, name)); } export async function deleteAll(patterns, log) { @@ -122,8 +122,8 @@ export async function deleteEmptyFolders(log, rootFolderPath, foldersToKeep) { // Delete empty is used to gather all the empty folders and // then we use del to actually delete them const emptyFoldersList = await deleteEmpty(rootFolderPath, { dryRun: true }); - const foldersToDelete = emptyFoldersList.filter(folderToDelete => { - return !foldersToKeep.some(folderToKeep => folderToDelete.includes(folderToKeep)); + const foldersToDelete = emptyFoldersList.filter((folderToDelete) => { + return !foldersToKeep.some((folderToKeep) => folderToDelete.includes(folderToKeep)); }); const deletedEmptyFolders = await del(foldersToDelete, { concurrency: 4, @@ -159,7 +159,7 @@ export async function copyAll(sourceDir, destination, options = {}) { base: destination, dot, }), - createMapStream(file => utimesAsync(file.path, time, time)), + createMapStream((file) => utimesAsync(file.path, time, time)), ]); } } @@ -171,7 +171,7 @@ export async function getFileHash(path, algo) { const readStream = fs.createReadStream(path); await new Promise((resolve, reject) => { readStream - .on('data', chunk => hash.update(chunk)) + .on('data', (chunk) => hash.update(chunk)) .on('error', reject) .on('end', resolve); }); diff --git a/src/dev/build/lib/scan.ts b/src/dev/build/lib/scan.ts index 45e61ca051879..1d77101bdfdb2 100644 --- a/src/dev/build/lib/scan.ts +++ b/src/dev/build/lib/scan.ts @@ -50,7 +50,7 @@ export function scan$(directory: string) { return Rx.concat( [path], getStat$(path).pipe( - mergeMap(stat => (stat.isDirectory() ? getChildPath$(path) : Rx.EMPTY)), + mergeMap((stat) => (stat.isDirectory() ? getChildPath$(path) : Rx.EMPTY)), mergeMap(getPaths$) ) ); diff --git a/src/dev/build/lib/scan_copy.test.ts b/src/dev/build/lib/scan_copy.test.ts index cda009f9137f8..ba693770445dc 100644 --- a/src/dev/build/lib/scan_copy.test.ts +++ b/src/dev/build/lib/scan_copy.test.ts @@ -31,10 +31,7 @@ const FIXTURES = resolve(__dirname, '__tests__/fixtures'); const WORLD_EXECUTABLE = resolve(FIXTURES, 'bin/world_executable'); const TMP = resolve(__dirname, '__tests__/__tmp__'); -const getCommonMode = (path: string) => - statSync(path) - .mode.toString(8) - .slice(-3); +const getCommonMode = (path: string) => statSync(path).mode.toString(8).slice(-3); // ensure WORLD_EXECUTABLE is actually executable by all beforeAll(async () => { @@ -104,7 +101,7 @@ it('applies filter function specified', async () => { await scanCopy({ source: FIXTURES, destination, - filter: record => !record.name.includes('bar'), + filter: (record) => !record.name.includes('bar'), }); expect((await getChildPaths(resolve(destination, 'foo_dir'))).sort()).toEqual([ diff --git a/src/dev/build/lib/scan_copy.ts b/src/dev/build/lib/scan_copy.ts index 0a4bfdc8d0b4f..7fa2d9b9d25a3 100644 --- a/src/dev/build/lib/scan_copy.ts +++ b/src/dev/build/lib/scan_copy.ts @@ -72,20 +72,20 @@ export async function scanCopy(options: Options) { const getChildRecords = async (parent: Record) => { const names = await readdirAsync(parent.absolute); const records = await Promise.all( - names.map(async name => { + names.map(async (name) => { const absolute = join(parent.absolute, name); const stat = await statAsync(absolute); return new Record(stat.isDirectory(), name, absolute, join(parent.absoluteDest, name)); }) ); - return records.filter(record => (filter ? filter(record) : true)); + return records.filter((record) => (filter ? filter(record) : true)); }; // create or copy each child of a directory const copyChildren = async (record: Record) => { const children = await getChildRecords(record); - await Promise.all(children.map(async child => await copy(child))); + await Promise.all(children.map(async (child) => await copy(child))); }; // create or copy a record and recurse into directories diff --git a/src/dev/build/lib/scan_delete.ts b/src/dev/build/lib/scan_delete.ts index cb4e64ce1b5f9..6e41d207e3111 100644 --- a/src/dev/build/lib/scan_delete.ts +++ b/src/dev/build/lib/scan_delete.ts @@ -49,7 +49,7 @@ export async function scanDelete(options: Options) { const { directory, regularExpressions, concurrency = 20, excludePaths } = options; assertAbsolute(directory); - (excludePaths || []).forEach(excluded => assertAbsolute(excluded)); + (excludePaths || []).forEach((excluded) => assertAbsolute(excluded)); // get an observable of absolute paths within a directory const getChildPath$ = (path: string) => @@ -66,12 +66,12 @@ export async function scanDelete(options: Options) { return Rx.EMPTY; } - if (regularExpressions.some(re => re.test(path))) { + if (regularExpressions.some((re) => re.test(path))) { return Rx.of(path); } return getStat$(path).pipe( - mergeMap(stat => (stat.isDirectory() ? getChildPath$(path) : Rx.EMPTY)), + mergeMap((stat) => (stat.isDirectory() ? getChildPath$(path) : Rx.EMPTY)), mergeMap(getPathsToDelete$) ); }; @@ -79,7 +79,7 @@ export async function scanDelete(options: Options) { return await Rx.of(directory) .pipe( mergeMap(getPathsToDelete$), - mergeMap(async path => await del(path), concurrency), + mergeMap(async (path) => await del(path), concurrency), count() ) .toPromise(); diff --git a/src/dev/build/tasks/bin/copy_bin_scripts_task.js b/src/dev/build/tasks/bin/copy_bin_scripts_task.js new file mode 100644 index 0000000000000..f620f12b17d88 --- /dev/null +++ b/src/dev/build/tasks/bin/copy_bin_scripts_task.js @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { copyAll } from '../../lib'; + +export const CopyBinScriptsTask = { + description: 'Copying bin scripts into platform-generic build directory', + + async run(config, log, build) { + await copyAll( + config.resolveFromRepo('src/dev/build/tasks/bin/scripts'), + build.resolvePath('bin') + ); + }, +}; diff --git a/src/dev/build/tasks/bin/index.js b/src/dev/build/tasks/bin/index.js new file mode 100644 index 0000000000000..e970ac5ec044b --- /dev/null +++ b/src/dev/build/tasks/bin/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { CopyBinScriptsTask } from './copy_bin_scripts_task'; diff --git a/bin/kibana b/src/dev/build/tasks/bin/scripts/kibana similarity index 100% rename from bin/kibana rename to src/dev/build/tasks/bin/scripts/kibana diff --git a/bin/kibana-keystore b/src/dev/build/tasks/bin/scripts/kibana-keystore similarity index 100% rename from bin/kibana-keystore rename to src/dev/build/tasks/bin/scripts/kibana-keystore diff --git a/bin/kibana-keystore.bat b/src/dev/build/tasks/bin/scripts/kibana-keystore.bat similarity index 100% rename from bin/kibana-keystore.bat rename to src/dev/build/tasks/bin/scripts/kibana-keystore.bat diff --git a/bin/kibana-plugin b/src/dev/build/tasks/bin/scripts/kibana-plugin similarity index 100% rename from bin/kibana-plugin rename to src/dev/build/tasks/bin/scripts/kibana-plugin diff --git a/bin/kibana-plugin.bat b/src/dev/build/tasks/bin/scripts/kibana-plugin.bat similarity index 100% rename from bin/kibana-plugin.bat rename to src/dev/build/tasks/bin/scripts/kibana-plugin.bat diff --git a/bin/kibana.bat b/src/dev/build/tasks/bin/scripts/kibana.bat similarity index 100% rename from bin/kibana.bat rename to src/dev/build/tasks/bin/scripts/kibana.bat diff --git a/src/dev/build/tasks/clean_tasks.js b/src/dev/build/tasks/clean_tasks.js index b23db67cc1b07..31731e392e5cb 100644 --- a/src/dev/build/tasks/clean_tasks.js +++ b/src/dev/build/tasks/clean_tasks.js @@ -63,8 +63,8 @@ export const CleanExtraFilesFromModulesTask = { description: 'Cleaning tests, examples, docs, etc. from node_modules', async run(config, log, build) { - const makeRegexps = patterns => - patterns.map(pattern => minimatch.makeRe(pattern, { nocase: true })); + const makeRegexps = (patterns) => + patterns.map((pattern) => minimatch.makeRe(pattern, { nocase: true })); const regularExpressions = makeRegexps([ // tests @@ -205,12 +205,12 @@ export const CleanExtraBrowsersTask = { description: 'Cleaning extra browsers from platform-specific builds', async run(config, log, build) { - const getBrowserPathsForPlatform = platform => { - const reportingDir = 'x-pack/legacy/plugins/reporting'; + const getBrowserPathsForPlatform = (platform) => { + const reportingDir = 'x-pack/plugins/reporting'; const chromiumDir = '.chromium'; - const chromiumPath = p => + const chromiumPath = (p) => build.resolvePathForPlatform(platform, reportingDir, chromiumDir, p); - return platforms => { + return (platforms) => { const paths = []; if (platforms.windows) { paths.push(chromiumPath('chromium-*-win32.zip')); diff --git a/src/dev/build/tasks/copy_source_task.js b/src/dev/build/tasks/copy_source_task.js index ee9dc159de47f..ddc6d000bca19 100644 --- a/src/dev/build/tasks/copy_source_task.js +++ b/src/dev/build/tasks/copy_source_task.js @@ -42,7 +42,6 @@ export const CopySourceTask = { '!src/es_archiver/**', '!src/functional_test_runner/**', '!src/dev/**', - 'bin/**', 'typings/**', 'webpackShims/**', 'config/kibana.yml', diff --git a/src/dev/build/tasks/create_archives_sources_task.js b/src/dev/build/tasks/create_archives_sources_task.js index 999fb0481bd4b..53cf750f484a1 100644 --- a/src/dev/build/tasks/create_archives_sources_task.js +++ b/src/dev/build/tasks/create_archives_sources_task.js @@ -24,7 +24,7 @@ export const CreateArchivesSourcesTask = { description: 'Creating platform-specific archive source directories', async run(config, log, build) { await Promise.all( - config.getTargetPlatforms().map(async platform => { + config.getTargetPlatforms().map(async (platform) => { // copy all files from generic build source directory into platform-specific build directory await scanCopy({ source: build.resolvePath(), diff --git a/src/dev/build/tasks/create_package_json_task.js b/src/dev/build/tasks/create_package_json_task.js index b65d3615633a5..e7a410b4c6350 100644 --- a/src/dev/build/tasks/create_package_json_task.js +++ b/src/dev/build/tasks/create_package_json_task.js @@ -50,7 +50,9 @@ export const CreatePackageJsonTask = { }; if (build.isOss()) { - newPkg.workspaces.packages = newPkg.workspaces.packages.filter(p => !p.startsWith('x-pack')); + newPkg.workspaces.packages = newPkg.workspaces.packages.filter( + (p) => !p.startsWith('x-pack') + ); } await write(build.resolvePath('package.json'), JSON.stringify(newPkg, null, ' ')); diff --git a/src/dev/build/tasks/index.js b/src/dev/build/tasks/index.js index 8105fa8a7d5d4..bafb5a2fe115e 100644 --- a/src/dev/build/tasks/index.js +++ b/src/dev/build/tasks/index.js @@ -17,6 +17,7 @@ * under the License. */ +export * from './bin'; export * from './build_packages_task'; export * from './clean_tasks'; export * from './copy_source_task'; diff --git a/src/dev/build/tasks/nodejs/__tests__/download.js b/src/dev/build/tasks/nodejs/__tests__/download.js index 81ed7a6195ae7..49cb9caaaf4ec 100644 --- a/src/dev/build/tasks/nodejs/__tests__/download.js +++ b/src/dev/build/tasks/nodejs/__tests__/download.js @@ -62,7 +62,7 @@ describe('src/dev/build/tasks/nodejs/download', () => { }); const FOO_SHA256 = '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'; - const createSendHandler = send => (req, res) => { + const createSendHandler = (send) => (req, res) => { res.statusCode = 200; res.end(send); }; @@ -91,7 +91,7 @@ describe('src/dev/build/tasks/nodejs/download', () => { new Promise((resolve, reject) => { server.once('error', reject); }), - new Promise(resolve => { + new Promise((resolve) => { server.listen(resolve); }), ]); @@ -206,9 +206,7 @@ describe('src/dev/build/tasks/nodejs/download', () => { }); throw new Error('Expected download() to reject'); } catch (error) { - expect(error) - .to.have.property('message') - .contain('Request failed with status code 500'); + expect(error).to.have.property('message').contain('Request failed with status code 500'); expect(reqCount).to.be(6); } }); @@ -232,9 +230,7 @@ describe('src/dev/build/tasks/nodejs/download', () => { throw new Error('expected download() to reject'); } catch (error) { - expect(error) - .to.have.property('message') - .contain('refusing to download'); + expect(error).to.have.property('message').contain('refusing to download'); } }); }); diff --git a/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js b/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js index 4c94ed776417d..9357735e3f5a3 100644 --- a/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js @@ -91,9 +91,7 @@ describe('src/dev/build/tasks/nodejs/download_node_builds_task', () => { await DownloadNodeBuildsTask.run(config, log); throw new Error('Expected DownloadNodeBuildsTask to reject'); } catch (error) { - expect(error) - .to.have.property('message') - .be('Download failed for reasons'); + expect(error).to.have.property('message').be('Download failed for reasons'); } }); }); diff --git a/src/dev/build/tasks/nodejs/__tests__/verify_existing_node_builds_task.js b/src/dev/build/tasks/nodejs/__tests__/verify_existing_node_builds_task.js index 0fc151258d779..a8f732a869d2d 100644 --- a/src/dev/build/tasks/nodejs/__tests__/verify_existing_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/__tests__/verify_existing_node_builds_task.js @@ -58,7 +58,7 @@ describe('src/dev/build/tasks/nodejs/verify_existing_node_builds_task', () => { } ); - sandbox.stub(FsNS, 'getFileHash').callsFake(path => { + sandbox.stub(FsNS, 'getFileHash').callsFake((path) => { switch (path) { case 'foo:downloadPath': return 'foo:sha256'; diff --git a/src/dev/build/tasks/nodejs/download.js b/src/dev/build/tasks/nodejs/download.js index 0bd10e5b84015..fb3294e2d1221 100644 --- a/src/dev/build/tasks/nodejs/download.js +++ b/src/dev/build/tasks/nodejs/download.js @@ -62,7 +62,7 @@ export async function download(options) { const hash = createHash('sha256'); await new Promise((resolve, reject) => { - response.data.on('data', chunk => { + response.data.on('data', (chunk) => { hash.update(chunk); writeSync(fileHandle, chunk); }); diff --git a/src/dev/build/tasks/nodejs/download_node_builds_task.js b/src/dev/build/tasks/nodejs/download_node_builds_task.js index df841960677c1..86ddb0506f972 100644 --- a/src/dev/build/tasks/nodejs/download_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/download_node_builds_task.js @@ -27,7 +27,7 @@ export const DownloadNodeBuildsTask = { async run(config, log) { const shasums = await getNodeShasums(config.getNodeVersion()); await Promise.all( - config.getNodePlatforms().map(async platform => { + config.getNodePlatforms().map(async (platform) => { const { url, downloadPath, downloadName } = getNodeDownloadInfo(config, platform); await download({ log, diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.js b/src/dev/build/tasks/nodejs/extract_node_builds_task.js index 68e56ac7d4aa6..caf0a389b4cc0 100644 --- a/src/dev/build/tasks/nodejs/extract_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.js @@ -32,7 +32,7 @@ export const ExtractNodeBuildsTask = { description: 'Extracting node.js builds for all platforms', async run(config) { await Promise.all( - config.getNodePlatforms().map(async platform => { + config.getNodePlatforms().map(async (platform) => { const { downloadPath, extractDir } = getNodeDownloadInfo(config, platform); // windows executable is not extractable, it's just an .exe file if (platform.isWindows()) { diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.js b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.js index 1a6adcd5d1db4..b320471fda33f 100644 --- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.js @@ -28,7 +28,7 @@ export const VerifyExistingNodeBuildsTask = { const shasums = await getNodeShasums(config.getNodeVersion()); await Promise.all( - config.getNodePlatforms().map(async platform => { + config.getNodePlatforms().map(async (platform) => { const { downloadPath, downloadName } = getNodeDownloadInfo(config, platform); const sha256 = await getFileHash(downloadPath, 'sha256'); diff --git a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js index 8e8d69a4dfefa..05bfd3ca31a65 100644 --- a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js +++ b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js @@ -34,7 +34,7 @@ export const CleanClientModulesOnDLLTask = { const kbnPkg = config.getKibanaPkg(); const kbnPkgDependencies = (kbnPkg && kbnPkg.dependencies) || {}; const kbnWebpackLoaders = Object.keys(kbnPkgDependencies).filter( - dep => !!dep.includes('-loader') + (dep) => !!dep.includes('-loader') ); // Define the entry points for the server code in order to @@ -44,7 +44,7 @@ export const CleanClientModulesOnDLLTask = { `${baseDir}/src/cli_keystore`, `${baseDir}/src/cli_plugin`, `${baseDir}/x-pack`, - ...kbnWebpackLoaders.map(loader => `${baseDir}/node_modules/${loader}`), + ...kbnWebpackLoaders.map((loader) => `${baseDir}/node_modules/${loader}`), ]; const discoveredLegacyCorePluginEntries = await globby([ `${baseDir}/src/legacy/core_plugins/*/index.js`, @@ -84,7 +84,7 @@ export const CleanClientModulesOnDLLTask = { // consider the top modules as exceptions as the entry points // to look for other exceptions dependent on that one const manualExceptionEntries = [ - ...manualExceptionModules.map(module => `${baseDir}/node_modules/${module}`), + ...manualExceptionModules.map((module) => `${baseDir}/node_modules/${module}`), ]; // dependencies for declared exception modules diff --git a/src/dev/build/tasks/nodejs_modules/webpack_dll.js b/src/dev/build/tasks/nodejs_modules/webpack_dll.js index 72910226bb04a..8de5e582c3d36 100644 --- a/src/dev/build/tasks/nodejs_modules/webpack_dll.js +++ b/src/dev/build/tasks/nodejs_modules/webpack_dll.js @@ -31,7 +31,7 @@ function checkDllEntryAccess(entry, baseDir = '') { export async function getDllEntries(manifestPaths, whiteListedModules, baseDir = '') { // Read and parse all manifests const manifests = await Promise.all( - manifestPaths.map(async manifestPath => JSON.parse(await read(manifestPath))) + manifestPaths.map(async (manifestPath) => JSON.parse(await read(manifestPath))) ); // Process and group modules from all manifests @@ -58,8 +58,8 @@ export async function getDllEntries(manifestPaths, whiteListedModules, baseDir = // Only includes modules who are not in the white list of modules // and that are node_modules - return manifestsModules.filter(entry => { - const isWhiteListed = whiteListedModules.some(nonEntry => + return manifestsModules.filter((entry) => { + const isWhiteListed = whiteListedModules.some((nonEntry) => normalizePosixPath(entry).includes(`node_modules/${nonEntry}`) ); const isNodeModule = entry.includes('node_modules'); @@ -113,7 +113,7 @@ export async function cleanDllModuleFromEntryPath(logger, entryPath) { ]); await deleteAll( - filesToDelete.filter(path => { + filesToDelete.filter((path) => { const relativePath = relative(moduleDir, path); return !relativePath.endsWith('package.json') || relativePath.includes('node_modules'); }) diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.js b/src/dev/build/tasks/os_packages/create_os_package_tasks.js index 0416dac0aad8c..6a00e681ab0ec 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.js +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.js @@ -47,7 +47,13 @@ export const CreateDockerPackageTask = { async run(config, log, build) { // Builds Docker targets for default and oss await runDockerGenerator(config, log, build); + }, +}; +export const CreateDockerUbiPackageTask = { + description: 'Creating docker ubi package', + + async run(config, log, build) { // Builds Docker target default with ubi7 base image await runDockerGeneratorForUBI(config, log, build); }, diff --git a/src/dev/build/tasks/os_packages/index.js b/src/dev/build/tasks/os_packages/index.js index 7eb6d90898d09..82626c47b6087 100644 --- a/src/dev/build/tasks/os_packages/index.js +++ b/src/dev/build/tasks/os_packages/index.js @@ -21,4 +21,5 @@ export { CreateRpmPackageTask, CreateDebPackageTask, CreateDockerPackageTask, + CreateDockerUbiPackageTask, } from './create_os_package_tasks'; diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service index 1d88ee535fefa..e66e0e7c8dfb5 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service @@ -10,7 +10,7 @@ Group=kibana # exist, it continues onward. EnvironmentFile=-/etc/default/kibana EnvironmentFile=-/etc/sysconfig/kibana -ExecStart=/usr/share/kibana/bin/kibana "-c /etc/kibana/kibana.yml" +ExecStart=/usr/share/kibana/bin/kibana Restart=on-failure RestartSec=3 StartLimitBurst=3 diff --git a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/default/kibana b/src/dev/build/tasks/os_packages/service_templates/sysv/etc/default/kibana index 7b411542986be..092dc6482fa1d 100644 --- a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/default/kibana +++ b/src/dev/build/tasks/os_packages/service_templates/sysv/etc/default/kibana @@ -11,3 +11,5 @@ nice="" KILL_ON_STOP_TIMEOUT=0 BABEL_CACHE_PATH="/var/lib/kibana/optimize/.babel_register_cache.json" + +KIBANA_PATH_CONF="/etc/kibana" diff --git a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana b/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana index fc1b797fe8ed2..ce29fd12b8e3c 100755 --- a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana +++ b/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana @@ -17,12 +17,12 @@ name=kibana program=/usr/share/kibana/bin/kibana -args=-c\\\ /etc/kibana/kibana.yml pidfile="/var/run/$name.pid" [ -r /etc/default/$name ] && . /etc/default/$name [ -r /etc/sysconfig/$name ] && . /etc/sysconfig/$name +export KIBANA_PATH_CONF export NODE_OPTIONS [ -z "$nice" ] && nice=0 @@ -52,7 +52,7 @@ start() { chroot --userspec "$user":"$group" "$chroot" sh -c " cd \"$chdir\" - exec \"$program\" $args + exec \"$program\" " >> /var/log/kibana/kibana.stdout 2>> /var/log/kibana/kibana.stderr & # Generate the pidfile from here. If we instead made the forked process diff --git a/src/dev/build/tasks/path_length_task.js b/src/dev/build/tasks/path_length_task.js index b108b8820575b..29ab9ce5a2499 100644 --- a/src/dev/build/tasks/path_length_task.js +++ b/src/dev/build/tasks/path_length_task.js @@ -30,10 +30,10 @@ export const PathLengthTask = { const buildRoot = build.resolvePath(); await scan$(buildRoot) .pipe( - map(path => relative(buildRoot, path)), - filter(relativePath => relativePath.length > 200), + map((path) => relative(buildRoot, path)), + filter((relativePath) => relativePath.length > 200), toArray(), - tap(tooLongPaths => { + tap((tooLongPaths) => { if (!tooLongPaths.length) { return; } diff --git a/src/dev/ci_setup/checkout_sibling_es.sh b/src/dev/ci_setup/checkout_sibling_es.sh index 2eec99a690134..915759d4214f9 100755 --- a/src/dev/ci_setup/checkout_sibling_es.sh +++ b/src/dev/ci_setup/checkout_sibling_es.sh @@ -43,7 +43,7 @@ function checkout_sibling { fi cloneAuthor="elastic" - cloneBranch="${PR_SOURCE_BRANCH:-${GIT_BRANCH#*/}}" # GIT_BRANCH starts with the repo, i.e., origin/master + cloneBranch="$GIT_BRANCH" if clone_target_is_valid ; then return 0 fi diff --git a/src/dev/ci_setup/extract_bootstrap_cache.sh b/src/dev/ci_setup/extract_bootstrap_cache.sh index 4a5977a68169e..fdd8bb6b90406 100755 --- a/src/dev/ci_setup/extract_bootstrap_cache.sh +++ b/src/dev/ci_setup/extract_bootstrap_cache.sh @@ -2,7 +2,7 @@ set -e -targetBranch="${PR_TARGET_BRANCH:-${GIT_BRANCH#*/}}" +targetBranch="${PR_TARGET_BRANCH:-$GIT_BRANCH}" bootstrapCache="$HOME/.kibana/bootstrap_cache/$targetBranch.tar" ### diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index d9d0528748dc0..343ff47199375 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -128,9 +128,17 @@ export GECKODRIVER_CDNURL="https://us-central1-elastic-kibana-184716.cloudfuncti export CHROMEDRIVER_CDNURL="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" export CYPRESS_DOWNLOAD_MIRROR="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/cypress" - export CHECKS_REPORTER_ACTIVE=false +# This is mainly for release-manager builds, which run in an environment that doesn't have Chrome installed +if [[ "$(which google-chrome-stable)" || "$(which google-chrome)" ]]; then + echo "Chrome detected, setting DETECT_CHROMEDRIVER_VERSION=true" + export DETECT_CHROMEDRIVER_VERSION=true + export CHROMEDRIVER_FORCE_DOWNLOAD=true +else + echo "Chrome not detected, installing default chromedriver binary for the package version" +fi + ### only run on pr jobs for elastic/kibana, checks-reporter doesn't work for other repos if [[ "$ghprbPullId" && "$ghprbGhRepository" == 'elastic/kibana' ]] ; then export CHECKS_REPORTER_ACTIVE=true diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js index c62a28a2956e1..8c982b792ed3b 100644 --- a/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js +++ b/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js @@ -35,13 +35,13 @@ describe(`Transform fn`, () => { it(`should remove the jenkins workspace path`, () => { const obj = { staticSiteUrl: - '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/x-pack/legacy/plugins/reporting/server/browsers/extract/unzip.js', + '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/x-pack/plugins/reporting/server/browsers/extract/unzip.js', COVERAGE_INGESTION_KIBANA_ROOT: '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana', }; expect(coveredFilePath(obj)).to.have.property( 'coveredFilePath', - 'x-pack/legacy/plugins/reporting/server/browsers/extract/unzip.js' + 'x-pack/plugins/reporting/server/browsers/extract/unzip.js' ); }); }); diff --git a/src/dev/code_coverage/ingest_coverage/either.js b/src/dev/code_coverage/ingest_coverage/either.js index bdab6e5882d26..a52fd2f7212d6 100644 --- a/src/dev/code_coverage/ingest_coverage/either.js +++ b/src/dev/code_coverage/ingest_coverage/either.js @@ -20,9 +20,9 @@ /* eslint new-cap: 0 */ /* eslint no-unused-vars: 0 */ -export const Right = x => ({ - chain: f => f(x), - map: f => Right(f(x)), +export const Right = (x) => ({ + chain: (f) => f(x), + map: (f) => Right(f(x)), fold: (f, g) => g(x), inspect: () => `Right(${x})`, }); @@ -35,9 +35,9 @@ export function right(x) { return Right.of(x); } -export const Left = x => ({ - chain: f => Left(x), - map: f => Left(x), +export const Left = (x) => ({ + chain: (f) => Left(x), + map: (f) => Left(x), fold: (f, g) => f(x), inspect: () => `Left(${x})`, }); @@ -50,10 +50,10 @@ export function left(x) { return Left.of(x); } -export const fromNullable = x => +export const fromNullable = (x) => x !== null && x !== undefined && x !== false && x !== 'undefined' ? Right(x) : Left(null); -export const tryCatch = f => { +export const tryCatch = (f) => { try { return Right(f()); } catch (e) { diff --git a/src/dev/code_coverage/ingest_coverage/ingest.js b/src/dev/code_coverage/ingest_coverage/ingest.js index 769fe250a8e18..9167bea17ae05 100644 --- a/src/dev/code_coverage/ingest_coverage/ingest.js +++ b/src/dev/code_coverage/ingest_coverage/ingest.js @@ -28,9 +28,9 @@ const node = process.env.ES_HOST || 'http://localhost:9200'; const redacted = redact(node); const client = new Client({ node }); -const indexName = body => (body.isTotal ? TOTALS_INDEX : COVERAGE_INDEX); +const indexName = (body) => (body.isTotal ? TOTALS_INDEX : COVERAGE_INDEX); -export const ingest = log => async body => { +export const ingest = (log) => async (body) => { const index = indexName(body); if (process.env.NODE_ENV === 'integration_test') { @@ -80,10 +80,7 @@ ${red('Perhaps the coverage data was not merged properly?\n')} } function partial(x) { - return x - .split('\n') - .splice(0, 2) - .join('\n'); + return x.split('\n').splice(0, 2).join('\n'); } function redact(x) { const url = new URL(x); diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js b/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js deleted file mode 100644 index 2ffb005993619..0000000000000 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { spawn } from 'child_process'; -import { resolve } from 'path'; -import { green, always } from '../utils'; - -const ROOT_DIR = resolve(__dirname, '../../../../..'); -const MOCKS_DIR = resolve(__dirname, './mocks'); -const staticSiteUrlRegexes = { - staticHostIncluded: /https:\/\/kibana-coverage\.elastic\.dev/, - timeStampIncluded: /\d{4}-\d{2}-\d{2}T\d{2}.*\d{2}.*\d{2}Z/, - folderStructureIncluded: /(?:.*|.*-combined)\//, -}; -const env = { - BUILD_ID: 407, - CI_RUN_URL: 'https://kibana-ci.elastic.co/job/elastic+kibana+code-coverage/407/', - STATIC_SITE_URL_BASE: 'https://kibana-coverage.elastic.dev', - TIME_STAMP: '2020-03-02T21:11:47Z', - ES_HOST: 'https://super:changeme@some.fake.host:9243', - NODE_ENV: 'integration_test', - COVERAGE_INGESTION_KIBANA_ROOT: '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana', -}; -const includesSiteUrlPredicate = x => x.includes('staticSiteUrl'); -const siteUrlLines = specificLinesOnly(includesSiteUrlPredicate); -const splitByNewLine = x => x.split('\n'); -const siteUrlsSplitByNewLine = siteUrlLines(splitByNewLine); -const siteUrlsSplitByNewLineWithoutBlanks = siteUrlsSplitByNewLine(notBlankLines); -const verboseArgs = [ - 'scripts/ingest_coverage.js', - '--verbose', - '--vcsInfoPath', - 'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/VCS_INFO.txt', - '--path', -]; - -describe('Ingesting coverage', () => { - const bothIndexesPath = 'jest-combined/coverage-summary-manual-mix.json'; - - describe(`to the coverage index`, () => { - const mutableCoverageIndexChunks = []; - - beforeAll(done => { - const ingestAndMutateAsync = ingestAndMutate(done); - const ingestAndMutateAsyncWithPath = ingestAndMutateAsync(bothIndexesPath); - const verboseIngestAndMutateAsyncWithPath = ingestAndMutateAsyncWithPath(verboseArgs); - verboseIngestAndMutateAsyncWithPath(mutableCoverageIndexChunks); - }); - - it( - 'should result in every posted item having a site url that meets all regex assertions', - always( - siteUrlsSplitByNewLineWithoutBlanks(mutableCoverageIndexChunks).forEach( - expectAllRegexesToPass({ - ...staticSiteUrlRegexes, - endsInDotJsDotHtml: /.js.html$/, - }) - ) - ) - ); - }); -}); - -function ingestAndMutate(done) { - return summaryPathSuffix => args => xs => { - const coverageSummaryPath = resolve(MOCKS_DIR, summaryPathSuffix); - const opts = [...args, coverageSummaryPath]; - const ingest = spawn(process.execPath, opts, { cwd: ROOT_DIR, env }); - - ingest.stdout.on('data', x => xs.push(x + '')); - ingest.on('close', done); - }; -} - -function specificLinesOnly(predicate) { - return splitByNewLine => notBlankLines => xs => - xs.filter(predicate).map(x => splitByNewLine(x).reduce(notBlankLines)); -} - -function notBlankLines(acc, item) { - if (item !== '') return item; - return acc; -} - -function expectAllRegexesToPass(staticSiteUrlRegexes) { - return urlLine => - Object.entries(staticSiteUrlRegexes).forEach(regexTuple => { - if (!regexTuple[1].test(urlLine)) - throw new Error( - `\n### ${green('FAILED')}\nAsserting: [\n\t${green( - regexTuple[0] - )}\n]\nAgainst: [\n\t${urlLine}\n]` - ); - }); -} diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/mocks/jest-combined/coverage-summary-manual-mix.json b/src/dev/code_coverage/ingest_coverage/integration_tests/mocks/jest-combined/coverage-summary-manual-mix.json index baf2b6d500679..25cd2fdfb259d 100644 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/mocks/jest-combined/coverage-summary-manual-mix.json +++ b/src/dev/code_coverage/ingest_coverage/integration_tests/mocks/jest-combined/coverage-summary-manual-mix.json @@ -1,5 +1,5 @@ { - "/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/x-pack/legacy/plugins/reporting/server/browsers/extract/unzip.js": { + "/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/x-pack/plugins/reporting/server/browsers/extract/unzip.js": { "lines": { "total": 4, "covered": 4, diff --git a/src/dev/code_coverage/ingest_coverage/json_stream.js b/src/dev/code_coverage/ingest_coverage/json_stream.js index fbc704b6372f2..edc329309df92 100644 --- a/src/dev/code_coverage/ingest_coverage/json_stream.js +++ b/src/dev/code_coverage/ingest_coverage/json_stream.js @@ -20,4 +20,4 @@ import oboe from 'oboe'; import { createReadStream } from 'fs'; -export default jsonSummaryPath => oboe(createReadStream(jsonSummaryPath)); +export default (jsonSummaryPath) => oboe(createReadStream(jsonSummaryPath)); diff --git a/src/dev/code_coverage/ingest_coverage/process.js b/src/dev/code_coverage/ingest_coverage/process.js index ecd09e0b2fd1a..6b9c8f09febfe 100644 --- a/src/dev/code_coverage/ingest_coverage/process.js +++ b/src/dev/code_coverage/ingest_coverage/process.js @@ -46,11 +46,11 @@ const addPrePopulatedTimeStamp = addTimeStamp(process.env.TIME_STAMP); const preamble = pipe(statsAndstaticSiteUrl, rootDirAndOrigPath, buildId, addPrePopulatedTimeStamp); const addTestRunnerAndStaticSiteUrl = pipe(testRunner, staticSite(staticSiteUrlBase)); -const transform = jsonSummaryPath => log => vcsInfo => { +const transform = (jsonSummaryPath) => (log) => (vcsInfo) => { const objStream = jsonStream(jsonSummaryPath).on('done', noop); const itemizeVcsInfo = itemizeVcs(vcsInfo); - const jsonSummary$ = _ => objStream.on('node', '!.*', _); + const jsonSummary$ = (_) => objStream.on('node', '!.*', _); fromEventPattern(jsonSummary$) .pipe( @@ -60,7 +60,7 @@ const transform = jsonSummaryPath => log => vcsInfo => { map(ciRunUrl), map(addJsonSummaryPath(jsonSummaryPath)), map(addTestRunnerAndStaticSiteUrl), - concatMap(x => of(x).pipe(delay(ms))) + concatMap((x) => of(x).pipe(delay(ms))) ) .subscribe(ingest(log)); }; @@ -73,8 +73,8 @@ function rootDirAndOrigPath(obj) { }; } -const mutateVcsInfo = vcsInfo => x => vcsInfo.push(x.trimStart().trimEnd()); -const vcsInfoLines$ = vcsInfoFilePath => { +const mutateVcsInfo = (vcsInfo) => (x) => vcsInfo.push(x.trimStart().trimEnd()); +const vcsInfoLines$ = (vcsInfoFilePath) => { const rl = readline.createInterface({ input: createReadStream(vcsInfoFilePath) }); return fromEvent(rl, 'line').pipe(takeUntil(fromEvent(rl, 'close'))); }; @@ -88,7 +88,7 @@ export const prok = ({ jsonSummaryPath, vcsInfoFilePath }, log) => { const vcsInfo = []; vcsInfoLines$(vcsInfoFilePath).subscribe( mutateVcsInfo(vcsInfo), - err => log.error(err), + (err) => log.error(err), always(xformWithPath(vcsInfo)) ); }; diff --git a/src/dev/code_coverage/ingest_coverage/transforms.js b/src/dev/code_coverage/ingest_coverage/transforms.js index d5a4d8933e36b..cecaf1e192b8c 100644 --- a/src/dev/code_coverage/ingest_coverage/transforms.js +++ b/src/dev/code_coverage/ingest_coverage/transforms.js @@ -20,7 +20,7 @@ import { left, right, fromNullable } from './either'; import { always, id, noop } from './utils'; -const maybeTotal = x => (x === 'total' ? left(x) : right(x)); +const maybeTotal = (x) => (x === 'total' ? left(x) : right(x)); const trimLeftFrom = (text, x) => x.substr(x.indexOf(text)); @@ -33,43 +33,43 @@ export const statsAndstaticSiteUrl = (...xs) => { }; }; -export const addJsonSummaryPath = jsonSummaryPath => obj => ({ jsonSummaryPath, ...obj }); +export const addJsonSummaryPath = (jsonSummaryPath) => (obj) => ({ jsonSummaryPath, ...obj }); -export const truncate = text => obj => { +export const truncate = (text) => (obj) => { const { staticSiteUrl } = obj; if (staticSiteUrl.includes(text)) obj.staticSiteUrl = trimLeftFrom(text, staticSiteUrl); return obj; }; -export const addTimeStamp = ts => obj => ({ +export const addTimeStamp = (ts) => (obj) => ({ ...obj, '@timestamp': ts, }); -const setTotal = x => obj => (obj.isTotal = x); +const setTotal = (x) => (obj) => (obj.isTotal = x); const mutateTrue = setTotal(true); const mutateFalse = setTotal(false); -const root = urlBase => ts => testRunnerType => +const root = (urlBase) => (ts) => (testRunnerType) => `${urlBase}/${ts}/${testRunnerType.toLowerCase()}-combined`; -const prokForTotalsIndex = mutateTrue => urlRoot => obj => +const prokForTotalsIndex = (mutateTrue) => (urlRoot) => (obj) => right(obj) .map(mutateTrue) .map(always(`${urlRoot}/index.html`)) .fold(noop, id); -const prokForCoverageIndex = root => mutateFalse => urlRoot => obj => siteUrl => +const prokForCoverageIndex = (root) => (mutateFalse) => (urlRoot) => (obj) => (siteUrl) => right(siteUrl) - .map(x => { + .map((x) => { mutateFalse(obj); return x; }) - .map(x => x.replace(root, '')) - .map(x => `${urlRoot}${x}.html`) + .map((x) => x.replace(root, '')) + .map((x) => `${urlRoot}${x}.html`) .fold(noop, id); -export const staticSite = urlBase => obj => { +export const staticSite = (urlBase) => (obj) => { const { staticSiteUrl, testRunnerType, COVERAGE_INGESTION_KIBANA_ROOT } = obj; const ts = obj['@timestamp']; const urlRoot = root(urlBase)(ts)(testRunnerType); @@ -82,27 +82,27 @@ export const staticSite = urlBase => obj => { return { ...obj, staticSiteUrl: prokForBoth() }; }; -export const coveredFilePath = obj => { +export const coveredFilePath = (obj) => { const { staticSiteUrl, COVERAGE_INGESTION_KIBANA_ROOT } = obj; const withoutCoveredFilePath = always(obj); const leadingSlashRe = /^\//; - const maybeDropLeadingSlash = x => (leadingSlashRe.test(x) ? right(x) : left(x)); - const dropLeadingSlash = x => x.replace(leadingSlashRe, ''); - const dropRoot = root => x => + const maybeDropLeadingSlash = (x) => (leadingSlashRe.test(x) ? right(x) : left(x)); + const dropLeadingSlash = (x) => x.replace(leadingSlashRe, ''); + const dropRoot = (root) => (x) => maybeDropLeadingSlash(x.replace(root, '')).fold(id, dropLeadingSlash); return maybeTotal(staticSiteUrl) .map(dropRoot(COVERAGE_INGESTION_KIBANA_ROOT)) - .fold(withoutCoveredFilePath, coveredFilePath => ({ ...obj, coveredFilePath })); + .fold(withoutCoveredFilePath, (coveredFilePath) => ({ ...obj, coveredFilePath })); }; -export const ciRunUrl = obj => - fromNullable(process.env.CI_RUN_URL).fold(always(obj), ciRunUrl => ({ ...obj, ciRunUrl })); +export const ciRunUrl = (obj) => + fromNullable(process.env.CI_RUN_URL).fold(always(obj), (ciRunUrl) => ({ ...obj, ciRunUrl })); const size = 50; -const truncateCommitMsg = x => (x.length > size ? `${x.slice(0, 50)}...` : x); +const truncateCommitMsg = (x) => (x.length > size ? `${x.slice(0, 50)}...` : x); -export const itemizeVcs = vcsInfo => obj => { +export const itemizeVcs = (vcsInfo) => (obj) => { const [branch, sha, author, commitMsg] = vcsInfo; return { ...obj, @@ -115,12 +115,12 @@ export const itemizeVcs = vcsInfo => obj => { }, }; }; -export const testRunner = obj => { +export const testRunner = (obj) => { const { jsonSummaryPath } = obj; let testRunnerType = 'other'; - const upperTestRunnerType = x => { + const upperTestRunnerType = (x) => { if (jsonSummaryPath.includes(x)) { testRunnerType = x.toUpperCase(); return; @@ -135,7 +135,7 @@ export const testRunner = obj => { }; }; -export const buildId = obj => { +export const buildId = (obj) => { const { env } = process; if (env.BUILD_ID) obj.BUILD_ID = env.BUILD_ID; diff --git a/src/dev/code_coverage/ingest_coverage/utils.js b/src/dev/code_coverage/ingest_coverage/utils.js index df064e73842e7..7d817bdf7a6f3 100644 --- a/src/dev/code_coverage/ingest_coverage/utils.js +++ b/src/dev/code_coverage/ingest_coverage/utils.js @@ -21,7 +21,7 @@ import chalk from 'chalk'; export const pipe = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args))); export const noop = () => {}; -export const green = x => chalk.greenBright.bold(x); -export const id = x => x; -export const always = x => () => x; -export const pretty = x => JSON.stringify(x, null, 2); +export const green = (x) => chalk.greenBright.bold(x); +export const id = (x) => x; +export const always = (x) => () => x; +export const pretty = (x) => JSON.stringify(x, null, 2); diff --git a/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh b/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh index bc184301a6831..098737eb2f800 100644 --- a/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh +++ b/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh @@ -7,7 +7,13 @@ COMBINED_EXRACT_DIR=/${EXTRACT_START_DIR}/${EXTRACT_END_DIR} PWD=$(pwd) du -sh $COMBINED_EXRACT_DIR -echo "### Replacing path in json files" +echo "### Jest: replacing path in json files" +for i in coverage-final xpack-coverage-final; do + sed -i "s|/dev/shm/workspace/kibana|${PWD}|g" $COMBINED_EXRACT_DIR/jest/${i}.json & +done +wait + +echo "### Functional: replacing path in json files" for i in {1..9}; do sed -i "s|/dev/shm/workspace/kibana|${PWD}|g" $COMBINED_EXRACT_DIR/functional/${i}*.json & done diff --git a/src/dev/eslint/lint_files.ts b/src/dev/eslint/lint_files.ts index 80c493233f39a..ba16163fc9bd3 100644 --- a/src/dev/eslint/lint_files.ts +++ b/src/dev/eslint/lint_files.ts @@ -38,7 +38,7 @@ export function lintFiles(log: ToolingLog, files: File[], { fix }: { fix?: boole fix, }); - const paths = files.map(file => file.getRelativePath()); + const paths = files.map((file) => file.getRelativePath()); const report = cli.executeOnFiles(paths); if (fix) { diff --git a/src/dev/eslint/pick_files_to_lint.ts b/src/dev/eslint/pick_files_to_lint.ts index b96781fc3a611..dc6713c5efd00 100644 --- a/src/dev/eslint/pick_files_to_lint.ts +++ b/src/dev/eslint/pick_files_to_lint.ts @@ -31,7 +31,7 @@ import { File } from '../file'; export function pickFilesToLint(log: ToolingLog, files: File[]) { const cli = new CLIEngine({}); - return files.filter(file => { + return files.filter((file) => { if (!file.isJs() && !file.isTypescript()) { return; } diff --git a/src/dev/globs.js b/src/dev/globs.js index 3d1637ff97f18..1289be7599ca9 100644 --- a/src/dev/globs.js +++ b/src/dev/globs.js @@ -20,7 +20,7 @@ import minimatch from 'minimatch'; export function matchesAnyGlob(path, globs) { - return globs.some(pattern => + return globs.some((pattern) => minimatch(path, pattern, { dot: true, }) diff --git a/src/dev/i18n/config.ts b/src/dev/i18n/config.ts index cd59408947d9b..c11ced51bce65 100644 --- a/src/dev/i18n/config.ts +++ b/src/dev/i18n/config.ts @@ -51,7 +51,7 @@ export async function assignConfigFromPath( for (const [namespace, namespacePaths] of Object.entries(additionalConfig.paths)) { const paths = Array.isArray(namespacePaths) ? namespacePaths : [namespacePaths]; - config.paths[namespace] = paths.map(path => normalizePath(resolve(configPath, '..', path))); + config.paths[namespace] = paths.map((path) => normalizePath(resolve(configPath, '..', path))); } for (const exclude of additionalConfig.exclude) { @@ -80,15 +80,17 @@ export function filterConfigPaths(inputPaths: string[], config: I18nConfig) { // If input path is the sub path of or equal to any available path, include it. if ( - availablePaths.some(path => normalizedPath.startsWith(`${path}/`) || path === normalizedPath) + availablePaths.some( + (path) => normalizedPath.startsWith(`${path}/`) || path === normalizedPath + ) ) { pathsForExtraction.add(normalizedPath); } else { // Otherwise go through all available paths and see if any of them is the sub // path of the input path (empty normalized path corresponds to root or above). availablePaths - .filter(path => !normalizedPath || path.startsWith(`${normalizedPath}/`)) - .forEach(ePath => pathsForExtraction.add(ePath)); + .filter((path) => !normalizedPath || path.startsWith(`${normalizedPath}/`)) + .forEach((ePath) => pathsForExtraction.add(ePath)); } } diff --git a/src/dev/i18n/extract_default_translations.js b/src/dev/i18n/extract_default_translations.js index 9daf431ad5401..e70c666422f7b 100644 --- a/src/dev/i18n/extract_default_translations.js +++ b/src/dev/i18n/extract_default_translations.js @@ -38,8 +38,8 @@ function addMessageToMap(targetMap, key, value, reporter) { } function filterEntries(entries, exclude) { - return entries.filter(entry => - exclude.every(excludedPath => !normalizePath(entry).startsWith(excludedPath)) + return entries.filter((entry) => + exclude.every((excludedPath) => !normalizePath(entry).startsWith(excludedPath)) ); } @@ -47,7 +47,7 @@ export function validateMessageNamespace(id, filePath, allowedPaths, reporter) { const normalizedPath = normalizePath(filePath); const [expectedNamespace] = Object.entries(allowedPaths).find(([, pluginPaths]) => - pluginPaths.some(pluginPath => normalizedPath.startsWith(`${pluginPath}/`)) + pluginPaths.some((pluginPath) => normalizedPath.startsWith(`${pluginPath}/`)) ); if (!id.startsWith(`${expectedNamespace}.`)) { @@ -107,7 +107,7 @@ export async function extractMessagesFromPathToMap(inputPath, targetMap, config, return Promise.all( categorizedEntries.map(async ([entries, extractFunction]) => { const files = await Promise.all( - filterEntries(entries, config.exclude).map(async entry => { + filterEntries(entries, config.exclude).map(async (entry) => { return { name: entry, content: await readFileAsync(entry), diff --git a/src/dev/i18n/extractors/code.test.js b/src/dev/i18n/extractors/code.test.js index 34861aee38352..726afedd9f929 100644 --- a/src/dev/i18n/extractors/code.test.js +++ b/src/dev/i18n/extractors/code.test.js @@ -94,11 +94,11 @@ describe('isIntlFormatMessageFunction', () => { test('detects intl.formatMessage call expression', () => { const callExpressionNodes = [ ...traverseNodes(parse(intlFormatMessageSource).program.body), - ].filter(node => isCallExpression(node)); + ].filter((node) => isCallExpression(node)); expect(callExpressionNodes).toHaveLength(4); expect( - callExpressionNodes.every(callExpressionNode => + callExpressionNodes.every((callExpressionNode) => isIntlFormatMessageFunction(callExpressionNode) ) ).toBe(true); @@ -108,7 +108,7 @@ describe('isIntlFormatMessageFunction', () => { describe('isFormattedMessageElement', () => { test('detects FormattedMessage jsx element', () => { const AST = parse(formattedMessageSource, { plugins: ['jsx'] }); - const jsxOpeningElementNode = [...traverseNodes(AST.program.body)].find(node => + const jsxOpeningElementNode = [...traverseNodes(AST.program.body)].find((node) => isJSXOpeningElement(node) ); diff --git a/src/dev/i18n/extractors/html.js b/src/dev/i18n/extractors/html.js index 5b45738209264..6c7b982b872a5 100644 --- a/src/dev/i18n/extractors/html.js +++ b/src/dev/i18n/extractors/html.js @@ -68,7 +68,7 @@ function parseExpression(expression) { */ function parseFilterObjectExpression(expression, messageId) { const ast = parseExpression(expression); - const objectExpressionNode = [...traverseNodes(ast.program.body)].find(node => + const objectExpressionNode = [...traverseNodes(ast.program.body)].find((node) => isObjectExpression(node) ); @@ -80,7 +80,9 @@ function parseFilterObjectExpression(expression, messageId) { DEFAULT_MESSAGE_KEY, DESCRIPTION_KEY, VALUES_KEY, - ].map(key => objectExpressionNode.properties.find(property => isPropertyWithKey(property, key))); + ].map((key) => + objectExpressionNode.properties.find((property) => isPropertyWithKey(property, key)) + ); const message = messageProperty ? formatJSString(extractMessageValueFromNode(messageProperty.value, messageId)) @@ -99,7 +101,7 @@ function parseFilterObjectExpression(expression, messageId) { function parseIdExpression(expression) { const ast = parseExpression(expression); - const stringNode = [...traverseNodes(ast.program.body)].find(node => isStringLiteral(node)); + const stringNode = [...traverseNodes(ast.program.body)].find((node) => isStringLiteral(node)); if (!stringNode) { throw createFailError(`Message id should be a string literal, but got: \n${expression}`); @@ -131,15 +133,13 @@ function trimOneTimeBindingOperator(string) { } function* extractExpressions(htmlContent) { - const elements = cheerio - .load(htmlContent)('*') - .toArray(); + const elements = cheerio.load(htmlContent)('*').toArray(); for (const element of elements) { for (const node of element.children) { if (node.type === 'text') { yield* (node.data.match(ANGULAR_EXPRESSION_REGEX) || []) - .filter(expression => expression.includes(I18N_FILTER_MARKER)) + .filter((expression) => expression.includes(I18N_FILTER_MARKER)) .map(trimCurlyBraces); } } @@ -232,7 +232,7 @@ function* getDirectiveMessages(htmlContent, reporter) { try { if (element.values) { const ast = parseExpression(element.values); - const valuesObjectNode = [...traverseNodes(ast.program.body)].find(node => + const valuesObjectNode = [...traverseNodes(ast.program.body)].find((node) => isObjectExpression(node) ); const valuesKeys = extractValuesKeysFromNode(valuesObjectNode); diff --git a/src/dev/i18n/extractors/i18n_call.js b/src/dev/i18n/extractors/i18n_call.js index 118cf63ce8a30..79aeee0ee2c5f 100644 --- a/src/dev/i18n/extractors/i18n_call.js +++ b/src/dev/i18n/extractors/i18n_call.js @@ -52,7 +52,7 @@ export function extractI18nCallMessages(node) { DEFAULT_MESSAGE_KEY, DESCRIPTION_KEY, VALUES_KEY, - ].map(key => optionsSubTree.properties.find(property => isPropertyWithKey(property, key))); + ].map((key) => optionsSubTree.properties.find((property) => isPropertyWithKey(property, key))); const message = messageProperty ? formatJSString(extractMessageValueFromNode(messageProperty.value, messageId)) diff --git a/src/dev/i18n/extractors/i18n_call.test.js b/src/dev/i18n/extractors/i18n_call.test.js index fd2873b5f8d43..7292ec83ec3b2 100644 --- a/src/dev/i18n/extractors/i18n_call.test.js +++ b/src/dev/i18n/extractors/i18n_call.test.js @@ -41,19 +41,19 @@ describe('dev/i18n/extractors/i18n_call', () => { test('extracts "i18n" and "i18n.translate" functions call message', () => { let callExpressionNode = [ ...traverseNodes(parse(i18nCallMessageSource).program.body), - ].find(node => isCallExpression(node)); + ].find((node) => isCallExpression(node)); expect(extractI18nCallMessages(callExpressionNode)).toMatchSnapshot(); callExpressionNode = [ ...traverseNodes(parse(translateCallMessageSource).program.body), - ].find(node => isCallExpression(node)); + ].find((node) => isCallExpression(node)); expect(extractI18nCallMessages(callExpressionNode)).toMatchSnapshot(); callExpressionNode = [ ...traverseNodes(parse(i18nCallMessageWithTemplateLiteralSource).program.body), - ].find(node => isCallExpression(node)); + ].find((node) => isCallExpression(node)); expect(extractI18nCallMessages(callExpressionNode)).toMatchSnapshot(); }); @@ -62,7 +62,7 @@ describe('dev/i18n/extractors/i18n_call', () => { const source = ` i18n(messageIdIdentifier, { defaultMessage: 'Default message', description: 'Message description' }); `; - const callExpressionNode = [...traverseNodes(parse(source).program.body)].find(node => + const callExpressionNode = [...traverseNodes(parse(source).program.body)].find((node) => isCallExpression(node) ); @@ -71,7 +71,7 @@ i18n(messageIdIdentifier, { defaultMessage: 'Default message', description: 'Mes test('throws if properties object is not provided', () => { const source = `i18n('message-id');`; - const callExpressionNode = [...traverseNodes(parse(source).program.body)].find(node => + const callExpressionNode = [...traverseNodes(parse(source).program.body)].find((node) => isCallExpression(node) ); @@ -83,7 +83,7 @@ i18n(messageIdIdentifier, { defaultMessage: 'Default message', description: 'Mes const message = 'Default message'; i18n('message-id', { defaultMessage: message }); `; - const callExpressionNode = [...traverseNodes(parse(source).program.body)].find(node => + const callExpressionNode = [...traverseNodes(parse(source).program.body)].find((node) => isCallExpression(node) ); @@ -92,7 +92,7 @@ i18n('message-id', { defaultMessage: message }); test('throws on empty defaultMessage', () => { const source = `i18n('message-id', { defaultMessage: '' });`; - const callExpressionNode = [...traverseNodes(parse(source).program.body)].find(node => + const callExpressionNode = [...traverseNodes(parse(source).program.body)].find((node) => isCallExpression(node) ); diff --git a/src/dev/i18n/extractors/pug.js b/src/dev/i18n/extractors/pug.js index a19d2183af2a1..20fc72a404843 100644 --- a/src/dev/i18n/extractors/pug.js +++ b/src/dev/i18n/extractors/pug.js @@ -56,7 +56,9 @@ export function* extractPugMessages(buffer, reporter) { for (const expression of expressions) { try { const ast = parsePugExpression(expression); - const node = [...traverseNodes(ast.program.body)].find(node => isI18nTranslateFunction(node)); + const node = [...traverseNodes(ast.program.body)].find((node) => + isI18nTranslateFunction(node) + ); if (node) { yield extractI18nCallMessages(node); diff --git a/src/dev/i18n/extractors/react.js b/src/dev/i18n/extractors/react.js index b5a55a825312b..6d3719faaeb61 100644 --- a/src/dev/i18n/extractors/react.js +++ b/src/dev/i18n/extractors/react.js @@ -50,7 +50,7 @@ export function extractIntlMessages(node) { 'id', DEFAULT_MESSAGE_KEY, DESCRIPTION_KEY, - ].map(key => options.properties.find(property => isPropertyWithKey(property, key))); + ].map((key) => options.properties.find((property) => isPropertyWithKey(property, key))); const messageId = messageIdProperty ? formatJSString(extractMessageIdFromNode(messageIdProperty.value)) @@ -92,7 +92,9 @@ export function extractFormattedMessages(node) { DEFAULT_MESSAGE_KEY, DESCRIPTION_KEY, VALUES_KEY, - ].map(key => node.attributes.find(attribute => isJSXIdentifier(attribute.name, { name: key }))); + ].map((key) => + node.attributes.find((attribute) => isJSXIdentifier(attribute.name, { name: key })) + ); const messageId = messageIdAttribute ? formatHTMLString(extractMessageIdFromNode(messageIdAttribute.value)) diff --git a/src/dev/i18n/extractors/react.test.js b/src/dev/i18n/extractors/react.test.js index d363672626ee9..f6fc85d28f139 100644 --- a/src/dev/i18n/extractors/react.test.js +++ b/src/dev/i18n/extractors/react.test.js @@ -83,7 +83,7 @@ describe('dev/i18n/extractors/react', () => { describe('extractIntlMessages', () => { test('extracts messages from "intl.formatMessage" function call', () => { const ast = parse(intlFormatMessageCallSource, { plugins: ['jsx'] }); - const expressionNode = [...traverseNodes(ast.program.body)].find(node => + const expressionNode = [...traverseNodes(ast.program.body)].find((node) => isCallExpression(node) ); @@ -93,7 +93,7 @@ describe('dev/i18n/extractors/react', () => { test('throws if message id is not a string literal', () => { const source = intlFormatMessageCallErrorSources[0]; const ast = parse(source, { plugins: ['jsx'] }); - const callExpressionNode = [...traverseNodes(ast.program.body)].find(node => + const callExpressionNode = [...traverseNodes(ast.program.body)].find((node) => isCallExpression(node) ); @@ -103,7 +103,7 @@ describe('dev/i18n/extractors/react', () => { test('throws if defaultMessage value is not a string literal', () => { const source = intlFormatMessageCallErrorSources[1]; const ast = parse(source, { plugins: ['jsx'] }); - const callExpressionNode = [...traverseNodes(ast.program.body)].find(node => + const callExpressionNode = [...traverseNodes(ast.program.body)].find((node) => isCallExpression(node) ); @@ -113,7 +113,7 @@ describe('dev/i18n/extractors/react', () => { test('throws if description value is not a string literal', () => { const source = intlFormatMessageCallErrorSources[2]; const ast = parse(source, { plugins: ['jsx'] }); - const callExpressionNode = [...traverseNodes(ast.program.body)].find(node => + const callExpressionNode = [...traverseNodes(ast.program.body)].find((node) => isCallExpression(node) ); @@ -125,7 +125,7 @@ describe('dev/i18n/extractors/react', () => { test('extracts messages from "" element', () => { const ast = parse(formattedMessageElementSource, { plugins: ['jsx'] }); const jsxOpeningElementNode = [...traverseNodes(ast.program.body)].find( - node => + (node) => isJSXOpeningElement(node) && isJSXIdentifier(node.name, { name: 'FormattedMessage' }) ); diff --git a/src/dev/i18n/integrate_locale_files.ts b/src/dev/i18n/integrate_locale_files.ts index 2ac8768ed72ad..d8ccccca15559 100644 --- a/src/dev/i18n/integrate_locale_files.ts +++ b/src/dev/i18n/integrate_locale_files.ts @@ -116,7 +116,7 @@ function groupMessagesByNamespace( ) { const localizedMessagesByNamespace = new Map(); for (const [messageId, messageValue] of localizedMessagesMap) { - const namespace = knownNamespaces.find(key => messageId.startsWith(`${key}.`)); + const namespace = knownNamespaces.find((key) => messageId.startsWith(`${key}.`)); if (!namespace) { throw createFailError(`Unknown namespace in id ${messageId}.`); } diff --git a/src/dev/i18n/serializers/json5.ts b/src/dev/i18n/serializers/json5.ts index 5b09764ce4d9b..a1c89d5ff8cf2 100644 --- a/src/dev/i18n/serializers/json5.ts +++ b/src/dev/i18n/serializers/json5.ts @@ -26,9 +26,7 @@ const ESCAPE_SINGLE_QUOTE_REGEX = /\\([\s\S])|(')/g; export const serializeToJson5: Serializer = (messages, formats = i18n.formats) => { // .slice(0, -4): remove closing curly braces from json to append messages let jsonBuffer = Buffer.from( - JSON5.stringify({ formats, messages: {} }, { quote: `'`, space: 2 }) - .slice(0, -4) - .concat('\n') + JSON5.stringify({ formats, messages: {} }, { quote: `'`, space: 2 }).slice(0, -4).concat('\n') ); for (const [mapKey, mapValue] of messages) { diff --git a/src/dev/i18n/tasks/check_compatibility.ts b/src/dev/i18n/tasks/check_compatibility.ts index 3c5f8c23466a4..5900bf5aff252 100644 --- a/src/dev/i18n/tasks/check_compatibility.ts +++ b/src/dev/i18n/tasks/check_compatibility.ts @@ -29,7 +29,7 @@ export interface I18nFlags { export function checkCompatibility(config: I18nConfig, flags: I18nFlags, log: ToolingLog) { const { fix, ignoreIncompatible, ignoreUnused, ignoreMissing } = flags; - return config.translations.map(translationsPath => ({ + return config.translations.map((translationsPath) => ({ task: async ({ messages }: { messages: Map }) => { // If `fix` is set we should try apply all possible fixes and override translations file. await integrateLocaleFiles(messages, { diff --git a/src/dev/i18n/tasks/check_configs.ts b/src/dev/i18n/tasks/check_configs.ts index ffb9a499fbb43..89d55cae07ffd 100644 --- a/src/dev/i18n/tasks/check_configs.ts +++ b/src/dev/i18n/tasks/check_configs.ts @@ -28,7 +28,7 @@ export function checkConfigs(additionalConfigPaths: string | string[] = []) { const configPaths = [kibanaRC, xpackRC, ...arrayify(additionalConfigPaths)]; - return configPaths.map(configPath => ({ + return configPaths.map((configPath) => ({ task: async (context: { reporter: ErrorReporter }) => { try { await checkConfigNamespacePrefix(configPath); diff --git a/src/dev/i18n/tasks/extract_default_translations.ts b/src/dev/i18n/tasks/extract_default_translations.ts index 8ff2bc14abafd..b8f4ede69526d 100644 --- a/src/dev/i18n/tasks/extract_default_translations.ts +++ b/src/dev/i18n/tasks/extract_default_translations.ts @@ -30,7 +30,7 @@ export function extractDefaultMessages(config: I18nConfig, inputPaths: string[]) )} None of input paths is covered by the mappings in .i18nrc.json.` ); } - return filteredPaths.map(filteredPath => ({ + return filteredPaths.map((filteredPath) => ({ task: async (context: { messages: Map; reporter: ErrorReporter; diff --git a/src/dev/i18n/tasks/extract_untracked_translations.ts b/src/dev/i18n/tasks/extract_untracked_translations.ts index 7c640d566dea2..39f8a1cc59c6b 100644 --- a/src/dev/i18n/tasks/extract_untracked_translations.ts +++ b/src/dev/i18n/tasks/extract_untracked_translations.ts @@ -67,10 +67,10 @@ export async function extractUntrackedMessagesTask({ for (const [entries, extractFunction] of categorizedEntries) { const files = await Promise.all( filterEntries(entries, config.exclude) - .filter(entry => { + .filter((entry) => { const normalizedEntry = normalizePath(entry); return !availablePaths.some( - availablePath => + (availablePath) => normalizedEntry.startsWith(`${normalizePath(availablePath)}/`) || normalizePath(availablePath) === normalizedEntry ); @@ -93,7 +93,7 @@ export async function extractUntrackedMessagesTask({ } export function extractUntrackedMessages(inputPaths: string[]) { - return inputPaths.map(inputPath => ({ + return inputPaths.map((inputPath) => ({ title: `Checking untracked messages in ${inputPath}`, task: async (context: { reporter: ErrorReporter; config: I18nConfig }) => { const { reporter, config } = context; diff --git a/src/dev/i18n/tasks/merge_configs.ts b/src/dev/i18n/tasks/merge_configs.ts index ec005f0ce9367..6e7b89fe4a668 100644 --- a/src/dev/i18n/tasks/merge_configs.ts +++ b/src/dev/i18n/tasks/merge_configs.ts @@ -27,7 +27,7 @@ export function mergeConfigs(additionalConfigPaths: string | string[] = []) { const configPaths = [kibanaRC, xpackRC, ...arrayify(additionalConfigPaths)]; - return configPaths.map(configPath => ({ + return configPaths.map((configPath) => ({ task: async (context: { reporter: ErrorReporter; config?: I18nConfig }) => { try { context.config = await assignConfigFromPath(context.config, configPath); diff --git a/src/dev/i18n/utils.js b/src/dev/i18n/utils.js index 091056c0837c3..1d1c3118e0852 100644 --- a/src/dev/i18n/utils.js +++ b/src/dev/i18n/utils.js @@ -55,7 +55,7 @@ export function normalizePath(inputPath) { } export function difference(left = [], right = []) { - return left.filter(value => !right.includes(value)); + return left.filter((value) => !right.includes(value)); } export function isPropertyWithKey(property, identifierName) { @@ -103,7 +103,9 @@ export function* traverseNodes(nodes) { // if node is an object / array, traverse all of its object values if (node && typeof node === 'object') { - yield* traverseNodes(Object.values(node).filter(value => value && typeof value === 'object')); + yield* traverseNodes( + Object.values(node).filter((value) => value && typeof value === 'object') + ); } } } @@ -187,7 +189,7 @@ export function checkValuesProperty(prefixedValuesKeys, defaultMessage, messageI return; } - const valuesKeys = prefixedValuesKeys.map(key => + const valuesKeys = prefixedValuesKeys.map((key) => key.startsWith(HTML_KEY_PREFIX) ? key.slice(HTML_KEY_PREFIX.length) : key ); @@ -309,7 +311,7 @@ export function extractValuesKeysFromNode(node, messageId) { throw createFailError(`"values" value should be an inline object literal ("${messageId}").`); } - return node.properties.map(property => + return node.properties.map((property) => isStringLiteral(property.key) ? property.key.value : property.key.name ); } @@ -318,7 +320,7 @@ export class ErrorReporter { errors = []; withContext(context) { - return { report: error => this.report(error, context) }; + return { report: (error) => this.report(error, context) }; } report(error, context) { diff --git a/src/dev/i18n/utils.test.js b/src/dev/i18n/utils.test.js index 7019218d4e249..49c42d3b577ad 100644 --- a/src/dev/i18n/utils.test.js +++ b/src/dev/i18n/utils.test.js @@ -32,7 +32,7 @@ import { } from './utils'; const i18nTranslateSources = ['i18n', 'i18n.translate'].map( - callee => ` + (callee) => ` ${callee}('plugin_1.id_1', { values: { key: 'value', @@ -64,14 +64,14 @@ describe('i18n utils', () => { test('should detect i18n translate function call', () => { let source = i18nTranslateSources[0]; - let expressionStatementNode = [...traverseNodes(parse(source).program.body)].find(node => + let expressionStatementNode = [...traverseNodes(parse(source).program.body)].find((node) => isExpressionStatement(node) ); expect(isI18nTranslateFunction(expressionStatementNode.expression)).toBe(true); source = i18nTranslateSources[1]; - expressionStatementNode = [...traverseNodes(parse(source).program.body)].find(node => + expressionStatementNode = [...traverseNodes(parse(source).program.body)].find((node) => isExpressionStatement(node) ); @@ -81,7 +81,7 @@ describe('i18n utils', () => { test('should detect object property with defined key', () => { const objectExpresssionNode = [ ...traverseNodes(parse(objectPropertySource).program.body), - ].find(node => isObjectExpression(node)); + ].find((node) => isObjectExpression(node)); const [objectExpresssionProperty] = objectExpresssionNode.properties; expect(isPropertyWithKey(objectExpresssionProperty, 'id')).toBe(true); @@ -183,7 +183,7 @@ describe('i18n utils', () => { i18n('namespace.id', { defaultMessage: 'Very ' + 'long ' + 'concatenated ' + 'string', });`; - const objectProperty = [...traverseNodes(parse(source).program.body)].find(node => + const objectProperty = [...traverseNodes(parse(source).program.body)].find((node) => isObjectProperty(node) ); diff --git a/src/dev/jest/config.integration.js b/src/dev/jest/config.integration.js index e13d0289a7e9b..2fd6bd120d553 100644 --- a/src/dev/jest/config.integration.js +++ b/src/dev/jest/config.integration.js @@ -27,7 +27,7 @@ export default { '**/integration_tests/**/*.test.tsx', ], testPathIgnorePatterns: config.testPathIgnorePatterns.filter( - pattern => !pattern.includes('integration_tests') + (pattern) => !pattern.includes('integration_tests') ), reporters: [ 'default', diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 497a639ecab56..64db131f5219a 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -64,7 +64,8 @@ export default { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/dev/jest/mocks/file_mock.js', '\\.(css|less|scss)$': '/src/dev/jest/mocks/style_mock.js', - '\\.ace\\.worker.js$': '/src/dev/jest/mocks/ace_worker_module_mock.js', + '\\.ace\\.worker.js$': '/src/dev/jest/mocks/worker_module_mock.js', + '\\.editor\\.worker.js$': '/src/dev/jest/mocks/worker_module_mock.js', '^(!!)?file-loader!': '/src/dev/jest/mocks/file_mock.js', }, setupFiles: [ diff --git a/src/dev/jest/junit_reporter.js b/src/dev/jest/junit_reporter.js index 0f8003f4ed6a1..f33358c081a06 100644 --- a/src/dev/jest/junit_reporter.js +++ b/src/dev/jest/junit_reporter.js @@ -58,8 +58,8 @@ export default class JestJUnitReporter { { skipNullAttributes: true } ); - const msToIso = ms => (ms ? new Date(ms).toISOString().slice(0, -5) : undefined); - const msToSec = ms => (ms ? (ms / 1000).toFixed(3) : undefined); + const msToIso = (ms) => (ms ? new Date(ms).toISOString().slice(0, -5) : undefined); + const msToSec = (ms) => (ms ? (ms / 1000).toFixed(3) : undefined); root.att({ name: 'jest', @@ -71,7 +71,7 @@ export default class JestJUnitReporter { }); // top level test results are the files/suites - results.testResults.forEach(suite => { + results.testResults.forEach((suite) => { const suiteEl = root.ele('testsuite', { name: relative(rootDirectory, suite.testFilePath), timestamp: msToIso(suite.perfStats.start), @@ -85,14 +85,14 @@ export default class JestJUnitReporter { // nested in there are the tests in that file const relativePath = dirname(relative(rootDirectory, suite.testFilePath)); const classname = `${reportName}.${relativePath.replace(/\./g, '·')}`; - suite.testResults.forEach(test => { + suite.testResults.forEach((test) => { const testEl = suiteEl.ele('testcase', { classname, name: [...test.ancestorTitles, test.title].join(' '), time: msToSec(test.duration), }); - test.failureMessages.forEach(message => { + test.failureMessages.forEach((message) => { testEl.ele('failure').dat(escapeCdata(message)); }); diff --git a/src/dev/jest/mocks/ace_worker_module_mock.js b/src/dev/jest/mocks/worker_module_mock.js similarity index 100% rename from src/dev/jest/mocks/ace_worker_module_mock.js rename to src/dev/jest/mocks/worker_module_mock.js diff --git a/src/dev/jest/setup/mocks.js b/src/dev/jest/setup/mocks.js index 095c82bd50fbd..6e7160e858cd7 100644 --- a/src/dev/jest/setup/mocks.js +++ b/src/dev/jest/setup/mocks.js @@ -54,6 +54,6 @@ jest.mock('@elastic/eui/lib/services/react', () => { // This is for performance, but when used in certain Jest scernarios it can be nondeterministic. // Jest tests are never concerned about the state prior to batch completion, so we bypass batching entirely. return { - enqueueStateChange: fn => fn(), + enqueueStateChange: (fn) => fn(), }; }); diff --git a/src/dev/jest/setup/polyfills.js b/src/dev/jest/setup/polyfills.js index a3342883d084e..9e4f1db9ac289 100644 --- a/src/dev/jest/setup/polyfills.js +++ b/src/dev/jest/setup/polyfills.js @@ -20,7 +20,7 @@ // bluebird < v3.3.5 does not work with MutationObserver polyfill // when MutationObserver exists, bluebird avoids using node's builtin async schedulers const bluebird = require('bluebird'); -bluebird.Promise.setScheduler(function(fn) { +bluebird.Promise.setScheduler(function (fn) { global.setImmediate.call(global, fn); }); diff --git a/src/dev/license_checker/run_check_licenses_cli.ts b/src/dev/license_checker/run_check_licenses_cli.ts index b011e8ff6ecb2..0267a1a90d4fe 100644 --- a/src/dev/license_checker/run_check_licenses_cli.ts +++ b/src/dev/license_checker/run_check_licenses_cli.ts @@ -35,7 +35,7 @@ run( // Assert if the found licenses in the production // packages are valid assertLicensesValid({ - packages: packages.filter(pkg => !pkg.isDevOnly), + packages: packages.filter((pkg) => !pkg.isDevOnly), validLicenses: LICENSE_WHITELIST, }); log.success('All production dependency licenses are allowed'); @@ -44,7 +44,7 @@ run( // if the dev flag is found if (flags.dev) { assertLicensesValid({ - packages: packages.filter(pkg => pkg.isDevOnly), + packages: packages.filter((pkg) => pkg.isDevOnly), validLicenses: LICENSE_WHITELIST.concat(DEV_ONLY_LICENSE_WHITELIST), }); log.success('All development dependency licenses are allowed'); diff --git a/src/dev/license_checker/valid.ts b/src/dev/license_checker/valid.ts index 9142955185a1a..f24f8a9bfdba7 100644 --- a/src/dev/license_checker/valid.ts +++ b/src/dev/license_checker/valid.ts @@ -37,7 +37,7 @@ interface Options { */ export function assertLicensesValid({ packages, validLicenses }: Options) { const invalidMsgs = packages.reduce((acc, pkg) => { - const invalidLicenses = pkg.licenses.filter(license => !validLicenses.includes(license)); + const invalidLicenses = pkg.licenses.filter((license) => !validLicenses.includes(license)); if (pkg.licenses.length && !invalidLicenses.length) { return acc; @@ -57,7 +57,7 @@ export function assertLicensesValid({ packages, validLicenses }: Options) { `Non-conforming licenses:\n${invalidMsgs .join('\n') .split('\n') - .map(l => ` ${l}`) + .map((l) => ` ${l}`) .join('\n')}` ); } diff --git a/src/dev/notice/bundled_notices.js b/src/dev/notice/bundled_notices.js index 1bdf526460016..a4710a79b0610 100644 --- a/src/dev/notice/bundled_notices.js +++ b/src/dev/notice/bundled_notices.js @@ -25,11 +25,11 @@ import glob from 'glob'; export async function getBundledNotices(packageDirectory) { const pattern = resolve(packageDirectory, '*{LICENSE,NOTICE}*'); - const paths = await fcb(cb => glob(pattern, cb)); + const paths = await fcb((cb) => glob(pattern, cb)); return Promise.all( - paths.map(async path => ({ + paths.map(async (path) => ({ path, - text: await fcb(cb => readFile(path, 'utf8', cb)), + text: await fcb((cb) => readFile(path, 'utf8', cb)), })) ); } diff --git a/src/dev/notice/cli.js b/src/dev/notice/cli.js index e5377d7f177d2..34217ef48a33b 100644 --- a/src/dev/notice/cli.js +++ b/src/dev/notice/cli.js @@ -41,7 +41,7 @@ const log = new ToolingLog({ }); if (unknownFlags.length) { - log.error(`Unknown flags ${unknownFlags.map(f => `"${f}"`).join(',')}`); + log.error(`Unknown flags ${unknownFlags.map((f) => `"${f}"`).join(',')}`); process.exitCode = 1; opts.help = true; } @@ -90,7 +90,7 @@ if (opts.help) { 'NOTICE.txt is out of date, run `node scripts/notice` to update and commit the results.' ); process.exit(1); -})().catch(error => { +})().catch((error) => { log.error(error); process.exit(1); }); diff --git a/src/dev/notice/generate_notice_from_source.ts b/src/dev/notice/generate_notice_from_source.ts index 6dd47db929f45..fb74bed0f26f4 100644 --- a/src/dev/notice/generate_notice_from_source.ts +++ b/src/dev/notice/generate_notice_from_source.ts @@ -62,7 +62,7 @@ export async function generateNoticeFromSource({ productName, directory, log }: const noticeComments: string[] = []; await new Promise((resolve, reject) => { files - .on('data', file => { + .on('data', (file) => { log.verbose(`Checking for @notice comments in ${file.relative}`); const source = file.contents.toString('utf8'); @@ -86,7 +86,7 @@ export async function generateNoticeFromSource({ productName, directory, log }: noticeText += '\n---\n'; noticeText += comment .split(NEWLINE_RE) - .map(line => + .map((line) => line // trim whitespace .trim() diff --git a/src/dev/notice/generate_package_notice_text.js b/src/dev/notice/generate_package_notice_text.js index 77c080d258e12..673f34fc57bf1 100644 --- a/src/dev/notice/generate_package_notice_text.js +++ b/src/dev/notice/generate_package_notice_text.js @@ -19,7 +19,7 @@ import { getBundledNotices } from './bundled_notices'; -const concatNotices = notices => notices.map(notice => notice.text).join('\n'); +const concatNotices = (notices) => notices.map((notice) => notice.text).join('\n'); export async function generatePackageNoticeText(pkg) { const bundledNotices = concatNotices(await getBundledNotices(pkg.directory)); diff --git a/src/dev/npm/integration_tests/installed_packages.test.ts b/src/dev/npm/integration_tests/installed_packages.test.ts index 75cd0e5607698..58c954cbc12f7 100644 --- a/src/dev/npm/integration_tests/installed_packages.test.ts +++ b/src/dev/npm/integration_tests/installed_packages.test.ts @@ -27,11 +27,11 @@ import { REPO_ROOT } from '../../constants'; const FIXTURE1_ROOT = resolve(__dirname, '__fixtures__/fixture1'); describe('src/dev/npm/installed_packages', () => { - describe('getInstalledPackages()', function() { + describe('getInstalledPackages()', function () { let kibanaPackages: InstalledPackage[]; let fixture1Packages: InstalledPackage[]; - beforeAll(async function() { + beforeAll(async function () { [kibanaPackages, fixture1Packages] = await Promise.all([ getInstalledPackages({ directory: REPO_ROOT, @@ -76,16 +76,16 @@ describe('src/dev/npm/installed_packages', () => { }); it('returns a single entry for every package/version combo', () => { - const tags = kibanaPackages.map(pkg => `${pkg.name}@${pkg.version}`); + const tags = kibanaPackages.map((pkg) => `${pkg.name}@${pkg.version}`); expect(tags).toEqual(uniq(tags)); }); it('does not include root package in the list', () => { - if (kibanaPackages.find(pkg => pkg.name === 'kibana')) { + if (kibanaPackages.find((pkg) => pkg.name === 'kibana')) { throw new Error('Expected getInstalledPackages(kibana) to not include kibana pkg'); } - if (fixture1Packages.find(pkg => pkg.name === 'fixture1')) { + if (fixture1Packages.find((pkg) => pkg.name === 'fixture1')) { throw new Error('Expected getInstalledPackages(fixture1) to not include fixture1 pkg'); } }); diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 2eee3b2c53bd3..253fc104061a8 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -64,6 +64,8 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/plugins/apm/public/**/*', 'x-pack/plugins/apm/scripts/**/*', 'x-pack/plugins/apm/e2e/**/*', + + 'x-pack/plugins/maps/server/fonts/**/*', ]; /** @@ -171,12 +173,12 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'x-pack/plugins/monitoring/public/icons/health-green.svg', 'x-pack/plugins/monitoring/public/icons/health-red.svg', 'x-pack/plugins/monitoring/public/icons/health-yellow.svg', - 'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Medium.ttf', - 'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Regular.ttf', - 'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Italic.ttf', - 'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Medium.ttf', - 'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Regular.ttf', - 'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/img/logo-grey.png', + 'x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Medium.ttf', + 'x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Regular.ttf', + 'x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Italic.ttf', + 'x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Medium.ttf', + 'x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Regular.ttf', + 'x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/img/logo-grey.png', 'x-pack/test/functional/es_archives/monitoring/beats-with-restarted-instance/data.json.gz', 'x-pack/test/functional/es_archives/monitoring/beats-with-restarted-instance/mappings.json', 'x-pack/test/functional/es_archives/monitoring/logstash-pipelines/data.json.gz', diff --git a/src/dev/precommit_hook/check_file_casing.js b/src/dev/precommit_hook/check_file_casing.js index be0fbe22457bf..ec77d9a8bad3c 100644 --- a/src/dev/precommit_hook/check_file_casing.js +++ b/src/dev/precommit_hook/check_file_casing.js @@ -35,7 +35,7 @@ const NON_SNAKE_CASE_RE = /[A-Z \-]/; const NON_KEBAB_CASE_RE = /[A-Z \_]/; function listPaths(paths) { - return paths.map(path => ` - ${path}`).join('\n'); + return paths.map((path) => ` - ${path}`).join('\n'); } /** @@ -78,7 +78,7 @@ async function checkForKebabCase(log, files) { return acc.concat( parents.filter( - parent => + (parent) => matchesAnyGlob(parent, KEBAB_CASE_DIRECTORY_GLOBS) && NON_KEBAB_CASE_RE.test(basename(parent)) ) @@ -109,7 +109,7 @@ async function checkForSnakeCase(log, files) { const errorPaths = []; const warningPaths = []; - files.forEach(file => { + files.forEach((file) => { const path = file.getRelativePath(); if (TEMPORARILY_IGNORED_PATHS.includes(path)) { diff --git a/src/dev/precommit_hook/get_files_for_commit.js b/src/dev/precommit_hook/get_files_for_commit.js index 418e6d1d0a852..dc5560be0d1ad 100644 --- a/src/dev/precommit_hook/get_files_for_commit.js +++ b/src/dev/precommit_hook/get_files_for_commit.js @@ -33,16 +33,16 @@ import { File } from '../file'; export async function getFilesForCommit() { const simpleGit = new SimpleGit(REPO_ROOT); - const output = await fcb(cb => simpleGit.diff(['--name-status', '--cached'], cb)); + const output = await fcb((cb) => simpleGit.diff(['--name-status', '--cached'], cb)); return ( output .split('\n') // Ignore blank lines - .filter(line => line.trim().length > 0) + .filter((line) => line.trim().length > 0) // git diff --name-status outputs lines with two OR three parts // separated by a tab character - .map(line => line.trim().split('\t')) + .map((line) => line.trim().split('\t')) .map(([status, ...paths]) => { // ignore deleted files if (status === 'D') { diff --git a/src/dev/prs/helpers.ts b/src/dev/prs/helpers.ts index d25db1a79a1b9..b10b30806942b 100644 --- a/src/dev/prs/helpers.ts +++ b/src/dev/prs/helpers.ts @@ -25,7 +25,7 @@ import { takeUntil } from 'rxjs/operators'; * Convert a Readable stream to an observable of lines */ export const getLine$ = (stream: Readable) => { - return new Rx.Observable(subscriber => { + return new Rx.Observable((subscriber) => { let buffer = ''; return Rx.fromEvent(stream, 'data') .pipe(takeUntil(Rx.fromEvent(stream, 'close'))) diff --git a/src/dev/prs/run_update_prs_cli.ts b/src/dev/prs/run_update_prs_cli.ts index bb7f50758a28c..49668199a26d4 100644 --- a/src/dev/prs/run_update_prs_cli.ts +++ b/src/dev/prs/run_update_prs_cli.ts @@ -48,7 +48,7 @@ run( throw createFlagError('invalid --repo-dir, expected a single string'); } - const prNumbers = flags._.map(arg => Pr.parseInput(arg)); + const prNumbers = flags._.map((arg) => Pr.parseInput(arg)); /** * Call the Gitub API once for each PR to get the targetRef so we know which branch to pull @@ -56,7 +56,7 @@ run( */ const api = new GithubApi(accessToken); const prs = await Promise.all( - prNumbers.map(async prNumber => { + prNumbers.map(async (prNumber) => { const { targetRef, owner, sourceBranch } = await api.getPrInfo(prNumber); return new Pr(prNumber, targetRef, owner, sourceBranch); }) @@ -73,7 +73,7 @@ run( await Promise.all([ proc.then(() => log.debug(` - ${cmd} exited with 0`)), Rx.merge(getLine$(proc.stdout), getLine$(proc.stderr)) - .pipe(tap(line => log.debug(line))) + .pipe(tap((line) => log.debug(line))) .toPromise(), ]); }; @@ -130,9 +130,7 @@ run( `) + '\n' ); - await getLine$(process.stdin) - .pipe(first()) - .toPromise(); + await getLine$(process.stdin).pipe(first()).toPromise(); try { await execInDir('git', ['diff-index', '--quiet', 'HEAD', '--']); diff --git a/src/dev/renovate/config.ts b/src/dev/renovate/config.ts index fae424982c930..d868f0a89b98c 100644 --- a/src/dev/renovate/config.ts +++ b/src/dev/renovate/config.ts @@ -21,7 +21,7 @@ import { RENOVATE_PACKAGE_GROUPS } from './package_groups'; import { PACKAGE_GLOBS } from './package_globs'; import { wordRegExp, maybeFlatMap, maybeMap, getTypePackageName } from './utils'; -const DEFAULT_LABELS = ['release_note:skip', 'Team:Operations', 'renovate', 'v8.0.0', 'v7.7.0']; +const DEFAULT_LABELS = ['release_note:skip', 'Team:Operations', 'renovate', 'v8.0.0', 'v7.9.0']; export const RENOVATE_CONFIG = { extends: ['config:base'], @@ -85,11 +85,11 @@ export const RENOVATE_CONFIG = { * Define groups of packages that should be updated/configured together */ packageRules: [ - ...RENOVATE_PACKAGE_GROUPS.map(group => ({ + ...RENOVATE_PACKAGE_GROUPS.map((group) => ({ groupSlug: group.name, groupName: `${group.name} related packages`, - packagePatterns: maybeMap(group.packageWords, word => wordRegExp(word).source), - packageNames: maybeFlatMap(group.packageNames, name => [name, getTypePackageName(name)]), + packagePatterns: maybeMap(group.packageWords, (word) => wordRegExp(word).source), + packageNames: maybeFlatMap(group.packageNames, (name) => [name, getTypePackageName(name)]), labels: group.extraLabels && [...DEFAULT_LABELS, ...group.extraLabels], enabled: group.enabled === false ? false : undefined, allowedVersions: group.allowedVersions || undefined, diff --git a/src/dev/renovate/package_groups.ts b/src/dev/renovate/package_groups.ts index 9f5aa8556ac21..d051f956d14df 100644 --- a/src/dev/renovate/package_groups.ts +++ b/src/dev/renovate/package_groups.ts @@ -214,9 +214,9 @@ for (const dep of getAllDepNames()) { // determine if one of the existing groups has typesFor in its // packageNames or if any of the packageWords is in typesFor const existing = RENOVATE_PACKAGE_GROUPS.some( - group => + (group) => (group.packageNames || []).includes(typesFor) || - (group.packageWords || []).some(word => wordRegExp(word).test(typesFor)) + (group.packageWords || []).some((word) => wordRegExp(word).test(typesFor)) ); if (existing) { diff --git a/src/dev/run_check_file_casing.js b/src/dev/run_check_file_casing.js index 87107b43d9ba0..bdbf1827897e7 100644 --- a/src/dev/run_check_file_casing.js +++ b/src/dev/run_check_file_casing.js @@ -39,7 +39,7 @@ run(async ({ log }) => { ], }); - const files = paths.map(path => new File(path)); + const files = paths.map((path) => new File(path)); await checkFileCasing(log, files); }); diff --git a/src/dev/run_check_lockfile_symlinks.js b/src/dev/run_check_lockfile_symlinks.js index b912ea9ddb87e..5ebb7952e2cf3 100644 --- a/src/dev/run_check_lockfile_symlinks.js +++ b/src/dev/run_check_lockfile_symlinks.js @@ -55,13 +55,15 @@ run(async ({ log }) => { ], }); - const files = paths.map(path => new File(path)); + const files = paths.map((path) => new File(path)); await checkLockfileSymlinks(log, files); }); async function checkLockfileSymlinks(log, files) { - const filtered = files.filter(file => !matchesAnyGlob(file.getRelativePath(), IGNORE_FILE_GLOBS)); + const filtered = files.filter( + (file) => !matchesAnyGlob(file.getRelativePath(), IGNORE_FILE_GLOBS) + ); await checkOnlyLockfileAtProjectRoot(filtered); await checkSuperfluousSymlinks(log, filtered); await checkMissingSymlinks(log, filtered); @@ -72,8 +74,8 @@ async function checkOnlyLockfileAtProjectRoot(files) { const errorPaths = []; files - .filter(file => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) - .forEach(file => { + .filter((file) => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) + .forEach((file) => { const path = file.getRelativePath(); const parent = dirname(path); const stats = lstatSync(path); @@ -93,8 +95,8 @@ async function checkSuperfluousSymlinks(log, files) { const errorPaths = []; files - .filter(file => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) - .forEach(file => { + .filter((file) => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) + .forEach((file) => { const path = file.getRelativePath(); const parent = dirname(path); const stats = lstatSync(path); @@ -146,8 +148,8 @@ async function checkMissingSymlinks(log, files) { const errorPaths = []; files - .filter(file => matchesAnyGlob(file.getRelativePath(), MANIFEST_GLOBS)) - .forEach(file => { + .filter((file) => matchesAnyGlob(file.getRelativePath(), MANIFEST_GLOBS)) + .forEach((file) => { const path = file.getRelativePath(); const parent = dirname(path); const lockfilePath = `${parent}/yarn.lock`; @@ -185,8 +187,8 @@ async function checkIncorrectSymlinks(log, files) { const errorPaths = []; files - .filter(file => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) - .forEach(file => { + .filter((file) => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) + .forEach((file) => { const path = file.getRelativePath(); const stats = lstatSync(path); if (!stats.isSymbolicLink()) { @@ -218,5 +220,5 @@ function getCorrectSymlink(path) { } function listPaths(paths) { - return paths.map(path => ` - ${path}`).join('\n'); + return paths.map((path) => ` - ${path}`).join('\n'); } diff --git a/src/dev/run_check_published_api_changes.ts b/src/dev/run_check_published_api_changes.ts index 6d5fa04a93951..45dafe1b415e3 100644 --- a/src/dev/run_check_published_api_changes.ts +++ b/src/dev/run_check_published_api_changes.ts @@ -268,14 +268,14 @@ async function run( const results = await Promise.all( folders - .filter(folder => (opts.filter.length ? folder.match(opts.filter) : true)) - .map(folder => run(folder, { log, opts })) + .filter((folder) => (opts.filter.length ? folder.match(opts.filter) : true)) + .map((folder) => run(folder, { log, opts })) ); - if (results.find(r => r === false) !== undefined) { + if (results.find((r) => r === false) !== undefined) { process.exitCode = 1; } -})().catch(e => { +})().catch((e) => { console.log(e); process.exitCode = 1; }); diff --git a/src/dev/run_i18n_extract.ts b/src/dev/run_i18n_extract.ts index 106c8f10cb5cc..208c86d4acecc 100644 --- a/src/dev/run_i18n_extract.ts +++ b/src/dev/run_i18n_extract.ts @@ -60,8 +60,8 @@ run( }, { title: 'Writing to file', - enabled: ctx => outputDir && ctx.messages.size, - task: async ctx => { + enabled: (ctx) => outputDir && ctx.messages.size, + task: async (ctx) => { const sortedMessages = [...ctx.messages].sort(([key1], [key2]) => key1.localeCompare(key2) ); diff --git a/src/dev/run_prettier_on_changed.ts b/src/dev/run_prettier_on_changed.ts deleted file mode 100644 index deca4fa1be3ce..0000000000000 --- a/src/dev/run_prettier_on_changed.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import execa from 'execa'; -// @ts-ignore -import SimpleGit from 'simple-git'; -import { run } from '@kbn/dev-utils'; -import dedent from 'dedent'; -import Util from 'util'; - -import pkg from '../../package.json'; -import { REPO_ROOT } from './constants'; -import { File } from './file'; -import * as Eslint from './eslint'; - -run(async function getChangedFiles({ log }) { - const simpleGit = new SimpleGit(REPO_ROOT); - - const getStatus = Util.promisify(simpleGit.status.bind(simpleGit)); - const gitStatus = await getStatus(); - - if (gitStatus.files.length > 0) { - throw new Error( - dedent(`You should run prettier formatter on a clean branch. - Found not committed changes to: - ${gitStatus.files.map((f: { path: string }) => f.path).join('\n')}`) - ); - } - - const revParse = Util.promisify(simpleGit.revparse.bind(simpleGit)); - const currentBranch = await revParse(['--abbrev-ref', 'HEAD']); - const headBranch = pkg.branch; - - const diff = Util.promisify(simpleGit.diff.bind(simpleGit)); - - const changedFileStatuses: string = await diff([ - '--name-status', - `${headBranch}...${currentBranch}`, - ]); - - const changedFiles = changedFileStatuses - .split('\n') - // Ignore blank lines - .filter(line => line.trim().length > 0) - // git diff --name-status outputs lines with two OR three parts - // separated by a tab character - .map(line => line.trim().split('\t')) - .map(([status, ...paths]) => { - // ignore deleted files - if (status === 'D') { - return undefined; - } - - // the status is always in the first column - // .. If the file is edited the line will only have two columns - // .. If the file is renamed it will have three columns - // .. In any case, the last column is the CURRENT path to the file - return new File(paths[paths.length - 1]); - }) - .filter((file): file is File => Boolean(file)); - - const pathsToLint = Eslint.pickFilesToLint(log, changedFiles).map(f => f.getAbsolutePath()); - - if (pathsToLint.length > 0) { - log.debug('[prettier] run on %j files: ', pathsToLint.length, pathsToLint); - } - - while (pathsToLint.length > 0) { - await execa('npx', ['prettier@2.0.4', '--write', ...pathsToLint.splice(0, 100)]); - } -}); diff --git a/src/dev/sass/build_sass.js b/src/dev/sass/build_sass.js index 1ff7c700d0386..7075bcf55adf5 100644 --- a/src/dev/sass/build_sass.js +++ b/src/dev/sass/build_sass.js @@ -44,7 +44,7 @@ const build = async ({ log, kibanaDir, styleSheetPaths, watch }) => { sourceMap: true, }); - bundles.forEach(bundle => { + bundles.forEach((bundle) => { log.debug(`Compiled SCSS: ${bundle.sourcePath} (theme=${bundle.theme})`); }); @@ -63,9 +63,7 @@ export async function buildSass({ log, kibanaDir, watch }) { const scanDirs = [resolve(kibanaDir, 'src/legacy/core_plugins')]; const paths = [resolve(kibanaDir, 'x-pack')]; const { spec$, disabledSpec$ } = findPluginSpecs({ plugins: { scanDirs, paths } }); - const allPlugins = await Rx.merge(spec$, disabledSpec$) - .pipe(toArray()) - .toPromise(); + const allPlugins = await Rx.merge(spec$, disabledSpec$).pipe(toArray()).toPromise(); const uiExports = collectUiExports(allPlugins); const { styleSheetPaths } = uiExports; @@ -73,17 +71,17 @@ export async function buildSass({ log, kibanaDir, watch }) { log.verbose(styleSheetPaths); if (watch) { - const debouncedBuild = debounce(async path => { + const debouncedBuild = debounce(async (path) => { let buildPaths = styleSheetPaths; if (path) { - buildPaths = styleSheetPaths.filter(styleSheetPath => + buildPaths = styleSheetPaths.filter((styleSheetPath) => path.includes(styleSheetPath.urlImports.publicDir) ); } await build({ log, kibanaDir, styleSheetPaths: buildPaths, watch }); }); - const watchPaths = styleSheetPaths.map(styleSheetPath => styleSheetPath.urlImports.publicDir); + const watchPaths = styleSheetPaths.map((styleSheetPath) => styleSheetPath.urlImports.publicDir); await build({ log, kibanaDir, styleSheetPaths }); diff --git a/src/dev/sasslint/lint_files.js b/src/dev/sasslint/lint_files.js index b9df6a1fa9abb..3a560b4a6ea1a 100644 --- a/src/dev/sasslint/lint_files.js +++ b/src/dev/sasslint/lint_files.js @@ -30,7 +30,7 @@ import { createFailError } from '@kbn/dev-utils'; * @return {undefined} */ export function lintFiles(log, files) { - const paths = files.map(file => file.getRelativePath()); + const paths = files.map((file) => file.getRelativePath()); const report = sassLint.lintFiles( paths.join(', '), diff --git a/src/dev/sasslint/pick_files_to_lint.js b/src/dev/sasslint/pick_files_to_lint.js index c4d8af7bb674f..57c38d0069e06 100644 --- a/src/dev/sasslint/pick_files_to_lint.js +++ b/src/dev/sasslint/pick_files_to_lint.js @@ -28,7 +28,7 @@ const sassLintConfig = safeLoad(fs.readFileSync(sassLintPath)); const { files: { include: includeGlobs }, } = sassLintConfig; -const includeRegex = includeGlobs.map(glob => makeRe(glob)); +const includeRegex = includeGlobs.map((glob) => makeRe(glob)); function matchesInclude(file) { for (let i = 0; i < includeRegex.length; i++) { @@ -40,5 +40,5 @@ function matchesInclude(file) { } export function pickFilesToLint(log, files) { - return files.filter(file => file.isSass()).filter(matchesInclude); + return files.filter((file) => file.isSass()).filter(matchesInclude); } diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 416702c56d852..2f785896da8d5 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -18,7 +18,6 @@ */ export const storybookAliases = { - advanced_ui_actions: 'x-pack/plugins/advanced_ui_actions/scripts/storybook.js', apm: 'x-pack/plugins/apm/scripts/storybook.js', canvas: 'x-pack/plugins/canvas/scripts/storybook_new.js', codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts', @@ -26,5 +25,6 @@ export const storybookAliases = { drilldowns: 'x-pack/plugins/drilldowns/scripts/storybook.js', embeddable: 'src/plugins/embeddable/scripts/storybook.js', infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', - siem: 'x-pack/plugins/siem/scripts/storybook.js', + security_solution: 'x-pack/plugins/security_solution/scripts/storybook.js', + ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/scripts/storybook.js', }; diff --git a/src/dev/storybook/run_storybook_cli.ts b/src/dev/storybook/run_storybook_cli.ts index efb618a48cd6e..7f97cff91aaaa 100644 --- a/src/dev/storybook/run_storybook_cli.ts +++ b/src/dev/storybook/run_storybook_cli.ts @@ -24,7 +24,7 @@ import { storybookAliases } from './aliases'; import { clean } from './commands/clean'; run( - async params => { + async (params) => { const { flags, log } = params; const { _: [alias], @@ -62,7 +62,7 @@ run( Available aliases: ${Object.keys(storybookAliases) - .map(alias => `📕 ${alias}`) + .map((alias) => `📕 ${alias}`) .join('\n ')} Add your alias in src/dev/storybook/aliases.ts diff --git a/src/dev/typescript/exec_in_projects.ts b/src/dev/typescript/exec_in_projects.ts index a34f2bdd28670..5197aa67c7268 100644 --- a/src/dev/typescript/exec_in_projects.ts +++ b/src/dev/typescript/exec_in_projects.ts @@ -37,7 +37,7 @@ export function execInProjects( getArgs: (project: Project) => string[] ) { const list = new Listr( - projects.map(project => ({ + projects.map((project) => ({ task: () => execa(cmd, getArgs(project), { // execute in the current working directory so that relative paths in errors @@ -46,7 +46,7 @@ export function execInProjects( env: chalk.enabled ? { FORCE_COLOR: 'true' } : {}, stdio: ['ignore', 'pipe', 'pipe'], preferLocal: true, - }).catch(error => { + }).catch((error) => { throw new ProjectFailure(project, error); }), title: project.name, diff --git a/src/dev/typescript/get_ts_project_for_absolute_path.ts b/src/dev/typescript/get_ts_project_for_absolute_path.ts index 68ce70685b607..54d7e950834a4 100644 --- a/src/dev/typescript/get_ts_project_for_absolute_path.ts +++ b/src/dev/typescript/get_ts_project_for_absolute_path.ts @@ -35,7 +35,7 @@ import { PROJECTS } from './projects'; export function getTsProjectForAbsolutePath(path: string): Project { const relPath = relative(REPO_ROOT, path); const file = new File(resolve(REPO_ROOT, path)); - const projects = PROJECTS.filter(p => p.isAbsolutePathSelected(path)); + const projects = PROJECTS.filter((p) => p.isAbsolutePathSelected(path)); if (!projects.length) { throw new Error( @@ -44,7 +44,7 @@ export function getTsProjectForAbsolutePath(path: string): Project { } if (projects.length !== 1 && !file.isTypescriptAmbient()) { - const configPaths = projects.map(p => `"${relative(REPO_ROOT, p.tsConfigPath)}"`); + const configPaths = projects.map((p) => `"${relative(REPO_ROOT, p.tsConfigPath)}"`); const pathsMsg = `${configPaths.slice(0, -1).join(', ')} or ${ configPaths[configPaths.length - 1] diff --git a/src/dev/typescript/project.ts b/src/dev/typescript/project.ts index 623c248144ff6..4cf87d812a0b3 100644 --- a/src/dev/typescript/project.ts +++ b/src/dev/typescript/project.ts @@ -27,7 +27,7 @@ import { REPO_ROOT } from '../constants'; function makeMatchers(directory: string, patterns: string[]) { return patterns.map( - pattern => + (pattern) => new Minimatch(resolve(directory, pattern), { dot: true, }) @@ -45,7 +45,7 @@ function parseTsConfig(path: string) { } function testMatchers(matchers: IMinimatch[], path: string) { - return matchers.some(matcher => matcher.match(path)); + return matchers.some((matcher) => matcher.match(path)); } export class Project { diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index 5019c8bd22341..1e0b631308d9e 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -27,8 +27,8 @@ export const PROJECTS = [ new Project(resolve(REPO_ROOT, 'test/tsconfig.json'), { name: 'kibana/test' }), new Project(resolve(REPO_ROOT, 'x-pack/tsconfig.json')), new Project(resolve(REPO_ROOT, 'x-pack/test/tsconfig.json'), { name: 'x-pack/test' }), - new Project(resolve(REPO_ROOT, 'x-pack/plugins/siem/cypress/tsconfig.json'), { - name: 'siem/cypress', + new Project(resolve(REPO_ROOT, 'x-pack/plugins/security_solution/cypress/tsconfig.json'), { + name: 'security_solution/cypress', }), new Project(resolve(REPO_ROOT, 'x-pack/plugins/apm/e2e/tsconfig.json'), { name: 'apm/cypress', @@ -40,19 +40,19 @@ export const PROJECTS = [ // both took closer to 1000ms. ...glob .sync('packages/*/tsconfig.json', { cwd: REPO_ROOT }) - .map(path => new Project(resolve(REPO_ROOT, path))), + .map((path) => new Project(resolve(REPO_ROOT, path))), ...glob .sync('examples/*/tsconfig.json', { cwd: REPO_ROOT }) - .map(path => new Project(resolve(REPO_ROOT, path))), + .map((path) => new Project(resolve(REPO_ROOT, path))), ...glob .sync('x-pack/examples/*/tsconfig.json', { cwd: REPO_ROOT }) - .map(path => new Project(resolve(REPO_ROOT, path))), + .map((path) => new Project(resolve(REPO_ROOT, path))), ...glob .sync('test/plugin_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT }) - .map(path => new Project(resolve(REPO_ROOT, path))), + .map((path) => new Project(resolve(REPO_ROOT, path))), ...glob .sync('test/interpreter_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT }) - .map(path => new Project(resolve(REPO_ROOT, path))), + .map((path) => new Project(resolve(REPO_ROOT, path))), ]; export function filterProjectsByFlag(projectFlag?: string) { @@ -61,5 +61,5 @@ export function filterProjectsByFlag(projectFlag?: string) { } const tsConfigPath = resolve(projectFlag); - return PROJECTS.filter(project => project.tsConfigPath === tsConfigPath); + return PROJECTS.filter((project) => project.tsConfigPath === tsConfigPath); } diff --git a/src/dev/typescript/run_check_ts_projects_cli.ts b/src/dev/typescript/run_check_ts_projects_cli.ts index 85f3d473dce6b..b0c125ea47829 100644 --- a/src/dev/typescript/run_check_ts_projects_cli.ts +++ b/src/dev/typescript/run_check_ts_projects_cli.ts @@ -51,7 +51,7 @@ export async function runCheckTsProjectsCli() { log.verbose('Checking %s', file.getAbsolutePath()); - const projects = PROJECTS.filter(p => p.isAbsolutePathSelected(file.getAbsolutePath())); + const projects = PROJECTS.filter((p) => p.isAbsolutePathSelected(file.getAbsolutePath())); if (projects.length === 0) { isNotInTsProject.push(file); } @@ -68,7 +68,7 @@ export async function runCheckTsProjectsCli() { if (isNotInTsProject.length) { log.error( `The following files do not belong to a tsconfig.json file, or that tsconfig.json file is not listed in src/dev/typescript/projects.ts\n${isNotInTsProject - .map(file => ` - ${file.getRelativePath()}`) + .map((file) => ` - ${file.getRelativePath()}`) .join('\n')}` ); } @@ -76,7 +76,7 @@ export async function runCheckTsProjectsCli() { if (isInMultipleTsProjects.length) { log.error( `The following files belong to multiple tsconfig.json files listed in src/dev/typescript/projects.ts\n${isInMultipleTsProjects - .map(file => ` - ${file.getRelativePath()}`) + .map((file) => ` - ${file.getRelativePath()}`) .join('\n')}` ); } diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index 1f0e4b48b7b4b..5d4cf14c1cd95 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -80,15 +80,15 @@ export function runTypeCheckCli() { } const tscArgs = ['--noEmit', '--pretty', ...(opts['skip-lib-check'] ? ['--skipLibCheck'] : [])]; - const projects = filterProjectsByFlag(opts.project).filter(p => !p.disableTypeCheck); + const projects = filterProjectsByFlag(opts.project).filter((p) => !p.disableTypeCheck); if (!projects.length) { log.error(`Unable to find project at ${opts.project}`); process.exit(1); } - execInProjects(log, projects, process.execPath, project => [ - ...(project.name === 'x-pack' ? ['--max-old-space-size=4096'] : []), + execInProjects(log, projects, process.execPath, (project) => [ + ...(project.name.startsWith('x-pack') ? ['--max-old-space-size=4096'] : []), require.resolve('typescript/bin/tsc'), ...['--project', project.tsConfigPath], ...tscArgs, diff --git a/src/es_archiver/actions/edit.ts b/src/es_archiver/actions/edit.ts index de63081a1ea1b..afa51a3b96477 100644 --- a/src/es_archiver/actions/edit.ts +++ b/src/es_archiver/actions/edit.ts @@ -44,13 +44,13 @@ export async function editAction({ cwd: prefix ? resolve(dataDir, prefix) : dataDir, absolute: true, }) - ).map(path => ({ + ).map((path) => ({ path, rawPath: path.slice(0, -3), })); await Promise.all( - archives.map(async archive => { + archives.map(async (archive) => { await createPromiseFromStreams([ Fs.createReadStream(archive.path), createGunzip(), @@ -70,7 +70,7 @@ export async function editAction({ await handler(); await Promise.all( - archives.map(async archive => { + archives.map(async (archive) => { await createPromiseFromStreams([ Fs.createReadStream(archive.rawPath), createGzip({ level: Z_BEST_COMPRESSION }), diff --git a/src/es_archiver/actions/load.ts b/src/es_archiver/actions/load.ts index ae7799205b299..19b5b9e75f31a 100644 --- a/src/es_archiver/actions/load.ts +++ b/src/es_archiver/actions/load.ts @@ -43,7 +43,7 @@ import { // are not listened for const pipeline = (...streams: Readable[]) => streams.reduce((source, dest) => - source.once('error', error => dest.emit('error', error)).pipe(dest as any) + source.once('error', (error) => dest.emit('error', error)).pipe(dest as any) ); export async function loadAction({ @@ -70,7 +70,7 @@ export async function loadAction({ // order, so that createIndexStream can track the state of indexes // across archives and properly skip docs from existing indexes const recordStream = concatStreamProviders( - files.map(filename => () => { + files.map((filename) => () => { log.info('[%s] Loading %j', name, filename); return pipeline( @@ -105,7 +105,7 @@ export async function loadAction({ }); // If we affected the Kibana index, we need to ensure it's migrated... - if (Object.keys(result).some(k => k.startsWith('.kibana'))) { + if (Object.keys(result).some((k) => k.startsWith('.kibana'))) { await migrateKibanaIndex({ client, kbnClient }); if (kibanaPluginIds.includes('spaces')) { diff --git a/src/es_archiver/actions/rebuild_all.ts b/src/es_archiver/actions/rebuild_all.ts index f35b2ca49c666..dfbd51300e04d 100644 --- a/src/es_archiver/actions/rebuild_all.ts +++ b/src/es_archiver/actions/rebuild_all.ts @@ -33,7 +33,7 @@ import { } from '../lib'; async function isDirectory(path: string): Promise { - const stats: Stats = await fromNode(cb => stat(path, cb)); + const stats: Stats = await fromNode((cb) => stat(path, cb)); return stats.isDirectory(); } @@ -71,7 +71,7 @@ export async function rebuildAllAction({ createWriteStream(tempFile), ] as [Readable, ...Writable[]]); - await fromNode(cb => rename(tempFile, childPath, cb)); + await fromNode((cb) => rename(tempFile, childPath, cb)); log.info(`${archiveName} Rebuilt ${childName}`); } } diff --git a/src/es_archiver/cli.ts b/src/es_archiver/cli.ts index 252f99f8f47af..98888b81d9a31 100644 --- a/src/es_archiver/cli.ts +++ b/src/es_archiver/cli.ts @@ -69,34 +69,34 @@ cmd cmd .command('load ') .description('load the archive in --dir with ') - .action(name => execute(archiver => archiver.load(name))); + .action((name) => execute((archiver) => archiver.load(name))); cmd .command('unload ') .description('remove indices created by the archive in --dir with ') - .action(name => execute(archiver => archiver.unload(name))); + .action((name) => execute((archiver) => archiver.unload(name))); cmd .command('empty-kibana-index') .description( '[internal] Delete any Kibana indices, and initialize the Kibana index as Kibana would do on startup.' ) - .action(() => execute(archiver => archiver.emptyKibanaIndex())); + .action(() => execute((archiver) => archiver.emptyKibanaIndex())); cmd .command('edit [prefix]') .description( 'extract the archives under the prefix, wait for edits to be completed, and then recompress the archives' ) - .action(prefix => - execute(archiver => + .action((prefix) => + execute((archiver) => archiver.edit(prefix, async () => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); - await new Promise(resolveInput => { + await new Promise((resolveInput) => { rl.question(`Press enter when you're done`, () => { rl.close(); resolveInput(); @@ -109,11 +109,11 @@ cmd cmd .command('rebuild-all') .description('[internal] read and write all archives in --dir to remove any inconsistencies') - .action(() => execute(archiver => archiver.rebuildAll())); + .action(() => execute((archiver) => archiver.rebuildAll())); cmd.parse(process.argv); -const missingCommand = cmd.args.every(a => !((a as any) instanceof Command)); +const missingCommand = cmd.args.every((a) => !((a as any) instanceof Command)); if (missingCommand) { execute(); } diff --git a/src/es_archiver/lib/__tests__/stats.ts b/src/es_archiver/lib/__tests__/stats.ts index 28e337b3da529..0ab7d161feb6e 100644 --- a/src/es_archiver/lib/__tests__/stats.ts +++ b/src/es_archiver/lib/__tests__/stats.ts @@ -28,7 +28,7 @@ function createBufferedLog(): ToolingLog & { buffer: string } { const log: ToolingLog = new ToolingLog({ level: 'debug', writeTo: { - write: chunk => ((log as any).buffer += chunk), + write: (chunk) => ((log as any).buffer += chunk), }, }); (log as any).buffer = ''; @@ -47,7 +47,7 @@ function assertDeepClones(a: any, b: any) { expect(one).to.eql(two); expect(one).to.not.be(two); const keys = uniq(Object.keys(one).concat(Object.keys(two))); - keys.forEach(k => { + keys.forEach((k) => { path.push(k); recurse(one[k], two[k]); path.pop(); diff --git a/src/es_archiver/lib/archives/__tests__/format.ts b/src/es_archiver/lib/archives/__tests__/format.ts index f472f094134d7..f3829273ea808 100644 --- a/src/es_archiver/lib/archives/__tests__/format.ts +++ b/src/es_archiver/lib/archives/__tests__/format.ts @@ -31,7 +31,7 @@ import { import { createFormatArchiveStreams } from '../format'; const INPUTS = [1, 2, { foo: 'bar' }, [1, 2]]; -const INPUT_JSON = INPUTS.map(i => JSON.stringify(i, null, 2)).join('\n\n'); +const INPUT_JSON = INPUTS.map((i) => JSON.stringify(i, null, 2)).join('\n\n'); describe('esArchiver createFormatArchiveStreams', () => { describe('{ gzip: false }', () => { @@ -39,7 +39,7 @@ describe('esArchiver createFormatArchiveStreams', () => { const streams = createFormatArchiveStreams({ gzip: false }); expect(streams).to.be.an('array'); expect(streams.length).to.be.greaterThan(0); - streams.forEach(s => expect(s).to.be.a(Stream)); + streams.forEach((s) => expect(s).to.be.a(Stream)); }); it('streams consume js values and produces buffers', async () => { @@ -50,7 +50,7 @@ describe('esArchiver createFormatArchiveStreams', () => { ] as [Readable, ...Writable[]]); expect(output.length).to.be.greaterThan(0); - output.forEach(b => expect(b).to.be.a(Buffer)); + output.forEach((b) => expect(b).to.be.a(Buffer)); }); it('product is pretty-printed JSON separated by two newlines', async () => { @@ -69,7 +69,7 @@ describe('esArchiver createFormatArchiveStreams', () => { const streams = createFormatArchiveStreams({ gzip: true }); expect(streams).to.be.an('array'); expect(streams.length).to.be.greaterThan(0); - streams.forEach(s => expect(s).to.be.a(Stream)); + streams.forEach((s) => expect(s).to.be.a(Stream)); }); it('streams consume js values and produces buffers', async () => { @@ -80,7 +80,7 @@ describe('esArchiver createFormatArchiveStreams', () => { ] as [Readable, ...Writable[]]); expect(output.length).to.be.greaterThan(0); - output.forEach(b => expect(b).to.be.a(Buffer)); + output.forEach((b) => expect(b).to.be.a(Buffer)); }); it('output can be gunzipped', async () => { diff --git a/src/es_archiver/lib/archives/__tests__/parse.ts b/src/es_archiver/lib/archives/__tests__/parse.ts index ba30156b5af39..50cbdfe06f361 100644 --- a/src/es_archiver/lib/archives/__tests__/parse.ts +++ b/src/es_archiver/lib/archives/__tests__/parse.ts @@ -36,7 +36,7 @@ describe('esArchiver createParseArchiveStreams', () => { const streams = createParseArchiveStreams({ gzip: false }); expect(streams).to.be.an('array'); expect(streams.length).to.be.greaterThan(0); - streams.forEach(s => expect(s).to.be.a(Stream)); + streams.forEach((s) => expect(s).to.be.a(Stream)); }); describe('streams', () => { @@ -73,7 +73,7 @@ describe('esArchiver createParseArchiveStreams', () => { it('provides each JSON object as soon as it is parsed', async () => { let onReceived: (resolved: any) => void; - const receivedPromise = new Promise(resolve => (onReceived = resolve)); + const receivedPromise = new Promise((resolve) => (onReceived = resolve)); const input = new PassThrough(); const check = new Transform({ writableObjectMode: true, @@ -124,7 +124,7 @@ describe('esArchiver createParseArchiveStreams', () => { const streams = createParseArchiveStreams({ gzip: true }); expect(streams).to.be.an('array'); expect(streams.length).to.be.greaterThan(0); - streams.forEach(s => expect(s).to.be.a(Stream)); + streams.forEach((s) => expect(s).to.be.a(Stream)); }); describe('streams', () => { diff --git a/src/es_archiver/lib/archives/format.ts b/src/es_archiver/lib/archives/format.ts index 9bef4c9adbf05..ac18147ad6948 100644 --- a/src/es_archiver/lib/archives/format.ts +++ b/src/es_archiver/lib/archives/format.ts @@ -26,7 +26,7 @@ import { RECORD_SEPARATOR } from './constants'; export function createFormatArchiveStreams({ gzip = false }: { gzip?: boolean } = {}) { return [ - createMapStream(record => stringify(record, { space: ' ' })), + createMapStream((record) => stringify(record, { space: ' ' })), createIntersperseStream(RECORD_SEPARATOR), gzip ? createGzip({ level: Z_BEST_COMPRESSION }) : new PassThrough(), ]; diff --git a/src/es_archiver/lib/archives/parse.ts b/src/es_archiver/lib/archives/parse.ts index 0f4460c925019..1d650815f9358 100644 --- a/src/es_archiver/lib/archives/parse.ts +++ b/src/es_archiver/lib/archives/parse.ts @@ -29,7 +29,7 @@ export function createParseArchiveStreams({ gzip = false } = {}) { gzip ? createGunzip() : new PassThrough(), createReplaceStream('\r\n', '\n'), createSplitStream(RECORD_SEPARATOR), - createFilterStream(l => !!l.match(/[^\s]/)), - createMapStream(json => JSON.parse(json.trim())), + createFilterStream((l) => !!l.match(/[^\s]/)), + createMapStream((json) => JSON.parse(json.trim())), ]; } diff --git a/src/es_archiver/lib/directory.ts b/src/es_archiver/lib/directory.ts index 8581207fa795d..3a48e576f44ce 100644 --- a/src/es_archiver/lib/directory.ts +++ b/src/es_archiver/lib/directory.ts @@ -21,6 +21,6 @@ import { readdir } from 'fs'; import { fromNode } from 'bluebird'; export async function readDirectory(path: string) { - const allNames = await fromNode(cb => readdir(path, cb)); - return allNames.filter(name => !name.startsWith('.')); + const allNames = await fromNode((cb) => readdir(path, cb)); + return allNames.filter((name) => !name.startsWith('.')); } diff --git a/src/es_archiver/lib/docs/__tests__/stubs.ts b/src/es_archiver/lib/docs/__tests__/stubs.ts index 698d62e450cb4..d8d961fa054ff 100644 --- a/src/es_archiver/lib/docs/__tests__/stubs.ts +++ b/src/es_archiver/lib/docs/__tests__/stubs.ts @@ -55,7 +55,7 @@ export const createStubClient = ( responses: Array<(name: string, params: any) => any | Promise> = [] ): MockClient => { const createStubClientMethod = (name: string) => - sinon.spy(async params => { + sinon.spy(async (params) => { if (responses.length === 0) { throw new Error(`unexpected client.${name} call`); } diff --git a/src/es_archiver/lib/docs/index_doc_records_stream.ts b/src/es_archiver/lib/docs/index_doc_records_stream.ts index 8236ae8adb6db..a21227aae66c2 100644 --- a/src/es_archiver/lib/docs/index_doc_records_stream.ts +++ b/src/es_archiver/lib/docs/index_doc_records_stream.ts @@ -26,7 +26,7 @@ export function createIndexDocRecordsStream(client: Client, stats: Stats, progre async function indexDocs(docs: any[]) { const body: any[] = []; - docs.forEach(doc => { + docs.forEach((doc) => { stats.indexedDoc(doc.index); body.push( { diff --git a/src/es_archiver/lib/indices/__tests__/generate_index_records_stream.ts b/src/es_archiver/lib/indices/__tests__/generate_index_records_stream.ts index 7a3712ca1a336..fe927483da7b0 100644 --- a/src/es_archiver/lib/indices/__tests__/generate_index_records_stream.ts +++ b/src/es_archiver/lib/indices/__tests__/generate_index_records_stream.ts @@ -63,10 +63,10 @@ describe('esArchiver: createGenerateIndexRecordsStream()', () => { const params = (client.indices.get as sinon.SinonSpy).args[0][0]; expect(params).to.have.property('filterPath'); const filters: string[] = params.filterPath; - expect(filters.some(path => path.includes('index.creation_date'))).to.be(true); - expect(filters.some(path => path.includes('index.uuid'))).to.be(true); - expect(filters.some(path => path.includes('index.version'))).to.be(true); - expect(filters.some(path => path.includes('index.provided_name'))).to.be(true); + expect(filters.some((path) => path.includes('index.creation_date'))).to.be(true); + expect(filters.some((path) => path.includes('index.uuid'))).to.be(true); + expect(filters.some((path) => path.includes('index.version'))).to.be(true); + expect(filters.some((path) => path.includes('index.provided_name'))).to.be(true); }); it('produces one index record for each index name it receives', async () => { diff --git a/src/es_archiver/lib/indices/__tests__/stubs.ts b/src/es_archiver/lib/indices/__tests__/stubs.ts index 3f4682299c38d..c894468dcfcf6 100644 --- a/src/es_archiver/lib/indices/__tests__/stubs.ts +++ b/src/es_archiver/lib/indices/__tests__/stubs.ts @@ -35,7 +35,7 @@ export const createStubStats = (): StubStats => archivedIndex: sinon.stub(), getTestSummary() { const summary: Record = {}; - Object.keys(this).forEach(key => { + Object.keys(this).forEach((key) => { if (this[key].callCount) { summary[key] = this[key].callCount; } @@ -74,7 +74,7 @@ const createEsClientError = (errorType: string) => { }; const indexAlias = (aliases: Record, index: string) => - Object.keys(aliases).find(k => aliases[k] === index); + Object.keys(aliases).find((k) => aliases[k] === index); type StubClient = Client; @@ -133,15 +133,15 @@ export const createStubClient = ( }), delete: sinon.spy(async ({ index }) => { const indices = Array.isArray(index) ? index : [index]; - if (indices.every(ix => existingIndices.includes(ix))) { + if (indices.every((ix) => existingIndices.includes(ix))) { // Delete aliases associated with our indices - indices.forEach(ix => { - const alias = Object.keys(aliases).find(k => aliases[k] === ix); + indices.forEach((ix) => { + const alias = Object.keys(aliases).find((k) => aliases[k] === ix); if (alias) { delete aliases[alias]; } }); - indices.forEach(ix => existingIndices.splice(existingIndices.indexOf(ix), 1)); + indices.forEach((ix) => existingIndices.splice(existingIndices.indexOf(ix), 1)); return { ok: true }; } else { throw createEsClientError('index_not_found_exception'); diff --git a/src/es_archiver/lib/indices/delete_index.ts b/src/es_archiver/lib/indices/delete_index.ts index e3fca587fbc3d..d7ef20b072b26 100644 --- a/src/es_archiver/lib/indices/delete_index.ts +++ b/src/es_archiver/lib/indices/delete_index.ts @@ -109,7 +109,7 @@ export async function waitForSnapshotCompletion(client: Client, index: string, l while (await isSnapshotPending(repository, found.snapshot)) { // wait a bit before getting status again - await new Promise(resolve => setTimeout(resolve, 500)); + await new Promise((resolve) => setTimeout(resolve, 500)); } return; diff --git a/src/es_archiver/lib/records/__tests__/filter_records_stream.ts b/src/es_archiver/lib/records/__tests__/filter_records_stream.ts index d5830478decba..f4f9f32e239ea 100644 --- a/src/es_archiver/lib/records/__tests__/filter_records_stream.ts +++ b/src/es_archiver/lib/records/__tests__/filter_records_stream.ts @@ -66,6 +66,6 @@ describe('esArchiver: createFilterRecordsStream()', () => { ]); expect(output).to.have.length(3); - expect(output.map(o => o.type)).to.eql([type1, type1, type1]); + expect(output.map((o) => o.type)).to.eql([type1, type1, type1]); }); }); diff --git a/src/es_archiver/lib/stats.ts b/src/es_archiver/lib/stats.ts index c69b764fc7290..c7b98c42d3efb 100644 --- a/src/es_archiver/lib/stats.ts +++ b/src/es_archiver/lib/stats.ts @@ -101,7 +101,7 @@ export function createStats(name: string, log: ToolingLog) { public createdIndex(index: string, metadata: Record = {}) { getOrCreate(index).created = true; info('Created index %j', index); - Object.keys(metadata).forEach(key => { + Object.keys(metadata).forEach((key) => { debug('%j %s %j', index, key, metadata[key]); }); } @@ -113,7 +113,7 @@ export function createStats(name: string, log: ToolingLog) { public archivedIndex(index: string, metadata: Record = {}) { getOrCreate(index).archived = true; info('Archived %j', index); - Object.keys(metadata).forEach(key => { + Object.keys(metadata).forEach((key) => { debug('%j %s %j', index, key, metadata[key]); }); } @@ -147,7 +147,7 @@ export function createStats(name: string, log: ToolingLog) { */ public forEachIndex(fn: (index: string, stats: IndexStats) => void) { const clone = this.toJSON(); - Object.keys(clone).forEach(index => { + Object.keys(clone).forEach((index) => { fn(index, clone[index]); }); } diff --git a/src/fixtures/agg_resp/geohash_grid.js b/src/fixtures/agg_resp/geohash_grid.js index 98d3128f7eb2c..0e576a88ab36a 100644 --- a/src/fixtures/agg_resp/geohash_grid.js +++ b/src/fixtures/agg_resp/geohash_grid.js @@ -40,14 +40,14 @@ export default function GeoHashGridAggResponseFixture() { _.range(97, 122) // a-z ); - const tags = _.times(_.random(4, 20), function(i) { + const tags = _.times(_.random(4, 20), function (i) { // random number of tags let docCount = 0; - const buckets = _.times(_.random(40, 200), function() { + const buckets = _.times(_.random(40, 200), function () { return _.sample(geoHashCharts, 3).join(''); }) .sort() - .map(function(geoHash) { + .map(function (geoHash) { const count = _.random(1, 5000); docCount += count; diff --git a/src/fixtures/logstash_fields.js b/src/fixtures/logstash_fields.js index 35a059f0c11f6..a824a94dbd939 100644 --- a/src/fixtures/logstash_fields.js +++ b/src/fixtures/logstash_fields.js @@ -52,7 +52,7 @@ function stubbedLogstashFields() { ['script number', 'long', true, false, { script: '1234' }], ['script date', 'date', true, false, { script: '1234', lang: 'painless' }], ['script murmur3', 'murmur3', true, false, { script: '1234' }], - ].map(function(row) { + ].map(function (row) { const [name, esType, aggregatable, searchable, metadata = {}, subType = undefined] = row; const { diff --git a/src/fixtures/mock_index_patterns.js b/src/fixtures/mock_index_patterns.js index 648fb13156816..f14fb02bd1ec7 100644 --- a/src/fixtures/mock_index_patterns.js +++ b/src/fixtures/mock_index_patterns.js @@ -20,7 +20,7 @@ import sinon from 'sinon'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -export default function(Private) { +export default function (Private) { const indexPatterns = Private(FixturesStubbedLogstashIndexPatternProvider); const getIndexPatternStub = sinon.stub().resolves(indexPatterns); diff --git a/src/fixtures/mock_ui_state.js b/src/fixtures/mock_ui_state.js index 42454df2837f7..919274390d4d0 100644 --- a/src/fixtures/mock_ui_state.js +++ b/src/fixtures/mock_ui_state.js @@ -20,24 +20,24 @@ import _ from 'lodash'; let values = {}; export default { - get: function(path, def) { + get: function (path, def) { return _.get(values, path, def); }, - set: function(path, val) { + set: function (path, val) { _.set(values, path, val); return val; }, - setSilent: function(path, val) { + setSilent: function (path, val) { _.set(values, path, val); return val; }, emit: _.noop, on: _.noop, off: _.noop, - clearAllKeys: function() { + clearAllKeys: function () { values = {}; }, - _reset: function() { + _reset: function () { values = {}; }, }; diff --git a/src/fixtures/stubbed_logstash_index_pattern.js b/src/fixtures/stubbed_logstash_index_pattern.js index e20d1b5cd7717..5bb926799fcf6 100644 --- a/src/fixtures/stubbed_logstash_index_pattern.js +++ b/src/fixtures/stubbed_logstash_index_pattern.js @@ -26,7 +26,7 @@ import { npSetup } from '../legacy/ui/public/new_platform/new_platform.karma_moc export default function stubbedLogstashIndexPatternService() { const mockLogstashFields = stubbedLogstashFields(); - const fields = mockLogstashFields.map(function(field) { + const fields = mockLogstashFields.map(function (field) { const kbnType = getKbnFieldType(field.type); if (!kbnType || kbnType.name === 'unknown') { @@ -41,7 +41,13 @@ export default function stubbedLogstashIndexPatternService() { }; }); - const indexPattern = new StubIndexPattern('logstash-*', cfg => cfg, 'time', fields, npSetup.core); + const indexPattern = new StubIndexPattern( + 'logstash-*', + (cfg) => cfg, + 'time', + fields, + npSetup.core + ); indexPattern.id = 'logstash-*'; indexPattern.isTimeNanosBased = () => false; diff --git a/src/fixtures/stubbed_search_source.js b/src/fixtures/stubbed_search_source.js index b444a9a27a6b1..4e43dcf70a7f3 100644 --- a/src/fixtures/stubbed_search_source.js +++ b/src/fixtures/stubbed_search_source.js @@ -30,7 +30,7 @@ export default function stubSearchSource(Private, $q, Promise) { setField: sinon.spy(), fetch: sinon.spy(), destroy: sinon.spy(), - getField: function(param) { + getField: function (param) { switch (param) { case 'index': return indexPattern; @@ -38,11 +38,11 @@ export default function stubSearchSource(Private, $q, Promise) { throw new Error(`Param "${param}" is not implemented in the stubbed search source`); } }, - crankResults: function() { + crankResults: function () { deferedResult.resolve(searchResponse); deferedResult = $q.defer(); }, - onResults: function() { + onResults: function () { onResultsCount++; // Up to the test to resolve this manually @@ -50,10 +50,10 @@ export default function stubSearchSource(Private, $q, Promise) { // someHandler.resolve(require('fixtures/search_response')) return deferedResult.promise; }, - getOnResultsCount: function() { + getOnResultsCount: function () { return onResultsCount; }, - _flatten: function() { + _flatten: function () { return Promise.resolve({ index: indexPattern, body: {} }); }, _requestStartHandlers: [], diff --git a/src/legacy/core_plugins/apm_oss/index.js b/src/legacy/core_plugins/apm_oss/index.js index 5923c1e85ee12..b7ab6797c0de9 100644 --- a/src/legacy/core_plugins/apm_oss/index.js +++ b/src/legacy/core_plugins/apm_oss/index.js @@ -52,7 +52,7 @@ export default function apmOss(kibana) { 'spanIndices', 'metricsIndices', 'onboardingIndices', - ].map(type => server.config().get(`apm_oss.${type}`)) + ].map((type) => server.config().get(`apm_oss.${type}`)) ) ); }, diff --git a/src/legacy/core_plugins/console_legacy/index.ts b/src/legacy/core_plugins/console_legacy/index.ts index af080fd5ace9c..c588b941112d1 100644 --- a/src/legacy/core_plugins/console_legacy/index.ts +++ b/src/legacy/core_plugins/console_legacy/index.ts @@ -28,7 +28,7 @@ export const readLegacyEsConfig = () => { }; // eslint-disable-next-line import/no-default-export -export default function(kibana: any) { +export default function (kibana: any) { return new kibana.Plugin({ id: 'console_legacy', diff --git a/src/legacy/core_plugins/elasticsearch/index.js b/src/legacy/core_plugins/elasticsearch/index.js index 35dd6562aed98..eb502e97fb77c 100644 --- a/src/legacy/core_plugins/elasticsearch/index.js +++ b/src/legacy/core_plugins/elasticsearch/index.js @@ -22,7 +22,7 @@ import { createProxy } from './server/lib/create_proxy'; import { handleESError } from './server/lib/handle_es_error'; import { versionHealthCheck } from './lib/version_health_check'; -export default function(kibana) { +export default function (kibana) { let defaultVars; return new kibana.Plugin({ @@ -34,9 +34,9 @@ export default function(kibana) { // All methods that ES plugin exposes are synchronous so we should get the first // value from all observables here to be able to synchronously return and create // cluster clients afterwards. - const { adminClient, dataClient } = server.newPlatform.setup.core.elasticsearch; - const adminCluster = new Cluster(adminClient); - const dataCluster = new Cluster(dataClient); + const { client } = server.newPlatform.setup.core.elasticsearch.legacy; + const adminCluster = new Cluster(client); + const dataCluster = new Cluster(client); const esConfig = await server.newPlatform.__internals.elasticsearch.legacy.config$ .pipe(first()) @@ -49,7 +49,7 @@ export default function(kibana) { }; const clusters = new Map(); - server.expose('getCluster', name => { + server.expose('getCluster', (name) => { if (name === 'admin') { return adminCluster; } @@ -72,7 +72,7 @@ export default function(kibana) { } const cluster = new Cluster( - server.newPlatform.setup.core.elasticsearch.createClient(name, clientConfig) + server.newPlatform.setup.core.elasticsearch.legacy.createClient(name, clientConfig) ); clusters.set(name, cluster); diff --git a/src/legacy/core_plugins/elasticsearch/integration_tests/elasticsearch.test.ts b/src/legacy/core_plugins/elasticsearch/integration_tests/elasticsearch.test.ts index 5806c31b78414..0331153cdf615 100644 --- a/src/legacy/core_plugins/elasticsearch/integration_tests/elasticsearch.test.ts +++ b/src/legacy/core_plugins/elasticsearch/integration_tests/elasticsearch.test.ts @@ -43,7 +43,7 @@ describe('Elasticsearch plugin', () => { kibanaVersion: '8.0.0', }); - beforeAll(async function() { + beforeAll(async function () { const settings = { elasticsearch: {}, adjustTimeout: (t: any) => { @@ -72,12 +72,12 @@ describe('Elasticsearch plugin', () => { await root.shutdown(); }, 30000); - it("should set it's status to green when all nodes are compatible", done => { + it("should set it's status to green when all nodes are compatible", (done) => { jest.setTimeout(30000); elasticsearch.status.on('green', () => done()); }); - it("should set it's status to red when some nodes aren't compatible", done => { + it("should set it's status to red when some nodes aren't compatible", (done) => { esNodesCompatibility$.next({ isCompatible: false, incompatibleNodes: [], diff --git a/src/legacy/core_plugins/elasticsearch/lib/version_health_check.js b/src/legacy/core_plugins/elasticsearch/lib/version_health_check.js index 4ee8307f490eb..b1a106d2aae5d 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/version_health_check.js +++ b/src/legacy/core_plugins/elasticsearch/lib/version_health_check.js @@ -20,7 +20,7 @@ export const versionHealthCheck = (esPlugin, logWithMetadata, esNodesCompatibility$) => { esPlugin.status.yellow('Waiting for Elasticsearch'); - return new Promise(resolve => { + return new Promise((resolve) => { esNodesCompatibility$.subscribe(({ isCompatible, message, kibanaVersion, warningNodes }) => { if (!isCompatible) { esPlugin.status.red(message); diff --git a/src/legacy/core_plugins/elasticsearch/lib/version_health_check.test.js b/src/legacy/core_plugins/elasticsearch/lib/version_health_check.test.js index ba7c95bcdfec5..4c03c0c0105ee 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/version_health_check.test.js +++ b/src/legacy/core_plugins/elasticsearch/lib/version_health_check.test.js @@ -20,7 +20,7 @@ import { versionHealthCheck } from './version_health_check'; import { Subject } from 'rxjs'; describe('plugins/elasticsearch', () => { - describe('lib/health_version_check', function() { + describe('lib/health_version_check', function () { let plugin; let logWithMetadata; @@ -37,7 +37,7 @@ describe('plugins/elasticsearch', () => { jest.clearAllMocks(); }); - it('returned promise resolves when all nodes are compatible ', function() { + it('returned promise resolves when all nodes are compatible ', function () { const esNodesCompatibility$ = new Subject(); const versionHealthyPromise = versionHealthCheck( plugin, @@ -48,7 +48,7 @@ describe('plugins/elasticsearch', () => { return expect(versionHealthyPromise).resolves.toBe(undefined); }); - it('should set elasticsearch plugin status to green when all nodes are compatible', function() { + it('should set elasticsearch plugin status to green when all nodes are compatible', function () { const esNodesCompatibility$ = new Subject(); versionHealthCheck(plugin, logWithMetadata, esNodesCompatibility$); expect(plugin.status.yellow).toHaveBeenCalledWith('Waiting for Elasticsearch'); @@ -58,7 +58,7 @@ describe('plugins/elasticsearch', () => { expect(plugin.status.red).not.toHaveBeenCalled(); }); - it('should set elasticsearch plugin status to red when some nodes are incompatible', function() { + it('should set elasticsearch plugin status to red when some nodes are incompatible', function () { const esNodesCompatibility$ = new Subject(); versionHealthCheck(plugin, logWithMetadata, esNodesCompatibility$); expect(plugin.status.yellow).toHaveBeenCalledWith('Waiting for Elasticsearch'); diff --git a/src/legacy/core_plugins/elasticsearch/server/lib/__tests__/handle_es_error.js b/src/legacy/core_plugins/elasticsearch/server/lib/__tests__/handle_es_error.js index 9ec4598674d5d..ccab1a3b830b6 100644 --- a/src/legacy/core_plugins/elasticsearch/server/lib/__tests__/handle_es_error.js +++ b/src/legacy/core_plugins/elasticsearch/server/lib/__tests__/handle_es_error.js @@ -21,8 +21,8 @@ import expect from '@kbn/expect'; import { handleESError } from '../handle_es_error'; import { errors as esErrors } from 'elasticsearch'; -describe('handleESError', function() { - it('should transform elasticsearch errors into boom errors with the same status code', function() { +describe('handleESError', function () { + it('should transform elasticsearch errors into boom errors with the same status code', function () { const conflict = handleESError(new esErrors.Conflict()); expect(conflict.isBoom).to.be(true); expect(conflict.output.statusCode).to.be(409); @@ -40,21 +40,19 @@ describe('handleESError', function() { expect(badRequest.output.statusCode).to.be(400); }); - it('should return an unknown error without transforming it', function() { + it('should return an unknown error without transforming it', function () { const unknown = new Error('mystery error'); expect(handleESError(unknown)).to.be(unknown); }); - it('should return a boom 503 server timeout error for ES connection errors', function() { + it('should return a boom 503 server timeout error for ES connection errors', function () { expect(handleESError(new esErrors.ConnectionFault()).output.statusCode).to.be(503); expect(handleESError(new esErrors.ServiceUnavailable()).output.statusCode).to.be(503); expect(handleESError(new esErrors.NoConnections()).output.statusCode).to.be(503); expect(handleESError(new esErrors.RequestTimeout()).output.statusCode).to.be(503); }); - it('should throw an error if called with a non-error argument', function() { - expect(handleESError) - .withArgs('notAnError') - .to.throwException(); + it('should throw an error if called with a non-error argument', function () { + expect(handleESError).withArgs('notAnError').to.throwException(); }); }); diff --git a/src/legacy/core_plugins/elasticsearch/server/lib/create_proxy.js b/src/legacy/core_plugins/elasticsearch/server/lib/create_proxy.js index 85bc57f89e91c..7302241c46939 100644 --- a/src/legacy/core_plugins/elasticsearch/server/lib/create_proxy.js +++ b/src/legacy/core_plugins/elasticsearch/server/lib/create_proxy.js @@ -43,7 +43,7 @@ export function createProxy(server) { body: payload.toString('utf8'), }, { signal } - ).finally(r => h.response(r)); + ).finally((r) => h.response(r)); }), }); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 5807c439bd277..ae613e0e80904 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -26,28 +26,21 @@ import { exportApi } from './server/routes/api/export'; import { getUiSettingDefaults } from './server/ui_setting_defaults'; import { registerCspCollector } from './server/lib/csp_usage_collector'; import { injectVars } from './inject_vars'; -import { i18n } from '@kbn/i18n'; -import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; + import { kbnBaseUrl } from '../../../plugins/kibana_legacy/server'; const mkdirAsync = promisify(Fs.mkdir); -export default function(kibana) { +export default function (kibana) { return new kibana.Plugin({ id: 'kibana', - config: function(Joi) { + config: function (Joi) { return Joi.object({ enabled: Joi.boolean().default(true), index: Joi.string().default('.kibana'), - autocompleteTerminateAfter: Joi.number() - .integer() - .min(1) - .default(100000), + autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000), // TODO Also allow units here like in elasticsearch config once this is moved to the new platform - autocompleteTimeout: Joi.number() - .integer() - .min(1) - .default(1000), + autocompleteTimeout: Joi.number().integer().min(1).default(1000), }).default(); }, @@ -59,19 +52,7 @@ export default function(kibana) { main: 'plugins/kibana/kibana', }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), - links: [ - { - id: 'kibana:stack_management', - title: i18n.translate('kbn.managementTitle', { - defaultMessage: 'Stack Management', - }), - order: 9003, - url: `${kbnBaseUrl}#/management`, - euiIconType: 'managementApp', - linkToLastSubUrl: false, - category: DEFAULT_APP_CATEGORIES.management, - }, - ], + links: [], injectDefaultVars(server, options) { const mapConfig = server.config().get('map'); @@ -96,7 +77,7 @@ export default function(kibana) { uiSettingDefaults: getUiSettingDefaults(), }, - preInit: async function(server) { + preInit: async function (server) { try { // Create the data directory (recursively, if the a parent dir doesn't exist). // If it already exists, does nothing. @@ -108,7 +89,7 @@ export default function(kibana) { } }, - init: async function(server) { + init: async function (server) { const { usageCollection } = server.newPlatform.setup.plugins; // routes importApi(server); diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc.js b/src/legacy/core_plugins/kibana/public/.eslintrc.js deleted file mode 100644 index 1153706eb8566..0000000000000 --- a/src/legacy/core_plugins/kibana/public/.eslintrc.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const topLevelConfig = require('../../../../../.eslintrc.js'); -const path = require('path'); - -const topLevelRestricedZones = topLevelConfig.overrides.find( - override => - override.files[0] === '**/*.{js,ts,tsx}' && - Object.keys(override.rules)[0] === '@kbn/eslint/no-restricted-paths' -).rules['@kbn/eslint/no-restricted-paths'][1].zones; - -/** - * Builds custom restricted paths configuration for the shimmed plugins within the kibana plugin. - * These custom rules extend the default checks in the top level `eslintrc.js` by also checking two other things: - * * Making sure nothing within np_ready imports from the `ui` directory - * * Making sure no other code is importing things deep from within the shimmed plugins - * @param shimmedPlugins List of plugin names within the kibana plugin that are partially np ready - * @returns zones configuration for the no-restricted-paths linter - */ -function buildRestrictedPaths(shimmedPlugins) { - return shimmedPlugins - .map(shimmedPlugin => [ - { - target: [`src/legacy/core_plugins/kibana/public/${shimmedPlugin}/np_ready/**/*`], - from: [ - 'ui/**/*', - 'src/legacy/ui/**/*', - 'src/legacy/core_plugins/kibana/public/**/*', - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, - ], - allowSameFolder: false, - errorMessage: `${shimmedPlugin} is a shimmed plugin that is not allowed to import modules from the legacy platform. If you need legacy modules for the transition period, import them either in the legacy_imports, kibana_services or index module.`, - }, - { - target: [ - 'src/**/*', - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, - 'x-pack/**/*', - ], - from: [ - `src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/index.ts`, - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/legacy.ts`, - ], - allowSameFolder: false, - errorMessage: `kibana/public/${shimmedPlugin} is behaving like a NP plugin and does not allow deep imports. If you need something from within ${shimmedPlugin} in another plugin, consider re-exporting it from the top level index module`, - }, - ]) - .reduce((acc, part) => [...acc, ...part], []); -} - -module.exports = { - rules: { - 'no-console': 2, - 'import/no-default-export': 'error', - '@kbn/eslint/no-restricted-paths': [ - 'error', - { - basePath: path.resolve(__dirname, '../../../../../'), - zones: topLevelRestricedZones.concat( - buildRestrictedPaths(['visualize', 'discover', 'dashboard', 'devTools']) - ), - }, - ], - }, -}; diff --git a/src/legacy/core_plugins/kibana/public/__tests__/discover/doc_table.js b/src/legacy/core_plugins/kibana/public/__tests__/discover/doc_table.js index e4ad8a5638fd1..504b00808718b 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/discover/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/discover/doc_table.js @@ -35,8 +35,8 @@ let $timeout; let indexPattern; -const init = function($elem, props) { - ngMock.inject(function($rootScope, $compile, _$timeout_) { +const init = function ($elem, props) { + ngMock.inject(function ($rootScope, $compile, _$timeout_) { $timeout = _$timeout_; $parentScope = $rootScope; _.assign($parentScope, props); @@ -44,7 +44,7 @@ const init = function($elem, props) { $compile($elem)($parentScope); // I think the prereq requires this? - $timeout(function() { + $timeout(function () { $elem.scope().$digest(); }, 0); @@ -52,19 +52,19 @@ const init = function($elem, props) { }); }; -const destroy = function() { +const destroy = function () { $scope.$destroy(); $parentScope.$destroy(); }; -describe('docTable', function() { +describe('docTable', function () { let $elem; before(() => setScopedHistory(createBrowserHistory())); beforeEach(() => pluginInstance.initializeInnerAngular()); beforeEach(() => pluginInstance.initializeServices()); beforeEach(ngMock.module('app/discover')); - beforeEach(function() { + beforeEach(function () { $elem = angular.element(` `); - ngMock.inject(function(Private) { + ngMock.inject(function (Private) { indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); }); init($elem, { @@ -87,15 +87,15 @@ describe('docTable', function() { $scope.$digest(); }); - afterEach(function() { + afterEach(function () { destroy(); }); - it('should compile', function() { + it('should compile', function () { expect($elem.text()).to.not.be.empty(); }); - it('should have an addRows function that increases the row count', function() { + it('should have an addRows function that increases the row count', function () { expect($scope.addRows).to.be.a(Function); $scope.$digest(); expect($scope.limit).to.be(50); @@ -103,7 +103,7 @@ describe('docTable', function() { expect($scope.limit).to.be(100); }); - it('should reset the row limit when results are received', function() { + it('should reset the row limit when results are received', function () { $scope.limit = 100; expect($scope.limit).to.be(100); $scope.hits = [...hits]; @@ -111,7 +111,7 @@ describe('docTable', function() { expect($scope.limit).to.be(50); }); - it('should have a header and a table element', function() { + it('should have a header and a table element', function () { $scope.$digest(); expect($elem.find('thead').length).to.be(1); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/discover/fixed_scroll.js b/src/legacy/core_plugins/kibana/public/__tests__/discover/fixed_scroll.js index 4a8736cc0d6a4..9bb0ebc76474d 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/discover/fixed_scroll.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/discover/fixed_scroll.js @@ -37,7 +37,7 @@ angular .service('debounce', ['$timeout', DebounceProviderTimeout]) .directive('fixedScroll', FixedScrollProvider); -describe('FixedScroll directive', function() { +describe('FixedScroll directive', function () { const sandbox = sinon.createSandbox(); let compile; @@ -45,13 +45,13 @@ describe('FixedScroll directive', function() { const trash = []; beforeEach(ngMock.module(testModuleName)); beforeEach( - ngMock.inject(function($compile, $rootScope, $timeout) { + ngMock.inject(function ($compile, $rootScope, $timeout) { flushPendingTasks = function flushPendingTasks() { $rootScope.$digest(); $timeout.flush(); }; - compile = function(ratioY, ratioX) { + compile = function (ratioY, ratioX) { if (ratioX == null) ratioX = ratioY; // since the directive works at the sibling level we create a @@ -93,15 +93,15 @@ describe('FixedScroll directive', function() { }) ); - afterEach(function() { - trash.splice(0).forEach(function($el) { + afterEach(function () { + trash.splice(0).forEach(function ($el) { $el.remove(); }); sandbox.restore(); }); - it('does nothing when not needed', function() { + it('does nothing when not needed', function () { let els = compile(0.5, 1.5); expect(els.$scroller).to.have.length(0); @@ -109,23 +109,23 @@ describe('FixedScroll directive', function() { expect(els.$scroller).to.have.length(0); }); - it('attaches a scroller below the element when the content is larger then the container', function() { + it('attaches a scroller below the element when the content is larger then the container', function () { const els = compile(1.5); expect(els.$scroller).to.have.length(1); }); - it('copies the width of the container', function() { + it('copies the width of the container', function () { const els = compile(1.5); expect(els.$scroller.width()).to.be(els.$container.width()); }); - it('mimics the scrollWidth of the element', function() { + it('mimics the scrollWidth of the element', function () { const els = compile(1.5); expect(els.$scroller.prop('scrollWidth')).to.be(els.$container.prop('scrollWidth')); }); - describe('scroll event handling / tug of war prevention', function() { - it('listens when needed, unlistens when not needed', function() { + describe('scroll event handling / tug of war prevention', function () { + it('listens when needed, unlistens when not needed', function () { const on = sandbox.spy($.fn, 'on'); const off = sandbox.spy($.fn, 'off'); @@ -143,7 +143,7 @@ describe('FixedScroll directive', function() { // the this values should be different expect(spy.thisValues[0].is(spy.thisValues[1])).to.be(false); // but they should be either $scroller or $container - spy.thisValues.forEach(function($this) { + spy.thisValues.forEach(function ($this) { if ($this.is(els.$scroller) || $this.is(els.$container)) return; expect.fail('expected ' + name + ' to be called with $scroller or $container'); }); @@ -153,21 +153,21 @@ describe('FixedScroll directive', function() { [ { from: '$container', to: '$scroller' }, { from: '$scroller', to: '$container' }, - ].forEach(function(names) { - describe('scroll events ' + JSON.stringify(names), function() { + ].forEach(function (names) { + describe('scroll events ' + JSON.stringify(names), function () { let spy; let els; let $from; let $to; - beforeEach(function() { + beforeEach(function () { spy = sandbox.spy($.fn, 'scrollLeft'); els = compile(1.5); $from = els[names.from]; $to = els[names.to]; }); - it('transfers the scrollLeft', function() { + it('transfers the scrollLeft', function () { expect(spy.callCount).to.be(0); $from.scroll(); expect(spy.callCount).to.be(2); @@ -188,7 +188,7 @@ describe('FixedScroll directive', function() { * but the browser seems to be very careful about triggering the event too much * and I can't reliably recreate the browsers behavior in a test. So... faking it! */ - it('prevents tug of war by ignoring echo scroll events', function() { + it('prevents tug of war by ignoring echo scroll events', function () { $from.scroll(); expect(spy.callCount).to.be(2); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/discover/row_headers.js b/src/legacy/core_plugins/kibana/public/__tests__/discover/row_headers.js index 2a34ace8f1312..29c301bf065c4 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/discover/row_headers.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/discover/row_headers.js @@ -28,7 +28,7 @@ import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logsta import { setScopedHistory } from '../../../../../../plugins/discover/public/kibana_services'; import { createBrowserHistory } from 'history'; -describe('Doc Table', function() { +describe('Doc Table', function () { let $parentScope; let $scope; @@ -42,7 +42,7 @@ describe('Doc Table', function() { before(() => setScopedHistory(createBrowserHistory())); beforeEach(ngMock.module('app/discover')); beforeEach( - ngMock.inject(function($rootScope, Private) { + ngMock.inject(function ($rootScope, Private) { $parentScope = $rootScope; $parentScope.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); mapping = $parentScope.indexPattern.fields; @@ -50,7 +50,7 @@ describe('Doc Table', function() { // Stub `getConverterFor` for a field in the indexPattern to return mock data. // Returns `val` if provided, otherwise generates fake data for the field. fakeRowVals = getFakeRowVals('formatted', 0, mapping); - stubFieldFormatConverter = function($root, field, val) { + stubFieldFormatConverter = function ($root, field, val) { const convertFn = (value, type, options) => { if (val) { return val; @@ -67,8 +67,8 @@ describe('Doc Table', function() { ); // Sets up the directive, take an element, and a list of properties to attach to the parent scope. - const init = function($elem, props) { - ngMock.inject(function($compile) { + const init = function ($elem, props) { + ngMock.inject(function ($compile) { _.assign($parentScope, props); $compile($elem)($parentScope); $elem.scope().$digest(); @@ -76,19 +76,19 @@ describe('Doc Table', function() { }); }; - const destroy = function() { + const destroy = function () { $scope.$destroy(); $parentScope.$destroy(); }; // For testing column removing/adding for the header and the rows - const columnTests = function(elemType, parentElem) { - it('should create a time column if the timefield is defined', function() { + const columnTests = function (elemType, parentElem) { + it('should create a time column if the timefield is defined', function () { const childElems = parentElem.find(elemType); expect(childElems.length).to.be(1); }); - it('should be able to add and remove columns', function() { + it('should be able to add and remove columns', function () { let childElems; stubFieldFormatConverter($parentScope, 'bytes'); @@ -114,7 +114,7 @@ describe('Doc Table', function() { expect($(childElems[1]).text()).to.contain('request_body'); }); - it('should create only the toggle column if there is no timeField', function() { + it('should create only the toggle column if there is no timeField', function () { delete parentElem.scope().indexPattern.timeFieldName; parentElem.scope().$digest(); @@ -123,7 +123,7 @@ describe('Doc Table', function() { }); }; - describe('kbnTableRow', function() { + describe('kbnTableRow', function () { const $elem = angular.element( ' stubFieldFormatConverter($root, f.name)); + $root.indexPattern.fields.forEach((f) => stubFieldFormatConverter($root, f.name)); $row = $('').attr({ 'kbn-table-row': 'row', @@ -262,26 +262,16 @@ describe('Doc Table', function() { $before = $row.find('td'); expect($before).to.have.length(3); - expect( - $before - .eq(0) - .text() - .trim() - ).to.be(''); - expect( - $before - .eq(1) - .text() - .trim() - ).to.match(/^time_formatted/); + expect($before.eq(0).text().trim()).to.be(''); + expect($before.eq(1).text().trim()).to.match(/^time_formatted/); }) ); - afterEach(function() { + afterEach(function () { $row.remove(); }); - it('handles a new column', function() { + it('handles a new column', function () { $root.columns.push('bytes'); $root.$apply(); @@ -290,15 +280,10 @@ describe('Doc Table', function() { expect($after[0]).to.be($before[0]); expect($after[1]).to.be($before[1]); expect($after[2]).to.be($before[2]); - expect( - $after - .eq(3) - .text() - .trim() - ).to.match(/^bytes_formatted/); + expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/); }); - it('handles two new columns at once', function() { + it('handles two new columns at once', function () { $root.columns.push('bytes'); $root.columns.push('request_body'); $root.$apply(); @@ -308,21 +293,11 @@ describe('Doc Table', function() { expect($after[0]).to.be($before[0]); expect($after[1]).to.be($before[1]); expect($after[2]).to.be($before[2]); - expect( - $after - .eq(3) - .text() - .trim() - ).to.match(/^bytes_formatted/); - expect( - $after - .eq(4) - .text() - .trim() - ).to.match(/^request_body_formatted/); + expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/); + expect($after.eq(4).text().trim()).to.match(/^request_body_formatted/); }); - it('handles three new columns in odd places', function() { + it('handles three new columns in odd places', function () { $root.columns = ['@timestamp', 'bytes', '_source', 'request_body']; $root.$apply(); @@ -330,28 +305,13 @@ describe('Doc Table', function() { expect($after).to.have.length(6); expect($after[0]).to.be($before[0]); expect($after[1]).to.be($before[1]); - expect( - $after - .eq(2) - .text() - .trim() - ).to.match(/^@timestamp_formatted/); - expect( - $after - .eq(3) - .text() - .trim() - ).to.match(/^bytes_formatted/); + expect($after.eq(2).text().trim()).to.match(/^@timestamp_formatted/); + expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/); expect($after[4]).to.be($before[2]); - expect( - $after - .eq(5) - .text() - .trim() - ).to.match(/^request_body_formatted/); + expect($after.eq(5).text().trim()).to.match(/^request_body_formatted/); }); - it('handles a removed column', function() { + it('handles a removed column', function () { _.pull($root.columns, '_source'); $root.$apply(); @@ -361,7 +321,7 @@ describe('Doc Table', function() { expect($after[1]).to.be($before[1]); }); - it('handles two removed columns', function() { + it('handles two removed columns', function () { // first add a column $root.columns.push('@timestamp'); $root.$apply(); @@ -379,7 +339,7 @@ describe('Doc Table', function() { expect($after[1]).to.be($before[1]); }); - it('handles three removed random columns', function() { + it('handles three removed random columns', function () { // first add two column $root.columns.push('@timestamp', 'bytes'); $root.$apply(); @@ -396,15 +356,10 @@ describe('Doc Table', function() { expect($after).to.have.length(3); expect($after[0]).to.be($before[0]); expect($after[1]).to.be($before[1]); - expect( - $after - .eq(2) - .text() - .trim() - ).to.match(/^@timestamp_formatted/); + expect($after.eq(2).text().trim()).to.match(/^@timestamp_formatted/); }); - it('handles two columns with the same content', function() { + it('handles two columns with the same content', function () { stubFieldFormatConverter($root, 'request_body', fakeRowVals.bytes); $root.columns.length = 0; @@ -414,21 +369,11 @@ describe('Doc Table', function() { const $after = $row.find('td'); expect($after).to.have.length(4); - expect( - $after - .eq(2) - .text() - .trim() - ).to.match(/^bytes_formatted/); - expect( - $after - .eq(3) - .text() - .trim() - ).to.match(/^bytes_formatted/); + expect($after.eq(2).text().trim()).to.match(/^bytes_formatted/); + expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/); }); - it('handles two columns swapping position', function() { + it('handles two columns swapping position', function () { $root.columns.push('bytes'); $root.$apply(); @@ -446,7 +391,7 @@ describe('Doc Table', function() { expect($after[3]).to.be($mid[2]); }); - it('handles four columns all reversing position', function() { + it('handles four columns all reversing position', function () { $root.columns.push('bytes', 'response', '@timestamp'); $root.$apply(); @@ -466,7 +411,7 @@ describe('Doc Table', function() { expect($after[5]).to.be($mid[2]); }); - it('handles multiple columns with the same name', function() { + it('handles multiple columns with the same name', function () { $root.columns.push('bytes', 'bytes', 'bytes'); $root.$apply(); @@ -475,24 +420,9 @@ describe('Doc Table', function() { expect($after[0]).to.be($before[0]); expect($after[1]).to.be($before[1]); expect($after[2]).to.be($before[2]); - expect( - $after - .eq(3) - .text() - .trim() - ).to.match(/^bytes_formatted/); - expect( - $after - .eq(4) - .text() - .trim() - ).to.match(/^bytes_formatted/); - expect( - $after - .eq(5) - .text() - .trim() - ).to.match(/^bytes_formatted/); + expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/); + expect($after.eq(4).text().trim()).to.match(/^bytes_formatted/); + expect($after.eq(5).text().trim()).to.match(/^bytes_formatted/); }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js index de85bec011eeb..88eb299e3c3a8 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js @@ -35,7 +35,7 @@ import { tabifiedData } from './tabified_data'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { configureAppAngularModule } from '../../../../../../plugins/kibana_legacy/public/angular'; -describe('Table Vis - AggTable Directive', function() { +describe('Table Vis - AggTable Directive', function () { let $rootScope; let $compile; let settings; @@ -50,7 +50,7 @@ describe('Table Vis - AggTable Directive', function() { beforeEach(ngMock.module('kibana/table_vis')); beforeEach( - ngMock.inject(function($injector, config) { + ngMock.inject(function ($injector, config) { settings = config; $rootScope = $injector.get('$rootScope'); @@ -59,14 +59,14 @@ describe('Table Vis - AggTable Directive', function() { ); let $scope; - beforeEach(function() { + beforeEach(function () { $scope = $rootScope.$new(); }); - afterEach(function() { + afterEach(function () { $scope.$destroy(); }); - it('renders a simple response properly', function() { + it('renders a simple response properly', function () { $scope.dimensions = { metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], buckets: [], @@ -83,7 +83,7 @@ describe('Table Vis - AggTable Directive', function() { expect($el.find('td').text()).to.eql('1,000'); }); - it('renders nothing if the table is empty', function() { + it('renders nothing if the table is empty', function () { $scope.dimensions = {}; $scope.table = null; const $el = $compile('')( @@ -94,7 +94,7 @@ describe('Table Vis - AggTable Directive', function() { expect($el.find('tbody').length).to.be(0); }); - it('renders a complex response properly', async function() { + it('renders a complex response properly', async function () { $scope.dimensions = { buckets: [ { accessor: 0, params: {} }, @@ -124,15 +124,13 @@ describe('Table Vis - AggTable Directive', function() { } } - $rows.each(function() { + $rows.each(function () { // 6 cells in every row const $cells = $(this).find('td'); expect($cells.length).to.be(6); - const txts = $cells.map(function() { - return $(this) - .text() - .trim(); + const txts = $cells.map(function () { + return $(this).text().trim(); }); // two character country code @@ -149,7 +147,7 @@ describe('Table Vis - AggTable Directive', function() { }); }); - describe('renders totals row', function() { + describe('renders totals row', function () { async function totalsRowTest(totalFunc, expected) { function setDefaultTimezone() { moment.tz.setDefault(settings.get('dateFormat:tz')); @@ -192,19 +190,15 @@ describe('Table Vis - AggTable Directive', function() { expect($cells.length).to.be(6); for (let i = 0; i < 6; i++) { - expect( - $($cells[i]) - .text() - .trim() - ).to.be(expected[i]); + expect($($cells[i]).text().trim()).to.be(expected[i]); } settings.set('dateFormat:tz', oldTimezoneSetting); off(); } - it('as count', async function() { + it('as count', async function () { await totalsRowTest('count', ['18', '18', '18', '18', '18', '18']); }); - it('as min', async function() { + it('as min', async function () { await totalsRowTest('min', [ '', '2014-09-28', @@ -214,7 +208,7 @@ describe('Table Vis - AggTable Directive', function() { '11', ]); }); - it('as max', async function() { + it('as max', async function () { await totalsRowTest('max', [ '', '2014-10-03', @@ -224,16 +218,16 @@ describe('Table Vis - AggTable Directive', function() { '837', ]); }); - it('as avg', async function() { + it('as avg', async function () { await totalsRowTest('avg', ['', '', '87,221.5', '', '64.667', '206.833']); }); - it('as sum', async function() { + it('as sum', async function () { await totalsRowTest('sum', ['', '', '1,569,987', '', '1,164', '3,723']); }); }); - describe('aggTable.toCsv()', function() { - it('escapes rows and columns properly', function() { + describe('aggTable.toCsv()', function () { + it('escapes rows and columns properly', function () { const $el = $compile('')( $scope ); @@ -255,7 +249,7 @@ describe('Table Vis - AggTable Directive', function() { ); }); - it('exports rows and columns properly', async function() { + it('exports rows and columns properly', async function () { $scope.dimensions = { buckets: [ { accessor: 0, params: {} }, @@ -283,34 +277,34 @@ describe('Table Vis - AggTable Directive', function() { expect(raw).to.be( '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + '\r\n' + - 'png,IT,win,412032,9299,0' + + 'png,412032,IT,9299,win,0' + '\r\n' + - 'png,IT,mac,412032,9299,9299' + + 'png,412032,IT,9299,mac,9299' + '\r\n' + - 'png,US,linux,412032,8293,3992' + + 'png,412032,US,8293,linux,3992' + '\r\n' + - 'png,US,mac,412032,8293,3029' + + 'png,412032,US,8293,mac,3029' + '\r\n' + - 'css,MX,win,412032,9299,4992' + + 'css,412032,MX,9299,win,4992' + '\r\n' + - 'css,MX,mac,412032,9299,5892' + + 'css,412032,MX,9299,mac,5892' + '\r\n' + - 'css,US,linux,412032,8293,3992' + + 'css,412032,US,8293,linux,3992' + '\r\n' + - 'css,US,mac,412032,8293,3029' + + 'css,412032,US,8293,mac,3029' + '\r\n' + - 'html,CN,win,412032,9299,4992' + + 'html,412032,CN,9299,win,4992' + '\r\n' + - 'html,CN,mac,412032,9299,5892' + + 'html,412032,CN,9299,mac,5892' + '\r\n' + - 'html,FR,win,412032,8293,3992' + + 'html,412032,FR,8293,win,3992' + '\r\n' + - 'html,FR,mac,412032,8293,3029' + + 'html,412032,FR,8293,mac,3029' + '\r\n' ); }); - it('exports formatted rows and columns properly', async function() { + it('exports formatted rows and columns properly', async function () { $scope.dimensions = { buckets: [ { accessor: 0, params: {} }, @@ -335,41 +329,41 @@ describe('Table Vis - AggTable Directive', function() { $tableScope.table = $scope.table; // Create our own converter since the ones we use for tests don't actually transform the provided value - $tableScope.formattedColumns[0].formatter.convert = v => `${v}_formatted`; + $tableScope.formattedColumns[0].formatter.convert = (v) => `${v}_formatted`; const formatted = aggTable.toCsv(true); expect(formatted).to.be( '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + '\r\n' + - '"png_formatted",IT,win,412032,9299,0' + + '"png_formatted",412032,IT,9299,win,0' + '\r\n' + - '"png_formatted",IT,mac,412032,9299,9299' + + '"png_formatted",412032,IT,9299,mac,9299' + '\r\n' + - '"png_formatted",US,linux,412032,8293,3992' + + '"png_formatted",412032,US,8293,linux,3992' + '\r\n' + - '"png_formatted",US,mac,412032,8293,3029' + + '"png_formatted",412032,US,8293,mac,3029' + '\r\n' + - '"css_formatted",MX,win,412032,9299,4992' + + '"css_formatted",412032,MX,9299,win,4992' + '\r\n' + - '"css_formatted",MX,mac,412032,9299,5892' + + '"css_formatted",412032,MX,9299,mac,5892' + '\r\n' + - '"css_formatted",US,linux,412032,8293,3992' + + '"css_formatted",412032,US,8293,linux,3992' + '\r\n' + - '"css_formatted",US,mac,412032,8293,3029' + + '"css_formatted",412032,US,8293,mac,3029' + '\r\n' + - '"html_formatted",CN,win,412032,9299,4992' + + '"html_formatted",412032,CN,9299,win,4992' + '\r\n' + - '"html_formatted",CN,mac,412032,9299,5892' + + '"html_formatted",412032,CN,9299,mac,5892' + '\r\n' + - '"html_formatted",FR,win,412032,8293,3992' + + '"html_formatted",412032,FR,8293,win,3992' + '\r\n' + - '"html_formatted",FR,mac,412032,8293,3029' + + '"html_formatted",412032,FR,8293,mac,3029' + '\r\n' ); }); }); - it('renders percentage columns', async function() { + it('renders percentage columns', async function () { $scope.dimensions = { buckets: [ { accessor: 0, params: {} }, @@ -397,22 +391,12 @@ describe('Table Vis - AggTable Directive', function() { const $headings = $el.find('th'); expect($headings.length).to.be(7); - expect( - $headings - .eq(3) - .text() - .trim() - ).to.be('Average bytes percentages'); - - const countColId = $scope.table.columns.find(col => col.name === $scope.percentageCol).id; - const counts = $scope.table.rows.map(row => row[countColId]); + expect($headings.eq(3).text().trim()).to.be('Average bytes percentages'); + + const countColId = $scope.table.columns.find((col) => col.name === $scope.percentageCol).id; + const counts = $scope.table.rows.map((row) => row[countColId]); const total = counts.reduce((sum, curr) => sum + curr, 0); - const $percentageColValues = $el.find('tbody tr').map((i, el) => - $(el) - .find('td') - .eq(3) - .text() - ); + const $percentageColValues = $el.find('tbody tr').map((i, el) => $(el).find('td').eq(3).text()); $percentageColValues.each((i, value) => { const percentage = `${round((counts[i] / total) * 100, 3)}%`; @@ -420,23 +404,23 @@ describe('Table Vis - AggTable Directive', function() { }); }); - describe('aggTable.exportAsCsv()', function() { + describe('aggTable.exportAsCsv()', function () { let origBlob; function FakeBlob(slices, opts) { this.slices = slices; this.opts = opts; } - beforeEach(function() { + beforeEach(function () { origBlob = window.Blob; window.Blob = FakeBlob; }); - afterEach(function() { + afterEach(function () { window.Blob = origBlob; }); - it('calls _saveAs properly', function() { + it('calls _saveAs properly', function () { const $el = $compile('')($scope); $scope.$digest(); @@ -468,7 +452,7 @@ describe('Table Vis - AggTable Directive', function() { expect(call.args[1]).to.be('somefilename.csv'); }); - it('should use the export-title attribute', function() { + it('should use the export-title attribute', function () { const expected = 'export file name'; const $el = $compile( `` diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js index 3cd7de393d66a..99b397167009d 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js @@ -31,7 +31,7 @@ import { npStart } from 'ui/new_platform'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { configureAppAngularModule } from '../../../../../../plugins/kibana_legacy/public/angular'; -describe('Table Vis - AggTableGroup Directive', function() { +describe('Table Vis - AggTableGroup Directive', function () { let $rootScope; let $compile; @@ -45,21 +45,21 @@ describe('Table Vis - AggTableGroup Directive', function() { beforeEach(ngMock.module('kibana/table_vis')); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $rootScope = $injector.get('$rootScope'); $compile = $injector.get('$compile'); }) ); let $scope; - beforeEach(function() { + beforeEach(function () { $scope = $rootScope.$new(); }); - afterEach(function() { + afterEach(function () { $scope.$destroy(); }); - it('renders a simple split response properly', function() { + it('renders a simple split response properly', function () { $scope.dimensions = { metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], buckets: [], @@ -80,7 +80,7 @@ describe('Table Vis - AggTableGroup Directive', function() { expect($el.find('kbn-agg-table').length).to.be(1); }); - it('renders nothing if the table list is empty', function() { + it('renders nothing if the table list is empty', function () { const $el = $( '' ); @@ -96,7 +96,7 @@ describe('Table Vis - AggTableGroup Directive', function() { expect($subTables.length).to.be(0); }); - it('renders a complex response properly', function() { + it('renders a complex response properly', function () { $scope.dimensions = { splitRow: [{ accessor: 0, params: {} }], buckets: [ @@ -122,7 +122,7 @@ describe('Table Vis - AggTableGroup Directive', function() { const $subTableHeaders = $el.find('.kbnAggTable__groupHeader'); expect($subTableHeaders.length).to.be(3); - $subTableHeaders.each(function(i) { + $subTableHeaders.each(function (i) { expect($(this).text()).to.be(group.tables[i].title); }); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js index 8f08f6a1f37e6..35c7b77687b94 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js @@ -32,7 +32,7 @@ import { seedColors } from '../../../../../../plugins/charts/public/services/col // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TagCloud } from '../../../../../../plugins/vis_type_tagcloud/public/components/tag_cloud'; -describe('tag cloud tests', function() { +describe('tag cloud tests', function () { const minValue = 1; const maxValue = 9; const midValue = (minValue + maxValue) / 2; @@ -126,30 +126,30 @@ describe('tag cloud tests', function() { sqrtScaleTest, biggerFontTest, trimDataTest, - ].forEach(function(test) { + ].forEach(function (test) { describe(`should position elements correctly for options: ${JSON.stringify( test.options - )}`, function() { - beforeEach(async function() { + )}`, function () { + beforeEach(async function () { setupDOM(); tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(test.data); tagCloud.setOptions(test.options); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); it( 'completeness should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); }) ); it( 'positions should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(test.expected, textElements, tagCloud); }) @@ -157,9 +157,9 @@ describe('tag cloud tests', function() { }); }); - [5, 100, 200, 300, 500].forEach(function(timeout) { - describe(`should only send single renderComplete event at the very end, using ${timeout}ms timeout`, function() { - beforeEach(async function() { + [5, 100, 200, 300, 500].forEach(function (timeout) { + describe(`should only send single renderComplete event at the very end, using ${timeout}ms timeout`, function () { + beforeEach(async function () { setupDOM(); //TagCloud takes at least 600ms to complete (due to d3 animation) @@ -171,21 +171,21 @@ describe('tag cloud tests', function() { //this timeout modifies the settings before the cloud is rendered. //the cloud needs to use the correct options setTimeout(() => tagCloud.setOptions(logScaleTest.options), timeout); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); it( 'completeness should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); }) ); it( 'positions should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(logScaleTest.expected, textElements, tagCloud); }) @@ -193,63 +193,63 @@ describe('tag cloud tests', function() { }); }); - describe('should use the latest state before notifying (when modifying options multiple times)', function() { - beforeEach(async function() { + describe('should use the latest state before notifying (when modifying options multiple times)', function () { + beforeEach(async function () { setupDOM(); tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); tagCloud.setOptions(logScaleTest.options); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); it( 'completeness should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); }) ); it( 'positions should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(logScaleTest.expected, textElements, tagCloud); }) ); }); - describe('should use the latest state before notifying (when modifying data multiple times)', function() { - beforeEach(async function() { + describe('should use the latest state before notifying (when modifying data multiple times)', function () { + beforeEach(async function () { setupDOM(); tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); tagCloud.setData(trimDataTest.data); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); it( 'completeness should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); }) ); it( 'positions should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(trimDataTest.expected, textElements, tagCloud); }) ); }); - describe('should not get multiple render-events', function() { + describe('should not get multiple render-events', function () { let counter; - beforeEach(function() { + beforeEach(function () { counter = 0; setupDOM(); return new Promise((resolve, reject) => { @@ -283,21 +283,21 @@ describe('tag cloud tests', function() { it( 'completeness should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); }) ); it( 'positions should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(logScaleTest.expected, textElements, tagCloud); }) ); }); - describe('should show correct data when state-updates are interleaved with resize event', function() { - beforeEach(async function() { + describe('should show correct data when state-updates are interleaved with resize event', function () { + beforeEach(async function () { setupDOM(); tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(logScaleTest.data); @@ -312,43 +312,43 @@ describe('tag cloud tests', function() { tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); }, 200); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); it( 'completeness should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); }) ); it( 'positions should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(baseTest.expected, textElements, tagCloud); }) ); }); - describe(`should not put elements in view when container is too small`, function() { - beforeEach(async function() { + describe(`should not put elements in view when container is too small`, function () { + beforeEach(async function () { setupDOM(); domNode.style.width = '1px'; domNode.style.height = '1px'; tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); - it('completeness should not be ok', function() { + it('completeness should not be ok', function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.INCOMPLETE); }); - it('positions should not be ok', function() { + it('positions should not be ok', function () { const textElements = domNode.querySelectorAll('text'); for (let i = 0; i < textElements; i++) { const bbox = textElements[i].getBoundingClientRect(); @@ -357,8 +357,8 @@ describe('tag cloud tests', function() { }); }); - describe(`tags should fit after making container bigger`, function() { - beforeEach(async function() { + describe(`tags should fit after making container bigger`, function () { + beforeEach(async function () { setupDOM(); domNode.style.width = '1px'; domNode.style.height = '1px'; @@ -366,50 +366,50 @@ describe('tag cloud tests', function() { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); //make bigger domNode.style.width = '512px'; domNode.style.height = '512px'; tagCloud.resize(); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); it( 'completeness should be ok', - handleExpectedBlip(function() { + handleExpectedBlip(function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); }) ); }); - describe(`tags should no longer fit after making container smaller`, function() { - beforeEach(async function() { + describe(`tags should no longer fit after making container smaller`, function () { + beforeEach(async function () { setupDOM(); tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); //make smaller domNode.style.width = '1px'; domNode.style.height = '1px'; tagCloud.resize(); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); - it('completeness should not be ok', function() { + it('completeness should not be ok', function () { expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.INCOMPLETE); }); }); - describe('tagcloudscreenshot', function() { + describe('tagcloudscreenshot', function () { let imageComparator; - beforeEach(async function() { + beforeEach(async function () { setupDOM(); imageComparator = new ImageComparator(); }); @@ -419,12 +419,12 @@ describe('tag cloud tests', function() { teardownDOM(); }); - it('should render simple image', async function() { + it('should render simple image', async function () { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); - await fromNode(cb => tagCloud.once('renderComplete', cb)); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); const mismatchedPixels = await imageComparator.compareDOMContents( domNode.innerHTML, @@ -522,7 +522,7 @@ describe('tag cloud tests', function() { const centered = largest[1] === 0 && largest[2] === 0; const halfWidth = debugInfo.size.width / 2; const halfHeight = debugInfo.size.height / 2; - const inside = debugInfo.positions.filter(position => { + const inside = debugInfo.positions.filter((position) => { const x = position.x + halfWidth; const y = position.y + halfHeight; return 0 <= x && x <= debugInfo.size.width && 0 <= y && y <= debugInfo.size.height; @@ -532,7 +532,7 @@ describe('tag cloud tests', function() { } function handleExpectedBlip(assertion) { - return function() { + return function () { if (!shouldAssert()) { return; } diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js index 040ee18916fa2..4a6e9e7765213 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js @@ -42,7 +42,7 @@ import { setFormatService } from '../../../../../../plugins/vis_type_tagcloud/pu const THRESHOLD = 0.65; const PIXEL_DIFF = 64; -describe('TagCloudVisualizationTest', function() { +describe('TagCloudVisualizationTest', function () { let domNode; let vis; let imageComparator; @@ -76,8 +76,8 @@ describe('TagCloudVisualizationTest', function() { beforeEach(ngMock.module('kibana')); - describe('TagCloudVisualization - basics', function() { - beforeEach(async function() { + describe('TagCloudVisualization - basics', function () { + beforeEach(async function () { const visType = new BaseVisType(createTagCloudVisTypeDefinition({ colors: seedColors })); setupDOM('512px', '512px'); imageComparator = new ImageComparator(); @@ -91,12 +91,12 @@ describe('TagCloudVisualizationTest', function() { }); }); - afterEach(function() { + afterEach(function () { teardownDOM(); imageComparator.destroy(); }); - it('simple draw', async function() { + it('simple draw', async function () { const tagcloudVisualization = new TagCloudVisualization(domNode, vis); await tagcloudVisualization.render(dummyTableGroup, vis.params, { @@ -118,7 +118,7 @@ describe('TagCloudVisualizationTest', function() { expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); }); - it('with resize', async function() { + it('with resize', async function () { const tagcloudVisualization = new TagCloudVisualization(domNode, vis); await tagcloudVisualization.render(dummyTableGroup, vis.params, { resize: false, @@ -149,7 +149,7 @@ describe('TagCloudVisualizationTest', function() { expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); }); - it('with param change', async function() { + it('with param change', async function () { const tagcloudVisualization = new TagCloudVisualization(domNode, vis); await tagcloudVisualization.render(dummyTableGroup, vis.params, { resize: false, diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js index 691318e32245b..6d6eb69e66792 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js @@ -139,7 +139,7 @@ describe('VegaVisualizations', () => { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(() => { - setInjectedVarFunc(injectedVar => { + setInjectedVarFunc((injectedVar) => { switch (injectedVar) { case 'mapConfig': return { @@ -186,7 +186,7 @@ describe('VegaVisualizations', () => { ); describe('VegaVisualization - basics', () => { - beforeEach(async function() { + beforeEach(async function () { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); @@ -195,12 +195,12 @@ describe('VegaVisualizations', () => { }); }); - afterEach(function() { + afterEach(function () { teardownDOM(); imageComparator.destroy(); }); - it('should show vegalite graph and update on resize (may fail in dev env)', async function() { + it('should show vegalite graph and update on resize (may fail in dev env)', async function () { let vegaVis; try { vegaVis = new VegaVisualization(domNode, vis); @@ -223,7 +223,7 @@ describe('VegaVisualizations', () => { } }); - it('should show vega graph (may fail in dev env)', async function() { + it('should show vega graph (may fail in dev env)', async function () { let vegaVis; try { vegaVis = new VegaVisualization(domNode, vis); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/_vis_fixture.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/_vis_fixture.js index 8a542fec0639c..7a68e847f13b1 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/_vis_fixture.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/_vis_fixture.js @@ -42,15 +42,13 @@ const $visCanvas = $('
') let count = 0; const visHeight = $visCanvas.height(); -$visCanvas.new = function() { +$visCanvas.new = function () { count += 1; if (count > 1) $visCanvas.height(visHeight * count); - return $('
') - .addClass('visChart') - .appendTo($visCanvas); + return $('
').addClass('visChart').appendTo($visCanvas); }; -afterEach(function() { +afterEach(function () { $visCanvas.empty(); if (count > 1) $visCanvas.height(visHeight); count = 0; diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/chart_title.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/chart_title.js index 81fef155daf57..6790c49691dfd 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/chart_title.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/chart_title.js @@ -25,7 +25,7 @@ import { ChartTitle } from '../../../../../../../plugins/vis_type_vislib/public/ import { VisConfig } from '../../../../../../../plugins/vis_type_vislib/public/vislib/lib/vis_config'; import { getMockUiState } from '../../../../../../../plugins/vis_type_vislib/public/fixtures/mocks'; -describe('Vislib ChartTitle Class Test Suite', function() { +describe('Vislib ChartTitle Class Test Suite', function () { let mockUiState; let chartTitle; let el; @@ -90,15 +90,9 @@ describe('Vislib ChartTitle Class Test Suite', function() { beforeEach(() => { mockUiState = getMockUiState(); - el = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper') - .datum(data); + el = d3.select('body').append('div').attr('class', 'visWrapper').datum(data); - el.append('div') - .attr('class', 'chart-title') - .style('height', '20px'); + el.append('div').attr('class', 'chart-title').style('height', '20px'); const visConfig = new VisConfig( { @@ -115,31 +109,26 @@ describe('Vislib ChartTitle Class Test Suite', function() { chartTitle = new ChartTitle(visConfig); }); - afterEach(function() { + afterEach(function () { el.remove(); }); - describe('render Method', function() { - beforeEach(function() { + describe('render Method', function () { + beforeEach(function () { chartTitle.render(); }); - it('should append an svg to div', function() { + it('should append an svg to div', function () { expect(el.select('.chart-title').selectAll('svg').length).to.be(1); }); - it('should append text', function() { - expect( - !!el - .select('.chart-title') - .selectAll('svg') - .selectAll('text') - ).to.be(true); + it('should append text', function () { + expect(!!el.select('.chart-title').selectAll('svg').selectAll('text')).to.be(true); }); }); - describe('draw Method', function() { - it('should be a function', function() { + describe('draw Method', function () { + it('should be a function', function () { expect(_.isFunction(chartTitle.draw())).to.be(true); }); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/dispatch.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/dispatch.js index eb4e109690c37..4f8cee2651a9f 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/dispatch.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/dispatch.js @@ -27,20 +27,16 @@ import data from '../../../../../../../plugins/vis_type_vislib/public/fixtures/m import { getMockUiState } from '../../../../../../../plugins/vis_type_vislib/public/fixtures/mocks'; import { getVis } from '../_vis_fixture'; -describe('Vislib Dispatch Class Test Suite', function() { +describe('Vislib Dispatch Class Test Suite', function () { function destroyVis(vis) { vis.destroy(); } function getEls(element, n, type) { - return d3 - .select(element) - .data(new Array(n)) - .enter() - .append(type); + return d3.select(element).data(new Array(n)).enter().append(type); } - describe('', function() { + describe('', function () { let vis; let mockUiState; @@ -50,14 +46,14 @@ describe('Vislib Dispatch Class Test Suite', function() { vis.render(data, mockUiState); }); - afterEach(function() { + afterEach(function () { destroyVis(vis); }); - it('implements on, off, emit methods', function() { + it('implements on, off, emit methods', function () { const events = _.pluck(vis.handler.charts, 'events'); expect(events.length).to.be.above(0); - events.forEach(function(dispatch) { + events.forEach(function (dispatch) { expect(dispatch).to.have.property('on'); expect(dispatch).to.have.property('off'); expect(dispatch).to.have.property('emit'); @@ -65,7 +61,7 @@ describe('Vislib Dispatch Class Test Suite', function() { }); }); - describe('Stock event handlers', function() { + describe('Stock event handlers', function () { let vis; let mockUiState; @@ -76,19 +72,19 @@ describe('Vislib Dispatch Class Test Suite', function() { vis.render(data, mockUiState); }); - afterEach(function() { + afterEach(function () { destroyVis(vis); }); - describe('addEvent method', function() { - it('returns a function that binds the passed event to a selection', function() { + describe('addEvent method', function () { + it('returns a function that binds the passed event to a selection', function () { const chart = _.first(vis.handler.charts); const apply = chart.events.addEvent('event', _.noop); expect(apply).to.be.a('function'); const els = getEls(vis.element, 3, 'div'); apply(els); - els.each(function() { + els.each(function () { expect(d3.select(this).on('event')).to.be(_.noop); }); }); @@ -97,21 +93,21 @@ describe('Vislib Dispatch Class Test Suite', function() { // test the addHoverEvent, addClickEvent methods by // checking that they return function which bind the events expected function checkBoundAddMethod(name, event) { - describe(name + ' method', function() { - it('should be a function', function() { - vis.handler.charts.forEach(function(chart) { + describe(name + ' method', function () { + it('should be a function', function () { + vis.handler.charts.forEach(function (chart) { expect(chart.events[name]).to.be.a('function'); }); }); - it('returns a function that binds ' + event + ' events to a selection', function() { + it('returns a function that binds ' + event + ' events to a selection', function () { const chart = _.first(vis.handler.charts); const apply = chart.events[name](chart.series[0].chartEl); expect(apply).to.be.a('function'); const els = getEls(vis.element, 3, 'div'); apply(els); - els.each(function() { + els.each(function () { expect(d3.select(this).on(event)).to.be.a('function'); }); }); @@ -122,9 +118,9 @@ describe('Vislib Dispatch Class Test Suite', function() { checkBoundAddMethod('addMouseoutEvent', 'mouseout'); checkBoundAddMethod('addClickEvent', 'click'); - describe('addMousePointer method', function() { - it('should be a function', function() { - vis.handler.charts.forEach(function(chart) { + describe('addMousePointer method', function () { + it('should be a function', function () { + vis.handler.charts.forEach(function (chart) { const pointer = chart.events.addMousePointer; expect(_.isFunction(pointer)).to.be(true); @@ -177,14 +173,14 @@ describe('Vislib Dispatch Class Test Suite', function() { }); }); - describe('Custom event handlers', function() { - it('should attach whatever gets passed on vis.on() to chart.events', function(done) { + describe('Custom event handlers', function () { + it('should attach whatever gets passed on vis.on() to chart.events', function (done) { const vis = getVis(); const mockUiState = getMockUiState(); vis.on('someEvent', _.noop); vis.render(data, mockUiState); - vis.handler.charts.forEach(function(chart) { + vis.handler.charts.forEach(function (chart) { expect(chart.events.listenerCount('someEvent')).to.be(1); }); @@ -192,13 +188,13 @@ describe('Vislib Dispatch Class Test Suite', function() { done(); }); - it('can be added after rendering', function() { + it('can be added after rendering', function () { const vis = getVis(); const mockUiState = getMockUiState(); vis.render(data, mockUiState); vis.on('someEvent', _.noop); - vis.handler.charts.forEach(function(chart) { + vis.handler.charts.forEach(function (chart) { expect(chart.events.listenerCount('someEvent')).to.be(1); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/handler/handler.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/handler/handler.js index 27f7f4ed3e073..e4f75c47e621c 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/handler/handler.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/handler/handler.js @@ -31,8 +31,8 @@ import { getVis } from '../../_vis_fixture'; const dateHistogramArray = [series, columns, rows, stackedSeries]; const names = ['series', 'columns', 'rows', 'stackedSeries']; -dateHistogramArray.forEach(function(data, i) { - describe('Vislib Handler Test Suite for ' + names[i] + ' Data', function() { +dateHistogramArray.forEach(function (data, i) { + describe('Vislib Handler Test Suite for ' + names[i] + ' Data', function () { const events = ['click', 'brush']; let vis; @@ -41,100 +41,100 @@ dateHistogramArray.forEach(function(data, i) { vis.render(data, getMockUiState()); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - describe('render Method', function() { - it('should render charts', function() { + describe('render Method', function () { + it('should render charts', function () { expect(vis.handler.charts.length).to.be.greaterThan(0); - vis.handler.charts.forEach(function(chart) { + vis.handler.charts.forEach(function (chart) { expect($(chart.chartEl).find('svg').length).to.be(1); }); }); }); - describe('enable Method', function() { + describe('enable Method', function () { let charts; - beforeEach(function() { + beforeEach(function () { charts = vis.handler.charts; - charts.forEach(function(chart) { - events.forEach(function(event) { + charts.forEach(function (chart) { + events.forEach(function (event) { vis.handler.enable(event, chart); }); }); }); - it('should add events to chart and emit to the Events class', function() { - charts.forEach(function(chart) { - events.forEach(function(event) { + it('should add events to chart and emit to the Events class', function () { + charts.forEach(function (chart) { + events.forEach(function (event) { expect(chart.events.listenerCount(event)).to.be.above(0); }); }); }); }); - describe('disable Method', function() { + describe('disable Method', function () { let charts; - beforeEach(function() { + beforeEach(function () { charts = vis.handler.charts; - charts.forEach(function(chart) { - events.forEach(function(event) { + charts.forEach(function (chart) { + events.forEach(function (event) { vis.handler.disable(event, chart); }); }); }); - it('should remove events from the chart', function() { - charts.forEach(function(chart) { - events.forEach(function(event) { + it('should remove events from the chart', function () { + charts.forEach(function (chart) { + events.forEach(function (event) { expect(chart.events.listenerCount(event)).to.be(0); }); }); }); }); - describe('removeAll Method', function() { - beforeEach(function() { + describe('removeAll Method', function () { + beforeEach(function () { vis.handler.removeAll(vis.element); }); - it('should remove all DOM elements from the el', function() { + it('should remove all DOM elements from the el', function () { expect($(vis.element).children().length).to.be(0); }); }); - describe('error Method', function() { - beforeEach(function() { + describe('error Method', function () { + beforeEach(function () { vis.handler.error('This is an error!'); }); - it('should return an error classed DOM element with a text message', function() { + it('should return an error classed DOM element with a text message', function () { expect($(vis.element).find('.error').length).to.be(1); expect($('.error h4').html()).to.be('This is an error!'); }); }); - describe('destroy Method', function() { - beforeEach(function() { + describe('destroy Method', function () { + beforeEach(function () { vis.handler.destroy(); }); - it('should destroy all the charts in the visualization', function() { + it('should destroy all the charts in the visualization', function () { expect(vis.handler.charts.length).to.be(0); }); }); - describe('event proxying', function() { - it('should only pass the original event object to downstream handlers', function(done) { + describe('event proxying', function () { + it('should only pass the original event object to downstream handlers', function (done) { const event = {}; const chart = vis.handler.charts[0]; - const mockEmitter = function() { + const mockEmitter = function () { const args = Array.from(arguments); expect(args.length).to.be(2); expect(args[0]).to.be('click'); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/layout/layout.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/layout/layout.js index 505b0a04c6183..7ad962fefc341 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/layout/layout.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/lib/layout/layout.js @@ -34,8 +34,8 @@ import { getVis } from '../../_vis_fixture'; const dateHistogramArray = [series, columns, rows, stackedSeries]; const names = ['series', 'columns', 'rows', 'stackedSeries']; -dateHistogramArray.forEach(function(data, i) { - describe('Vislib Layout Class Test Suite for ' + names[i] + ' Data', function() { +dateHistogramArray.forEach(function (data, i) { + describe('Vislib Layout Class Test Suite for ' + names[i] + ' Data', function () { let vis; let mockUiState; let numberOfCharts; @@ -52,8 +52,8 @@ dateHistogramArray.forEach(function(data, i) { vis.destroy(); }); - describe('createLayout Method', function() { - it('should append all the divs', function() { + describe('createLayout Method', function () { + it('should append all the divs', function () { expect($(vis.element).find('.visWrapper').length).to.be(1); expect($(vis.element).find('.visAxis--y').length).to.be(2); expect($(vis.element).find('.visWrapper__column').length).to.be(1); @@ -68,8 +68,8 @@ dateHistogramArray.forEach(function(data, i) { }); }); - describe('layout Method', function() { - beforeEach(function() { + describe('layout Method', function () { + beforeEach(function () { const visConfig = new VisConfig( { type: 'histogram', @@ -82,28 +82,24 @@ dateHistogramArray.forEach(function(data, i) { testLayout = new Layout(visConfig); }); - it('should append a div with the correct class name', function() { + it('should append a div with the correct class name', function () { expect($(vis.element).find('.chart').length).to.be(numberOfCharts); }); - it('should bind data to the DOM element', function() { - expect( - !!$(vis.element) - .find('.chart') - .data() - ).to.be(true); + it('should bind data to the DOM element', function () { + expect(!!$(vis.element).find('.chart').data()).to.be(true); }); - it('should create children', function() { + it('should create children', function () { expect(typeof $(vis.element).find('.x-axis-div')).to.be('object'); }); - it('should call split function when provided', function() { + it('should call split function when provided', function () { expect(typeof $(vis.element).find('.x-axis-div')).to.be('object'); }); - it('should throw errors when incorrect arguments provided', function() { - expect(function() { + it('should throw errors when incorrect arguments provided', function () { + expect(function () { testLayout.layout({ parent: vis.element, type: undefined, @@ -111,24 +107,24 @@ dateHistogramArray.forEach(function(data, i) { }); }).to.throwError(); - expect(function() { + expect(function () { testLayout.layout({ type: 'div', class: 'chart', }); }).to.throwError(); - expect(function() { + expect(function () { testLayout.layout({ parent: 'histogram', type: 'div', }); }).to.throwError(); - expect(function() { + expect(function () { testLayout.layout({ parent: vis.element, - type: function(d) { + type: function (d) { return d; }, class: 'chart', @@ -137,27 +133,25 @@ dateHistogramArray.forEach(function(data, i) { }); }); - describe('appendElem Method', function() { - beforeEach(function() { + describe('appendElem Method', function () { + beforeEach(function () { vis.handler.layout.appendElem(vis.element, 'svg', 'column'); vis.handler.layout.appendElem('.visChart', 'div', 'test'); }); - it('should append DOM element to el with a class name', function() { + it('should append DOM element to el with a class name', function () { expect(typeof $(vis.element).find('.column')).to.be('object'); expect(typeof $(vis.element).find('.test')).to.be('object'); }); }); - describe('removeAll Method', function() { - beforeEach(function() { - d3.select(vis.element) - .append('div') - .attr('class', 'visualize'); + describe('removeAll Method', function () { + beforeEach(function () { + d3.select(vis.element).append('div').attr('class', 'visualize'); vis.handler.layout.removeAll(vis.element); }); - it('should remove all DOM elements from the el', function() { + it('should remove all DOM elements from the el', function () { expect($(vis.element).children().length).to.be(0); }); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/vis.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/vis.js index 67f29ee96a336..36decdc415ed8 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/vis.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/vis.js @@ -31,8 +31,8 @@ import { getVis } from './_vis_fixture'; const dataArray = [series, columns, rows, stackedSeries]; const names = ['series', 'columns', 'rows', 'stackedSeries']; -dataArray.forEach(function(data, i) { - describe('Vislib Vis Test Suite for ' + names[i] + ' Data', function() { +dataArray.forEach(function (data, i) { + describe('Vislib Vis Test Suite for ' + names[i] + ' Data', function () { const beforeEvent = 'click'; const afterEvent = 'brush'; let vis; @@ -46,31 +46,31 @@ dataArray.forEach(function(data, i) { mockUiState = getMockUiState(); }); - afterEach(function() { + afterEach(function () { vis.destroy(); secondVis.destroy(); }); - describe('render Method', function() { - beforeEach(function() { + describe('render Method', function () { + beforeEach(function () { vis.render(data, mockUiState); numberOfCharts = vis.handler.charts.length; }); - it('should bind data to this object', function() { + it('should bind data to this object', function () { expect(_.isObject(vis.data)).to.be(true); }); - it('should instantiate a handler object', function() { + it('should instantiate a handler object', function () { expect(_.isObject(vis.handler)).to.be(true); }); - it('should append a chart', function() { + it('should append a chart', function () { expect($('.chart').length).to.be(numberOfCharts); }); - it('should throw an error if no data is provided', function() { - expect(function() { + it('should throw an error if no data is provided', function () { + expect(function () { vis.render(null, mockUiState); }).to.throwError(); }); @@ -82,55 +82,55 @@ dataArray.forEach(function(data, i) { }); }); - describe('destroy Method', function() { - beforeEach(function() { + describe('destroy Method', function () { + beforeEach(function () { vis.render(data, mockUiState); secondVis.render(data, mockUiState); secondVis.destroy(); }); - it('should remove all DOM elements from el', function() { + it('should remove all DOM elements from el', function () { expect($(secondVis.el).find('.visWrapper').length).to.be(0); }); - it('should not remove visualizations that have not been destroyed', function() { + it('should not remove visualizations that have not been destroyed', function () { expect($(vis.element).find('.visWrapper').length).to.be(1); }); }); - describe('set Method', function() { - beforeEach(function() { + describe('set Method', function () { + beforeEach(function () { vis.render(data, mockUiState); vis.set('addLegend', false); vis.set('offset', 'wiggle'); }); - it('should set an attribute', function() { + it('should set an attribute', function () { expect(vis.get('addLegend')).to.be(false); expect(vis.get('offset')).to.be('wiggle'); }); }); - describe('get Method', function() { - beforeEach(function() { + describe('get Method', function () { + beforeEach(function () { vis.render(data, mockUiState); }); - it('should get attribute values', function() { + it('should get attribute values', function () { expect(vis.get('addLegend')).to.be(true); expect(vis.get('addTooltip')).to.be(true); expect(vis.get('type')).to.be('point_series'); }); }); - describe('on Method', function() { + describe('on Method', function () { let listeners; - beforeEach(function() { - listeners = [function() {}, function() {}]; + beforeEach(function () { + listeners = [function () {}, function () {}]; // Add event and listeners to chart - listeners.forEach(function(listener) { + listeners.forEach(function (listener) { vis.on(beforeEvent, listener); }); @@ -138,50 +138,50 @@ dataArray.forEach(function(data, i) { vis.render(data, mockUiState); // Add event after charts have rendered - listeners.forEach(function(listener) { + listeners.forEach(function (listener) { vis.on(afterEvent, listener); }); }); - afterEach(function() { + afterEach(function () { vis.removeAllListeners(beforeEvent); vis.removeAllListeners(afterEvent); }); - it('should add an event and its listeners', function() { - listeners.forEach(function(listener) { + it('should add an event and its listeners', function () { + listeners.forEach(function (listener) { expect(vis.listeners(beforeEvent)).to.contain(listener); }); - listeners.forEach(function(listener) { + listeners.forEach(function (listener) { expect(vis.listeners(afterEvent)).to.contain(listener); }); }); - it('should cause a listener for each event to be attached to each chart', function() { + it('should cause a listener for each event to be attached to each chart', function () { const charts = vis.handler.charts; - charts.forEach(function(chart) { + charts.forEach(function (chart) { expect(chart.events.listenerCount(beforeEvent)).to.be.above(0); expect(chart.events.listenerCount(afterEvent)).to.be.above(0); }); }); }); - describe('off Method', function() { + describe('off Method', function () { let listeners; let listener1; let listener2; - beforeEach(function() { + beforeEach(function () { listeners = []; - listener1 = function() {}; - listener2 = function() {}; + listener1 = function () {}; + listener2 = function () {}; listeners.push(listener1); listeners.push(listener2); // Add event and listeners to chart - listeners.forEach(function(listener) { + listeners.forEach(function (listener) { vis.on(beforeEvent, listener); }); @@ -192,7 +192,7 @@ dataArray.forEach(function(data, i) { vis.render(data, mockUiState); // Add event after charts have rendered - listeners.forEach(function(listener) { + listeners.forEach(function (listener) { vis.on(afterEvent, listener); }); @@ -200,12 +200,12 @@ dataArray.forEach(function(data, i) { vis.off(afterEvent, listener1); }); - afterEach(function() { + afterEach(function () { vis.removeAllListeners(beforeEvent); vis.removeAllListeners(afterEvent); }); - it('should remove a listener', function() { + it('should remove a listener', function () { const charts = vis.handler.charts; expect(vis.listeners(beforeEvent)).to.not.contain(listener1); @@ -215,13 +215,13 @@ dataArray.forEach(function(data, i) { expect(vis.listeners(afterEvent)).to.contain(listener2); // Events should still be attached to charts - charts.forEach(function(chart) { + charts.forEach(function (chart) { expect(chart.events.listenerCount(beforeEvent)).to.be.above(0); expect(chart.events.listenerCount(afterEvent)).to.be.above(0); }); }); - it('should remove the event and all listeners when only event passed an argument', function() { + it('should remove the event and all listeners when only event passed an argument', function () { const charts = vis.handler.charts; vis.removeAllListeners(afterEvent); @@ -230,19 +230,19 @@ dataArray.forEach(function(data, i) { expect(vis.listeners(afterEvent)).to.not.contain(listener2); // should remove the event from the charts - charts.forEach(function(chart) { + charts.forEach(function (chart) { expect(chart.events.listenerCount(beforeEvent)).to.be.above(0); expect(chart.events.listenerCount(afterEvent)).to.be(0); }); }); - it('should remove the event from the chart when the last listener is removed', function() { + it('should remove the event from the chart when the last listener is removed', function () { const charts = vis.handler.charts; vis.off(afterEvent, listener2); expect(vis.listenerCount(afterEvent)).to.be(0); - charts.forEach(function(chart) { + charts.forEach(function (chart) { expect(chart.events.listenerCount(afterEvent)).to.be(0); }); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/area_chart.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/area_chart.js index eb529c380cdda..fd2240c0c64c5 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/area_chart.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/area_chart.js @@ -41,8 +41,8 @@ const visLibParams = { mode: 'stacked', }; -_.forOwn(dataTypesArray, function(dataType, dataTypeName) { - describe('Vislib Area Chart Test Suite for ' + dataTypeName + ' Data', function() { +_.forOwn(dataTypesArray, function (dataType, dataTypeName) { + describe('Vislib Area Chart Test Suite for ' + dataTypeName + ' Data', function () { let vis; let mockUiState; @@ -53,46 +53,46 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { vis.render(dataType, mockUiState); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - describe('stackData method', function() { + describe('stackData method', function () { let stackedData; let isStacked; - beforeEach(function() { - vis.handler.charts.forEach(function(chart) { + beforeEach(function () { + vis.handler.charts.forEach(function (chart) { stackedData = chart.chartData; - isStacked = stackedData.series.every(function(arr) { - return arr.values.every(function(d) { + isStacked = stackedData.series.every(function (arr) { + return arr.values.every(function (d) { return _.isNumber(d.y0); }); }); }); }); - it('should append a d.y0 key to the data object', function() { + it('should append a d.y0 key to the data object', function () { expect(isStacked).to.be(true); }); }); - describe('addPath method', function() { - it('should append a area paths', function() { - vis.handler.charts.forEach(function(chart) { + describe('addPath method', function () { + it('should append a area paths', function () { + vis.handler.charts.forEach(function (chart) { expect($(chart.chartEl).find('path').length).to.be.greaterThan(0); }); }); }); - describe('addPathEvents method', function() { + describe('addPathEvents method', function () { let path; let d3selectedPath; let onMouseOver; - beforeEach(function() { - vis.handler.charts.forEach(function(chart) { + beforeEach(function () { + vis.handler.charts.forEach(function (chart) { path = $(chart.chartEl).find('path')[0]; d3selectedPath = d3.select(path)[0][0]; @@ -101,14 +101,14 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { }); }); - it('should attach a hover event', function() { - vis.handler.charts.forEach(function() { + it('should attach a hover event', function () { + vis.handler.charts.forEach(function () { expect(onMouseOver).to.be(true); }); }); }); - describe('addCircleEvents method', function() { + describe('addCircleEvents method', function () { let circle; let brush; let d3selectedCircle; @@ -117,7 +117,7 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { let onMouseOver; beforeEach(() => { - vis.handler.charts.forEach(function(chart) { + vis.handler.charts.forEach(function (chart) { circle = $(chart.chartEl).find('circle')[0]; brush = $(chart.chartEl).find('.brush'); d3selectedCircle = d3.select(circle)[0][0]; @@ -134,40 +134,40 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { // listeners, however, I was not able to test for the listener // function being present. I will need to update this test // in the future. - it('should attach a brush g element', function() { - vis.handler.charts.forEach(function() { + it('should attach a brush g element', function () { + vis.handler.charts.forEach(function () { expect(onBrush).to.be(true); }); }); - it('should attach a click event', function() { - vis.handler.charts.forEach(function() { + it('should attach a click event', function () { + vis.handler.charts.forEach(function () { expect(onClick).to.be(true); }); }); - it('should attach a hover event', function() { - vis.handler.charts.forEach(function() { + it('should attach a hover event', function () { + vis.handler.charts.forEach(function () { expect(onMouseOver).to.be(true); }); }); }); - describe('addCircles method', function() { - it('should append circles', function() { - vis.handler.charts.forEach(function(chart) { + describe('addCircles method', function () { + it('should append circles', function () { + vis.handler.charts.forEach(function (chart) { expect($(chart.chartEl).find('circle').length).to.be.greaterThan(0); }); }); - it('should not draw circles where d.y === 0', function() { - vis.handler.charts.forEach(function(chart) { + it('should not draw circles where d.y === 0', function () { + vis.handler.charts.forEach(function (chart) { const series = chart.chartData.series; - const isZero = series.some(function(d) { + const isZero = series.some(function (d) { return d.y === 0; }); const circles = $.makeArray($(chart.chartEl).find('circle')); - const isNotDrawn = circles.some(function(d) { + const isNotDrawn = circles.some(function (d) { return d.__data__.y === 0; }); @@ -178,15 +178,15 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { }); }); - describe('draw method', function() { - it('should return a function', function() { - vis.handler.charts.forEach(function(chart) { + describe('draw method', function () { + it('should return a function', function () { + vis.handler.charts.forEach(function (chart) { expect(_.isFunction(chart.draw())).to.be(true); }); }); - it('should return a yMin and yMax', function() { - vis.handler.charts.forEach(function(chart) { + it('should return a yMin and yMax', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const domain = yAxis.getScale().domain(); @@ -195,8 +195,8 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { }); }); - it('should render a zero axis line', function() { - vis.handler.charts.forEach(function(chart) { + it('should render a zero axis line', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { @@ -206,14 +206,14 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { }); }); - describe('defaultYExtents is true', function() { - beforeEach(function() { + describe('defaultYExtents is true', function () { + beforeEach(function () { vis.visConfigArgs.defaultYExtents = true; vis.render(dataType, mockUiState); }); - it('should return yAxis extents equal to data extents', function() { - vis.handler.charts.forEach(function(chart) { + it('should return yAxis extents equal to data extents', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const min = vis.handler.valueAxes[0].axisScale.getYMin(); const max = vis.handler.valueAxes[0].axisScale.getYMax(); @@ -223,16 +223,16 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { }); }); }); - [0, 2, 4, 8].forEach(function(boundsMarginValue) { - describe('defaultYExtents is true and boundsMargin is defined', function() { - beforeEach(function() { + [0, 2, 4, 8].forEach(function (boundsMarginValue) { + describe('defaultYExtents is true and boundsMargin is defined', function () { + beforeEach(function () { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; vis.render(dataType, mockUiState); }); - it('should return yAxis extents equal to data extents with boundsMargin', function() { - vis.handler.charts.forEach(function(chart) { + it('should return yAxis extents equal to data extents with boundsMargin', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const min = vis.handler.valueAxes[0].axisScale.getYMin(); const max = vis.handler.valueAxes[0].axisScale.getYMax(); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/chart.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/chart.js index 4c5e3db316243..2b41ce5d1a5c6 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/chart.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/chart.js @@ -24,7 +24,7 @@ import { Chart } from '../../../../../../../plugins/vis_type_vislib/public/visli import { getMockUiState } from '../../../../../../../plugins/vis_type_vislib/public/fixtures/mocks'; import { getVis } from '../_vis_fixture'; -describe('Vislib _chart Test Suite', function() { +describe('Vislib _chart Test Suite', function () { let vis; let el; let myChart; @@ -96,10 +96,10 @@ describe('Vislib _chart Test Suite', function() { ], }, ], - tooltipFormatter: function(datapoint) { + tooltipFormatter: function (datapoint) { return datapoint; }, - xAxisFormatter: function(thing) { + xAxisFormatter: function (thing) { return thing; }, xAxisLabel: 'Date Histogram', @@ -107,10 +107,7 @@ describe('Vislib _chart Test Suite', function() { }; beforeEach(() => { - el = d3 - .select('body') - .append('div') - .attr('class', 'column-chart'); + el = d3.select('body').append('div').attr('class', 'column-chart'); config = { type: 'histogram', @@ -125,16 +122,16 @@ describe('Vislib _chart Test Suite', function() { myChart = vis.handler.charts[0]; }); - afterEach(function() { + afterEach(function () { el.remove(); vis.destroy(); }); - it('should be a constructor for visualization modules', function() { + it('should be a constructor for visualization modules', function () { expect(myChart instanceof Chart).to.be(true); }); - it('should have a render method', function() { + it('should have a render method', function () { expect(typeof myChart.render === 'function').to.be(true); }); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/column_chart.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/column_chart.js index 5cbd5948bc477..f075dff466793 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/column_chart.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/column_chart.js @@ -46,12 +46,12 @@ const dataTypesArray = [ ['stackedSeries', 'stacked', stackedSeries], ]; -dataTypesArray.forEach(function(dataType) { +dataTypesArray.forEach(function (dataType) { const name = dataType[0]; const mode = dataType[1]; const data = dataType[2]; - describe('Vislib Column Chart Test Suite for ' + name + ' Data', function() { + describe('Vislib Column Chart Test Suite for ' + name + ' Data', function () { let vis; let mockUiState; const visLibParams = { @@ -73,46 +73,46 @@ dataTypesArray.forEach(function(dataType) { vis.render(data, mockUiState); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - describe('stackData method', function() { + describe('stackData method', function () { let stackedData; let isStacked; - beforeEach(function() { - vis.handler.charts.forEach(function(chart) { + beforeEach(function () { + vis.handler.charts.forEach(function (chart) { stackedData = chart.chartData; - isStacked = stackedData.series.every(function(arr) { - return arr.values.every(function(d) { + isStacked = stackedData.series.every(function (arr) { + return arr.values.every(function (d) { return _.isNumber(d.y0); }); }); }); }); - it('should stack values when mode is stacked', function() { + it('should stack values when mode is stacked', function () { if (mode === 'stacked') { expect(isStacked).to.be(true); } }); - it('should stack values when mode is percentage', function() { + it('should stack values when mode is percentage', function () { if (mode === 'percentage') { expect(isStacked).to.be(true); } }); }); - describe('addBars method', function() { - it('should append rects', function() { + describe('addBars method', function () { + it('should append rects', function () { let numOfSeries; let numOfValues; let product; - vis.handler.charts.forEach(function(chart) { + vis.handler.charts.forEach(function (chart) { numOfSeries = chart.chartData.series.length; numOfValues = chart.chartData.series[0].values.length; product = numOfSeries * numOfValues; @@ -121,11 +121,9 @@ dataTypesArray.forEach(function(dataType) { }); }); - describe('addBarEvents method', function() { + describe('addBarEvents method', function () { function checkChart(chart) { - const rect = $(chart.chartEl) - .find('.series rect') - .get(0); + const rect = $(chart.chartEl).find('.series rect').get(0); // check for existence of stuff and things return { @@ -140,8 +138,8 @@ dataTypesArray.forEach(function(dataType) { }; } - it('should attach the brush if data is a set is ordered', function() { - vis.handler.charts.forEach(function(chart) { + it('should attach the brush if data is a set is ordered', function () { + vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); const ordered = vis.handler.data.get('ordered'); const allowBrushing = Boolean(ordered); @@ -149,30 +147,30 @@ dataTypesArray.forEach(function(dataType) { }); }); - it('should attach a click event', function() { - vis.handler.charts.forEach(function(chart) { + it('should attach a click event', function () { + vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); expect(has.click).to.be(true); }); }); - it('should attach a hover event', function() { - vis.handler.charts.forEach(function(chart) { + it('should attach a hover event', function () { + vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); expect(has.mouseOver).to.be(true); }); }); }); - describe('draw method', function() { - it('should return a function', function() { - vis.handler.charts.forEach(function(chart) { + describe('draw method', function () { + it('should return a function', function () { + vis.handler.charts.forEach(function (chart) { expect(_.isFunction(chart.draw())).to.be(true); }); }); - it('should return a yMin and yMax', function() { - vis.handler.charts.forEach(function(chart) { + it('should return a yMin and yMax', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const domain = yAxis.getScale().domain(); @@ -181,8 +179,8 @@ dataTypesArray.forEach(function(dataType) { }); }); - it('should render a zero axis line', function() { - vis.handler.charts.forEach(function(chart) { + it('should render a zero axis line', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { @@ -192,14 +190,14 @@ dataTypesArray.forEach(function(dataType) { }); }); - describe('defaultYExtents is true', function() { - beforeEach(function() { + describe('defaultYExtents is true', function () { + beforeEach(function () { vis.visConfigArgs.defaultYExtents = true; vis.render(data, mockUiState); }); - it('should return yAxis extents equal to data extents', function() { - vis.handler.charts.forEach(function(chart) { + it('should return yAxis extents equal to data extents', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const min = vis.handler.valueAxes[0].axisScale.getYMin(); const max = vis.handler.valueAxes[0].axisScale.getYMax(); @@ -209,16 +207,16 @@ dataTypesArray.forEach(function(dataType) { }); }); }); - [0, 2, 4, 8].forEach(function(boundsMarginValue) { - describe('defaultYExtents is true and boundsMargin is defined', function() { - beforeEach(function() { + [0, 2, 4, 8].forEach(function (boundsMarginValue) { + describe('defaultYExtents is true and boundsMargin is defined', function () { + beforeEach(function () { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; vis.render(data, mockUiState); }); - it('should return yAxis extents equal to data extents with boundsMargin', function() { - vis.handler.charts.forEach(function(chart) { + it('should return yAxis extents equal to data extents with boundsMargin', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const min = vis.handler.valueAxes[0].axisScale.getYMin(); const max = vis.handler.valueAxes[0].axisScale.getYMax(); @@ -240,7 +238,7 @@ dataTypesArray.forEach(function(dataType) { }); }); -describe('stackData method - data set with zeros in percentage mode', function() { +describe('stackData method - data set with zeros in percentage mode', function () { let vis; let mockUiState; const visLibParams = { @@ -257,11 +255,11 @@ describe('stackData method - data set with zeros in percentage mode', function() vis.on('brush', _.noop); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - it('should not mutate the injected zeros', function() { + it('should not mutate the injected zeros', function () { vis.render(seriesMonthlyInterval, mockUiState); expect(vis.handler.charts).to.have.length(1); @@ -274,7 +272,7 @@ describe('stackData method - data set with zeros in percentage mode', function() expect(point.y).to.be(0); }); - it('should not mutate zeros that exist in the data', function() { + it('should not mutate zeros that exist in the data', function () { vis.render(rowsWithZeros, mockUiState); expect(vis.handler.charts).to.have.length(2); @@ -287,7 +285,7 @@ describe('stackData method - data set with zeros in percentage mode', function() }); }); -describe('datumWidth - split chart data set with holes', function() { +describe('datumWidth - split chart data set with holes', function () { let vis; let mockUiState; const visLibParams = { @@ -305,23 +303,23 @@ describe('datumWidth - split chart data set with holes', function() { vis.render(rowsSeriesWithHoles, mockUiState); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - it('should not have bar widths that span multiple time bins', function() { + it('should not have bar widths that span multiple time bins', function () { expect(vis.handler.charts.length).to.equal(1); const chart = vis.handler.charts[0]; const rects = $(chart.chartEl).find('.series rect'); const MAX_WIDTH_IN_PIXELS = 27; - rects.each(function() { + rects.each(function () { const width = $(this).attr('width'); expect(width).to.be.lessThan(MAX_WIDTH_IN_PIXELS); }); }); }); -describe('datumWidth - monthly interval', function() { +describe('datumWidth - monthly interval', function () { let vis; let mockUiState; const visLibParams = { @@ -339,11 +337,11 @@ describe('datumWidth - monthly interval', function() { vis.render(seriesMonthlyInterval, mockUiState); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - it('should vary bar width when date histogram intervals are not equal', function() { + it('should vary bar width when date histogram intervals are not equal', function () { expect(vis.handler.charts.length).to.equal(1); const chart = vis.handler.charts[0]; const rects = $(chart.chartEl).find('.series rect'); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/gauge_chart.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/gauge_chart.js index d8ce8f1f5f44b..7c588800ae659 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/gauge_chart.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/gauge_chart.js @@ -25,7 +25,7 @@ import data from '../../../../../../../plugins/vis_type_vislib/public/fixtures/m import { getMockUiState } from '../../../../../../../plugins/vis_type_vislib/public/fixtures/mocks'; import { getVis } from '../_vis_fixture'; -describe('Vislib Gauge Chart Test Suite', function() { +describe('Vislib Gauge Chart Test Suite', function () { let vis; let chartEl; const visLibParams = { @@ -86,21 +86,17 @@ describe('Vislib Gauge Chart Test Suite', function() { generateVis(); }); - afterEach(function() { + afterEach(function () { vis.destroy(); $('.visChart').remove(); }); - it('creates meter gauge', function() { + it('creates meter gauge', function () { expect($(chartEl).find('svg').length).to.equal(5); - expect( - $(chartEl) - .find('svg > g > g > text') - .text() - ).to.equal('2820231918357341352'); + expect($(chartEl).find('svg > g > g > text').text()).to.equal('2820231918357341352'); }); - it('creates circle gauge', function() { + it('creates circle gauge', function () { generateVis({ gauge: { minAngle: 0, @@ -110,36 +106,28 @@ describe('Vislib Gauge Chart Test Suite', function() { expect($(chartEl).find('svg').length).to.equal(5); }); - it('creates gauge with automatic mode', function() { + it('creates gauge with automatic mode', function () { generateVis({ gauge: { alignment: 'automatic', }, }); - expect( - $(chartEl) - .find('svg') - .width() - ).to.equal(197); + expect($(chartEl).find('svg').width()).to.equal(197); }); - it('creates gauge with vertical mode', function() { + it('creates gauge with vertical mode', function () { generateVis({ gauge: { alignment: 'vertical', }, }); - expect( - $(chartEl) - .find('svg') - .width() - ).to.equal($(chartEl).width()); + expect($(chartEl).find('svg').width()).to.equal($(chartEl).width()); }); - it('applies range settings correctly', function() { + it('applies range settings correctly', function () { const paths = $(chartEl).find('svg > g > g:nth-child(1) > path:nth-child(2)'); const fills = []; - paths.each(function() { + paths.each(function () { fills.push(this.style.fill); }); expect(fills).to.eql([ @@ -151,7 +139,7 @@ describe('Vislib Gauge Chart Test Suite', function() { ]); }); - it('applies color schema correctly', function() { + it('applies color schema correctly', function () { generateVis({ gauge: { colorSchema: 'Blues', @@ -159,7 +147,7 @@ describe('Vislib Gauge Chart Test Suite', function() { }); const paths = $(chartEl).find('svg > g > g:nth-child(1) > path:nth-child(2)'); const fills = []; - paths.each(function() { + paths.each(function () { fills.push(this.style.fill); }); expect(fills).to.eql([ diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/heatmap_chart.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/heatmap_chart.js index 765b9118e6844..9fa51fb59ed48 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/heatmap_chart.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/heatmap_chart.js @@ -40,12 +40,12 @@ const dataTypesArray = [ ['stackedSeries', stackedSeries], ]; -describe('Vislib Heatmap Chart Test Suite', function() { - dataTypesArray.forEach(function(dataType) { +describe('Vislib Heatmap Chart Test Suite', function () { + dataTypesArray.forEach(function (dataType) { const name = dataType[0]; const data = dataType[1]; - describe('for ' + name + ' Data', function() { + describe('for ' + name + ' Data', function () { let vis; let mockUiState; const visLibParams = { @@ -72,12 +72,12 @@ describe('Vislib Heatmap Chart Test Suite', function() { generateVis(); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); it('category axes should be rendered in reverse order', () => { - const renderedCategoryAxes = vis.handler.renderArray.filter(item => { + const renderedCategoryAxes = vis.handler.renderArray.filter((item) => { return ( item.constructor && item.constructor.name === 'Axis' && @@ -93,9 +93,9 @@ describe('Vislib Heatmap Chart Test Suite', function() { ); }); - describe('addSquares method', function() { - it('should append rects', function() { - vis.handler.charts.forEach(function(chart) { + describe('addSquares method', function () { + it('should append rects', function () { + vis.handler.charts.forEach(function (chart) { const numOfRects = chart.chartData.series.reduce((result, series) => { return result + series.values.length; }, 0); @@ -104,11 +104,9 @@ describe('Vislib Heatmap Chart Test Suite', function() { }); }); - describe('addBarEvents method', function() { + describe('addBarEvents method', function () { function checkChart(chart) { - const rect = $(chart.chartEl) - .find('.series rect') - .get(0); + const rect = $(chart.chartEl).find('.series rect').get(0); return { click: !!rect.__onclick, @@ -122,8 +120,8 @@ describe('Vislib Heatmap Chart Test Suite', function() { }; } - it('should attach the brush if data is a set of ordered dates', function() { - vis.handler.charts.forEach(function(chart) { + it('should attach the brush if data is a set of ordered dates', function () { + vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); const ordered = vis.handler.data.get('ordered'); const date = Boolean(ordered && ordered.date); @@ -131,30 +129,30 @@ describe('Vislib Heatmap Chart Test Suite', function() { }); }); - it('should attach a click event', function() { - vis.handler.charts.forEach(function(chart) { + it('should attach a click event', function () { + vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); expect(has.click).to.be(true); }); }); - it('should attach a hover event', function() { - vis.handler.charts.forEach(function(chart) { + it('should attach a hover event', function () { + vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); expect(has.mouseOver).to.be(true); }); }); }); - describe('draw method', function() { - it('should return a function', function() { - vis.handler.charts.forEach(function(chart) { + describe('draw method', function () { + it('should return a function', function () { + vis.handler.charts.forEach(function (chart) { expect(_.isFunction(chart.draw())).to.be(true); }); }); - it('should return a yMin and yMax', function() { - vis.handler.charts.forEach(function(chart) { + it('should return a yMin and yMax', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const domain = yAxis.getScale().domain(); @@ -164,11 +162,11 @@ describe('Vislib Heatmap Chart Test Suite', function() { }); }); - it('should define default colors', function() { + it('should define default colors', function () { expect(mockUiState.get('vis.defaultColors')).to.not.be(undefined); }); - it('should set custom range', function() { + it('should set custom range', function () { vis.destroy(); generateVis({ setColorRange: true, @@ -186,7 +184,7 @@ describe('Vislib Heatmap Chart Test Suite', function() { expect(labels[3]).to.be('500 - Infinity'); }); - it('should show correct Y axis title', function() { + it('should show correct Y axis title', function () { expect(vis.handler.categoryAxes[1].axisConfig.get('title.text')).to.equal(''); }); }); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/line_chart.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/line_chart.js index 691417e968eed..dae92c831cd8d 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/line_chart.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/line_chart.js @@ -41,12 +41,12 @@ const dataTypes = [ ['term series', termSeries], ]; -describe('Vislib Line Chart', function() { - dataTypes.forEach(function(type) { +describe('Vislib Line Chart', function () { + dataTypes.forEach(function (type) { const name = type[0]; const data = type[1]; - describe(name + ' Data', function() { + describe(name + ' Data', function () { let vis; let mockUiState; @@ -64,11 +64,11 @@ describe('Vislib Line Chart', function() { vis.on('brush', _.noop); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - describe('addCircleEvents method', function() { + describe('addCircleEvents method', function () { let circle; let brush; let d3selectedCircle; @@ -76,8 +76,8 @@ describe('Vislib Line Chart', function() { let onClick; let onMouseOver; - beforeEach(function() { - vis.handler.charts.forEach(function(chart) { + beforeEach(function () { + vis.handler.charts.forEach(function (chart) { circle = $(chart.chartEl).find('.circle')[0]; brush = $(chart.chartEl).find('.brush'); d3selectedCircle = d3.select(circle)[0][0]; @@ -94,36 +94,36 @@ describe('Vislib Line Chart', function() { // listeners, however, I was not able to test for the listener // function being present. I will need to update this test // in the future. - it('should attach a brush g element', function() { - vis.handler.charts.forEach(function() { + it('should attach a brush g element', function () { + vis.handler.charts.forEach(function () { expect(onBrush).to.be(true); }); }); - it('should attach a click event', function() { - vis.handler.charts.forEach(function() { + it('should attach a click event', function () { + vis.handler.charts.forEach(function () { expect(onClick).to.be(true); }); }); - it('should attach a hover event', function() { - vis.handler.charts.forEach(function() { + it('should attach a hover event', function () { + vis.handler.charts.forEach(function () { expect(onMouseOver).to.be(true); }); }); }); - describe('addCircles method', function() { - it('should append circles', function() { - vis.handler.charts.forEach(function(chart) { + describe('addCircles method', function () { + it('should append circles', function () { + vis.handler.charts.forEach(function (chart) { expect($(chart.chartEl).find('circle').length).to.be.greaterThan(0); }); }); }); - describe('addLines method', function() { - it('should append a paths', function() { - vis.handler.charts.forEach(function(chart) { + describe('addLines method', function () { + it('should append a paths', function () { + vis.handler.charts.forEach(function (chart) { expect($(chart.chartEl).find('path').length).to.be.greaterThan(0); }); }); @@ -139,15 +139,15 @@ describe('Vislib Line Chart', function() { // }); //}); - describe('draw method', function() { - it('should return a function', function() { - vis.handler.charts.forEach(function(chart) { + describe('draw method', function () { + it('should return a function', function () { + vis.handler.charts.forEach(function (chart) { expect(chart.draw()).to.be.a(Function); }); }); - it('should return a yMin and yMax', function() { - vis.handler.charts.forEach(function(chart) { + it('should return a yMin and yMax', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const domain = yAxis.getScale().domain(); expect(domain[0]).to.not.be(undefined); @@ -155,8 +155,8 @@ describe('Vislib Line Chart', function() { }); }); - it('should render a zero axis line', function() { - vis.handler.charts.forEach(function(chart) { + it('should render a zero axis line', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { @@ -166,14 +166,14 @@ describe('Vislib Line Chart', function() { }); }); - describe('defaultYExtents is true', function() { - beforeEach(function() { + describe('defaultYExtents is true', function () { + beforeEach(function () { vis.visConfigArgs.defaultYExtents = true; vis.render(data, mockUiState); }); - it('should return yAxis extents equal to data extents', function() { - vis.handler.charts.forEach(function(chart) { + it('should return yAxis extents equal to data extents', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const min = vis.handler.valueAxes[0].axisScale.getYMin(); const max = vis.handler.valueAxes[0].axisScale.getYMax(); @@ -183,16 +183,16 @@ describe('Vislib Line Chart', function() { }); }); }); - [0, 2, 4, 8].forEach(function(boundsMarginValue) { - describe('defaultYExtents is true and boundsMargin is defined', function() { - beforeEach(function() { + [0, 2, 4, 8].forEach(function (boundsMarginValue) { + describe('defaultYExtents is true and boundsMargin is defined', function () { + beforeEach(function () { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; vis.render(data, mockUiState); }); - it('should return yAxis extents equal to data extents with boundsMargin', function() { - vis.handler.charts.forEach(function(chart) { + it('should return yAxis extents equal to data extents with boundsMargin', function () { + vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const min = vis.handler.valueAxes[0].axisScale.getYMin(); const max = vis.handler.valueAxes[0].axisScale.getYMax(); diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/pie_chart.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/pie_chart.js index 506ad2af85c34..d245905729c7e 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vislib/visualizations/pie_chart.js @@ -30,7 +30,7 @@ const names = ['rows', 'columns', 'slices']; const sizes = [0, 5, 15, 30, 60, 120]; -describe('No global chart settings', function() { +describe('No global chart settings', function () { const visLibParams1 = { el: '
', type: 'pie', @@ -49,15 +49,15 @@ describe('No global chart settings', function() { chart1.render(pieChartMockData.rowData, mockUiState); }); - afterEach(function() { + afterEach(function () { chart1.destroy(); }); - it('should render chart titles for all charts', function() { + it('should render chart titles for all charts', function () { expect($(chart1.element).find('.visAxis__splitTitles--y').length).to.be(1); }); - describe('_validatePieData method', function() { + describe('_validatePieData method', function () { const allZeros = [ { slices: { children: [] } }, { slices: { children: [] } }, @@ -76,26 +76,26 @@ describe('No global chart settings', function() { { slices: { children: [{}] } }, ]; - it('should throw an error when all charts contain zeros', function() { - expect(function() { + it('should throw an error when all charts contain zeros', function () { + expect(function () { chart1.handler.ChartClass.prototype._validatePieData(allZeros); }).to.throwError(); }); - it('should not throw an error when only some or no charts contain zeros', function() { - expect(function() { + it('should not throw an error when only some or no charts contain zeros', function () { + expect(function () { chart1.handler.ChartClass.prototype._validatePieData(someZeros); }).to.not.throwError(); - expect(function() { + expect(function () { chart1.handler.ChartClass.prototype._validatePieData(noZeros); }).to.not.throwError(); }); }); }); -describe('Vislib PieChart Class Test Suite', function() { - ['rowData', 'columnData', 'sliceData'].forEach(function(aggItem, i) { - describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function() { +describe('Vislib PieChart Class Test Suite', function () { + ['rowData', 'columnData', 'sliceData'].forEach(function (aggItem, i) { + describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () { const mockPieData = pieChartMockData[aggItem]; const visLibParams = { @@ -111,18 +111,18 @@ describe('Vislib PieChart Class Test Suite', function() { vis.render(mockPieData, mockUiState); }); - afterEach(function() { + afterEach(function () { vis.destroy(); }); - describe('addPathEvents method', function() { + describe('addPathEvents method', function () { let path; let d3selectedPath; let onClick; let onMouseOver; - beforeEach(function() { - vis.handler.charts.forEach(function(chart) { + beforeEach(function () { + vis.handler.charts.forEach(function (chart) { path = $(chart.chartEl).find('path')[0]; d3selectedPath = d3.select(path)[0][0]; @@ -132,30 +132,28 @@ describe('Vislib PieChart Class Test Suite', function() { }); }); - it('should attach a click event', function() { - vis.handler.charts.forEach(function() { + it('should attach a click event', function () { + vis.handler.charts.forEach(function () { expect(onClick).to.be(true); }); }); - it('should attach a hover event', function() { - vis.handler.charts.forEach(function() { + it('should attach a hover event', function () { + vis.handler.charts.forEach(function () { expect(onMouseOver).to.be(true); }); }); }); - describe('addPath method', function() { + describe('addPath method', function () { let width; let height; let svg; let slices; - it('should return an SVG object', function() { - vis.handler.charts.forEach(function(chart) { - $(chart.chartEl) - .find('svg') - .empty(); + it('should return an SVG object', function () { + vis.handler.charts.forEach(function (chart) { + $(chart.chartEl).find('svg').empty(); width = $(chart.chartEl).width(); height = $(chart.chartEl).height(); svg = d3.select($(chart.chartEl).find('svg')[0]); @@ -164,18 +162,16 @@ describe('Vislib PieChart Class Test Suite', function() { }); }); - it('should draw path elements', function() { - vis.handler.charts.forEach(function(chart) { + it('should draw path elements', function () { + vis.handler.charts.forEach(function (chart) { // test whether path elements are drawn expect($(chart.chartEl).find('path').length).to.be.greaterThan(0); }); }); - it('should draw labels', function() { - vis.handler.charts.forEach(function(chart) { - $(chart.chartEl) - .find('svg') - .empty(); + it('should draw labels', function () { + vis.handler.charts.forEach(function (chart) { + $(chart.chartEl).find('svg').empty(); width = $(chart.chartEl).width(); height = $(chart.chartEl).height(); svg = d3.select($(chart.chartEl).find('svg')[0]); @@ -187,37 +183,37 @@ describe('Vislib PieChart Class Test Suite', function() { }); }); - describe('draw method', function() { - it('should return a function', function() { - vis.handler.charts.forEach(function(chart) { + describe('draw method', function () { + it('should return a function', function () { + vis.handler.charts.forEach(function (chart) { expect(_.isFunction(chart.draw())).to.be(true); }); }); }); - sizes.forEach(function(size) { - describe('containerTooSmall error', function() { - it('should throw an error', function() { + sizes.forEach(function (size) { + describe('containerTooSmall error', function () { + it('should throw an error', function () { // 20px is the minimum height and width - vis.handler.charts.forEach(function(chart) { + vis.handler.charts.forEach(function (chart) { $(chart.chartEl).height(size); $(chart.chartEl).width(size); if (size < 20) { - expect(function() { + expect(function () { chart.render(); }).to.throwError(); } }); }); - it('should not throw an error', function() { - vis.handler.charts.forEach(function(chart) { + it('should not throw an error', function () { + vis.handler.charts.forEach(function (chart) { $(chart.chartEl).height(size); $(chart.chartEl).width(size); if (size > 20) { - expect(function() { + expect(function () { chart.render(); }).to.not.throwError(); } diff --git a/src/legacy/core_plugins/kibana/public/_hacks.scss b/src/legacy/core_plugins/kibana/public/_hacks.scss deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 0bf74edc77cb6..51dedcc629c76 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -38,17 +38,22 @@ import 'uiExports/shareContextMenuExtensions'; import 'uiExports/interpreter'; import 'ui/autoload/all'; -import './management'; + import { localApplicationService } from './local_application_service'; npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('doc', 'discover', { keepPrefix: true }); npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('context', 'discover', { keepPrefix: true }); +npSetup.plugins.kibanaLegacy.forwardApp('management', 'management', (path) => { + return path.replace('/management', ''); +}); + localApplicationService.attachToAngular(routes); routes.enable(); const { config } = npSetup.plugins.kibanaLegacy; + routes.otherwise({ redirectTo: `/${config.defaultAppId || 'discover'}`, }); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index 44a3507e57aa5..59e5238578d25 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -50,7 +50,7 @@ export class LocalApplicationService { * @param angularRouteManager The current `ui/routes` instance */ attachToAngular(angularRouteManager: UIRoutes) { - npStart.plugins.kibanaLegacy.getApps().forEach(app => { + npStart.plugins.kibanaLegacy.getApps().forEach((app) => { const wrapperElementId = this.idGenerator(); angularRouteManager.when(matchAllWithPrefix(app), { outerAngularWrapperRoute: true, @@ -87,7 +87,7 @@ export class LocalApplicationService { }); if (app.updater$) { - app.updater$.subscribe(updater => { + app.updater$.subscribe((updater) => { const updatedFields = updater(app); if (updatedFields && updatedFields.activeUrl) { npStart.core.chrome.navLinks.update(app.navLinkId || app.id, { @@ -98,7 +98,7 @@ export class LocalApplicationService { } }); - npStart.plugins.kibanaLegacy.getForwards().forEach(forwardDefinition => { + npStart.plugins.kibanaLegacy.getForwards().forEach((forwardDefinition) => { angularRouteManager.when(matchAllWithPrefix(forwardDefinition.legacyAppId), { outerAngularWrapperRoute: true, reloadOnSearch: false, diff --git a/src/legacy/core_plugins/kibana/public/management/_hacks.scss b/src/legacy/core_plugins/kibana/public/management/_hacks.scss deleted file mode 100644 index 59af9c9617a30..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/_hacks.scss +++ /dev/null @@ -1,29 +0,0 @@ -// SASSTODO: figure out why this is needed -kbn-management-app, -kbn-management-landing, -kbn-management-indices, -kbn-management-indices-edit, -kbn-management-indices-create, -kbn-management-advanced, -kbn-management-objects, -kbn-management-objects-view { - display: block; -} - -#management-landing { - display: flex; -} - -.kbn-management-tab:first-letter { - text-transform: capitalize; -} - -// SASSTODO: Remove when this is replaced with EuiCode -kbn-management-objects-view { - .ace_editor { height: 300px; } -} - -// Hack because the management wrapper is flat HTML and needs a class -.mgtPage__body { - max-width: map-get($euiBreakpoints, 'xl'); -} diff --git a/src/legacy/core_plugins/kibana/public/management/_management_app.scss b/src/legacy/core_plugins/kibana/public/management/_management_app.scss deleted file mode 100644 index bd3cabbc574d3..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/_management_app.scss +++ /dev/null @@ -1,69 +0,0 @@ -.mgtPanel { - margin-bottom: $euiSize; - background: $euiColorEmptyShade; -} - -/** - * 1. Override kuiPanelBody styles to accommodate padding of items within the panel body.. - */ -.mgtPanel__body { - padding: 5px 10px; /* 1 */ -} - -/** - * 1. Create vertical space between items when they wrap. - */ -.mgtPanel__item { - padding: 5px 15px; /* 1 */ -} - -// SASSTODO: Remove when this is replaced by the side nav -.mgtPanel__link { - @include euiFontSizeL; - - line-height: 1.5; // Make sure the space between wrapped lines is than the vertical space between items. - - &.mgtPanel__link--disabled { - opacity: $euiColorDarkShade; - cursor: default; - - &:hover, &:visited { - color: $euiColorPrimary; - } - } -} - -// SASSTODO: Remove when this form is replaced by EUI -kbn-management-objects { - form { - margin-bottom: $euiSize; - } - .list-unstyled { - li { - border-bottom: $euiBorderThin; - padding: $euiSizeS; - } - } - .empty { - color: $euiColorDarkShade; - } - - .item { - padding: $euiSizeM; - - .item-title { - margin-left: $euiSizeL; - } - - .actions { - margin-top: $euiSizeXS; - } - } - - .header { - .title, .controls { - padding-right: 1em; - display: inline-block; - } - } -} diff --git a/src/legacy/core_plugins/kibana/public/management/app.html b/src/legacy/core_plugins/kibana/public/management/app.html deleted file mode 100644 index 11198c02960c7..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/app.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js deleted file mode 100644 index ff253629a4825..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import uiRoutes from 'ui/routes'; -import { I18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -import appTemplate from './app.html'; -import landingTemplate from './landing.html'; -import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { ManagementSidebarNav } from '../../../../../plugins/management/public'; -import { timefilter } from 'ui/timefilter'; -import { - EuiPageContent, - EuiTitle, - EuiText, - EuiSpacer, - EuiIcon, - EuiHorizontalRule, -} from '@elastic/eui'; -import { npStart } from 'ui/new_platform'; - -const SIDENAV_ID = 'management-sidenav'; -const LANDING_ID = 'management-landing'; - -uiRoutes.when('/management', { - template: landingTemplate, - k7Breadcrumbs: () => [MANAGEMENT_BREADCRUMB], -}); - -uiRoutes.when('/management/:section', { - redirectTo: '/management', -}); - -export function updateLandingPage(version) { - const node = document.getElementById(LANDING_ID); - if (!node) { - return; - } - - render( - - -
-
- - - -

- -

-
- - - -
- - - - -

- -

-
-
-
-
, - node - ); -} - -export function updateSidebar(legacySections, id) { - const node = document.getElementById(SIDENAV_ID); - if (!node) { - return; - } - - render( - - - , - node - ); -} - -export const destroyReact = id => { - const node = document.getElementById(id); - node && unmountComponentAtNode(node); -}; - -uiModules.get('apps/management').directive('kbnManagementApp', function($location) { - return { - restrict: 'E', - template: appTemplate, - transclude: true, - scope: { - sectionName: '@section', - omitPages: '@omitBreadcrumbPages', - pageTitle: '=', - }, - - link: function($scope) { - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - $scope.sections = management.visibleItems; - $scope.section = management.getSection($scope.sectionName) || management; - - if ($scope.section) { - $scope.section.items.forEach(item => { - item.active = `#${$location.path()}`.indexOf(item.url) > -1; - }); - } - - updateSidebar($scope.sections, $scope.section.id); - $scope.$on('$destroy', () => destroyReact(SIDENAV_ID)); - management.addListener(() => updateSidebar(management.visibleItems, $scope.section.id)); - - updateLandingPage($scope.$root.chrome.getKibanaVersion()); - $scope.$on('$destroy', () => destroyReact(LANDING_ID)); - }, - }; -}); - -uiModules.get('apps/management').directive('kbnManagementLanding', function(kbnVersion) { - return { - restrict: 'E', - link: function($scope) { - $scope.sections = management.visibleItems; - $scope.kbnVersion = kbnVersion; - }, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/index.scss b/src/legacy/core_plugins/kibana/public/management/index.scss index 123580c0b7907..fb267b714f1c9 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.scss +++ b/src/legacy/core_plugins/kibana/public/management/index.scss @@ -7,9 +7,7 @@ // mgtChart__legend--small // mgtChart__legend-isLoading -@import 'hacks'; - // Core -@import 'management_app'; @import '../../../../../plugins/advanced_settings/public/index'; + @import 'sections/index_patterns/index'; diff --git a/src/legacy/core_plugins/kibana/public/management/landing.html b/src/legacy/core_plugins/kibana/public/management/landing.html deleted file mode 100644 index 39459b26f7415..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/landing.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts deleted file mode 100644 index 587a372f91555..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { npSetup } from 'ui/new_platform'; - -const registry = npSetup.plugins.savedObjectsManagement?.serviceRegistry; - -export const savedObjectManagementRegistry = registry!; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts deleted file mode 100644 index 81184d6fdd1a3..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export interface IndexPatternCreationOption { - text: string; - description?: string; - onClick: () => void; -} - -export interface IndexPattern { - id: string; - title: string; - url: string; - active: boolean; - default: boolean; - tag?: string[]; - sort: string; -} diff --git a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js index 64676b1bce75c..4df0e7a140205 100644 --- a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js +++ b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js @@ -69,7 +69,7 @@ const savedObjectsManagement = getManagementaMock({ }, getInAppUrl(obj) { return { - path: `/app/kibana#/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, + path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, uiCapabilitiesPath: 'management.kibana.index_patterns', }; }, @@ -325,7 +325,7 @@ describe('findRelationships', () => { title: 'My Index Pattern', editUrl: '/management/kibana/indexPatterns/patterns/1', inAppUrl: { - path: '/app/kibana#/management/kibana/indexPatterns/patterns/1', + path: '/app/management/kibana/indexPatterns/patterns/1', uiCapabilitiesPath: 'management.kibana.index_patterns', }, }, @@ -439,7 +439,7 @@ describe('findRelationships', () => { title: 'My Index Pattern', editUrl: '/management/kibana/indexPatterns/patterns/1', inAppUrl: { - path: '/app/kibana#/management/kibana/indexPatterns/patterns/1', + path: '/app/management/kibana/indexPatterns/patterns/1', uiCapabilitiesPath: 'management.kibana.index_patterns', }, }, diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts index 8aedc6f7332dc..d1be3d64fdb3f 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts +++ b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts @@ -103,13 +103,13 @@ const data: Array> = [ test('collects dashboard and all dependencies', async () => { const savedObjectClient = savedObjectsClientMock.create(); - savedObjectClient.bulkGet.mockImplementation(objects => { + savedObjectClient.bulkGet.mockImplementation((objects) => { if (!objects) { throw new Error('Invalid test data'); } return Promise.resolve({ saved_objects: objects.map( - (obj: any) => data.find(row => row.id === obj.id && row.type === obj.type)! + (obj: any) => data.find((row) => row.id === obj.id && row.type === obj.type)! ), }); }); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts index 9d620b69bf2e3..e44db901a0cb8 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts +++ b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts @@ -40,7 +40,7 @@ export async function collectReferencesDeep( for (const reference of references) { const isDuplicate = queue .concat(result) - .some(obj => obj.type === reference.type && obj.id === reference.id); + .some((obj) => obj.type === reference.type && obj.id === reference.id); if (isDuplicate) { continue; } diff --git a/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js b/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js index 8df55aabb5554..913ebff588f84 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js +++ b/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js @@ -25,7 +25,7 @@ export async function exportDashboards(req) { const config = req.server.config(); const savedObjectsClient = req.getSavedObjectsClient(); - const objectsToExport = ids.map(id => ({ id, type: 'dashboard' })); + const objectsToExport = ids.map((id) => ({ id, type: 'dashboard' })); const objects = await collectReferencesDeep(savedObjectsClient, objectsToExport); return { diff --git a/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.js b/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.js index 4c68df7a1a960..7c28b184144f1 100644 --- a/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.js +++ b/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.js @@ -32,8 +32,8 @@ export async function importDashboards(req) { // need to set migrationVersion to something other than undefined, so that imported // docs are not seen as automatically up-to-date. const docs = payload.objects - .filter(item => !exclude.includes(item.type)) - .map(doc => ({ ...doc, migrationVersion: doc.migrationVersion || {} })); + .filter((item) => !exclude.includes(item.type)) + .map((doc) => ({ ...doc, migrationVersion: doc.migrationVersion || {} })); const results = await savedObjectsClient.bulkCreate(docs, { overwrite }); return { objects: results.saved_objects }; diff --git a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js b/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js index 585d9ce938710..e0a6c574b7ad8 100644 --- a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js +++ b/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js @@ -43,16 +43,16 @@ export async function findRelationships(type, id, options = {}) { return [].concat( referencedObjects.saved_objects - .map(obj => injectMetaAttributes(obj, savedObjectsManagement)) + .map((obj) => injectMetaAttributes(obj, savedObjectsManagement)) .map(extractCommonProperties) - .map(obj => ({ + .map((obj) => ({ ...obj, relationship: 'child', })), referencedResponse.saved_objects - .map(obj => injectMetaAttributes(obj, savedObjectsManagement)) + .map((obj) => injectMetaAttributes(obj, savedObjectsManagement)) .map(extractCommonProperties) - .map(obj => ({ + .map((obj) => ({ ...obj, relationship: 'parent', })) diff --git a/src/legacy/core_plugins/kibana/server/routes/api/export/index.js b/src/legacy/core_plugins/kibana/server/routes/api/export/index.js index b939e06b7bdb9..ef556ed53f4fc 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/export/index.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/export/index.js @@ -38,7 +38,7 @@ export function exportApi(server) { method: ['GET'], handler: async (req, h) => { const currentDate = moment.utc(); - return exportDashboards(req).then(resp => { + return exportDashboards(req).then((resp) => { const json = JSON.stringify(resp, null, ' '); const filename = `kibana-dashboards.${currentDate.format('YYYY-MM-DD-HH-mm-ss')}.json`; return h diff --git a/src/legacy/core_plugins/kibana/server/routes/api/import/index.js b/src/legacy/core_plugins/kibana/server/routes/api/import/index.js index d0fa0bb4489cb..b7efb7da3c5a9 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/import/index.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/import/index.js @@ -38,7 +38,7 @@ export function importApi(server) { tags: ['api'], }, - handler: async req => { + handler: async (req) => { return await importDashboards(req); }, }); diff --git a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js index 69ee2d80ea0a6..b7af6a73e1bc1 100644 --- a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js @@ -18,53 +18,32 @@ */ import moment from 'moment-timezone'; -import numeralLanguages from '@elastic/numeral/languages'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { isRelativeUrl } from '../../../../core/server'; -import { DEFAULT_QUERY_LANGUAGE } from '../../../../plugins/data/common'; export function getUiSettingDefaults() { const weekdays = moment.weekdays().slice(); const [defaultWeekday] = weekdays; - // We add the `en` key manually here, since that's not a real numeral locale, but the - // default fallback in case the locale is not found. - const numeralLanguageIds = [ - 'en', - ...numeralLanguages.map(function(numeralLanguage) { - return numeralLanguage.id; - }), - ]; - - const luceneQueryLanguageLabel = i18n.translate( - 'kbn.advancedSettings.searchQueryLanguageLucene', - { - defaultMessage: 'Lucene', - } - ); - - const queryLanguageSettingName = i18n.translate('kbn.advancedSettings.searchQueryLanguageTitle', { - defaultMessage: 'Query language', - }); - - const requestPreferenceOptionLabels = { - sessionId: i18n.translate('kbn.advancedSettings.courier.requestPreferenceSessionId', { - defaultMessage: 'Session ID', - }), - custom: i18n.translate('kbn.advancedSettings.courier.requestPreferenceCustom', { - defaultMessage: 'Custom', - }), - none: i18n.translate('kbn.advancedSettings.courier.requestPreferenceNone', { - defaultMessage: 'None', - }), - }; // wrapped in provider so that a new instance is given to each app/test return { buildNum: { readonly: true, }, + 'state:storeInSessionStorage': { + name: i18n.translate('kbn.advancedSettings.storeUrlTitle', { + defaultMessage: 'Store URLs in session storage', + }), + value: false, + description: i18n.translate('kbn.advancedSettings.storeUrlText', { + defaultMessage: + 'The URL can sometimes grow to be too large for some browsers to handle. ' + + 'To counter-act this we are testing if storing parts of the URL in session storage could help. ' + + 'Please let us know how it goes!', + }), + }, defaultRoute: { name: i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteTitle', { defaultMessage: 'Default route', @@ -89,83 +68,6 @@ export function getUiSettingDefaults() { 'The route must be a relative URL.', }), }, - 'query:queryString:options': { - name: i18n.translate('kbn.advancedSettings.query.queryStringOptionsTitle', { - defaultMessage: 'Query string options', - }), - value: '{ "analyze_wildcard": true }', - description: i18n.translate('kbn.advancedSettings.query.queryStringOptionsText', { - defaultMessage: - '{optionsLink} for the lucene query string parser. Is only used when "{queryLanguage}" is set ' + - 'to {luceneLanguage}.', - description: - 'Part of composite text: kbn.advancedSettings.query.queryStringOptions.optionsLinkText + ' + - 'kbn.advancedSettings.query.queryStringOptionsText', - values: { - optionsLink: - '' + - i18n.translate('kbn.advancedSettings.query.queryStringOptions.optionsLinkText', { - defaultMessage: 'Options', - }) + - '', - luceneLanguage: luceneQueryLanguageLabel, - queryLanguage: queryLanguageSettingName, - }, - }), - type: 'json', - }, - 'query:allowLeadingWildcards': { - name: i18n.translate('kbn.advancedSettings.query.allowWildcardsTitle', { - defaultMessage: 'Allow leading wildcards in query', - }), - value: true, - description: i18n.translate('kbn.advancedSettings.query.allowWildcardsText', { - defaultMessage: - 'When set, * is allowed as the first character in a query clause. ' + - 'Currently only applies when experimental query features are enabled in the query bar. ' + - 'To disallow leading wildcards in basic lucene queries, use {queryStringOptionsPattern}.', - values: { - queryStringOptionsPattern: 'query:queryString:options', - }, - }), - }, - 'search:queryLanguage': { - name: queryLanguageSettingName, - value: DEFAULT_QUERY_LANGUAGE, - description: i18n.translate('kbn.advancedSettings.searchQueryLanguageText', { - defaultMessage: - 'Query language used by the query bar. KQL is a new language built specifically for Kibana.', - }), - type: 'select', - options: ['lucene', 'kuery'], - optionLabels: { - lucene: luceneQueryLanguageLabel, - kuery: i18n.translate('kbn.advancedSettings.searchQueryLanguageKql', { - defaultMessage: 'KQL', - }), - }, - }, - 'sort:options': { - name: i18n.translate('kbn.advancedSettings.sortOptionsTitle', { - defaultMessage: 'Sort options', - }), - value: '{ "unmapped_type": "boolean" }', - description: i18n.translate('kbn.advancedSettings.sortOptionsText', { - defaultMessage: '{optionsLink} for the Elasticsearch sort parameter', - description: - 'Part of composite text: kbn.advancedSettings.sortOptions.optionsLinkText + ' + - 'kbn.advancedSettings.sortOptionsText', - values: { - optionsLink: - '' + - i18n.translate('kbn.advancedSettings.sortOptions.optionsLinkText', { - defaultMessage: 'Options', - }) + - '', - }, - }), - type: 'json', - }, dateFormat: { name: i18n.translate('kbn.advancedSettings.dateFormatTitle', { defaultMessage: 'Date format', @@ -261,160 +163,6 @@ export function getUiSettingDefaults() { }, }), }, - defaultIndex: { - name: i18n.translate('kbn.advancedSettings.defaultIndexTitle', { - defaultMessage: 'Default index', - }), - value: null, - type: 'string', - description: i18n.translate('kbn.advancedSettings.defaultIndexText', { - defaultMessage: 'The index to access if no index is set', - }), - }, - 'courier:ignoreFilterIfFieldNotInIndex': { - name: i18n.translate('kbn.advancedSettings.courier.ignoreFilterTitle', { - defaultMessage: 'Ignore filter(s)', - }), - value: false, - description: i18n.translate('kbn.advancedSettings.courier.ignoreFilterText', { - defaultMessage: - 'This configuration enhances support for dashboards containing visualizations accessing dissimilar indexes. ' + - 'When disabled, all filters are applied to all visualizations. ' + - 'When enabled, filter(s) will be ignored for a visualization ' + - `when the visualization's index does not contain the filtering field.`, - }), - category: ['search'], - }, - 'courier:setRequestPreference': { - name: i18n.translate('kbn.advancedSettings.courier.requestPreferenceTitle', { - defaultMessage: 'Request preference', - }), - value: 'sessionId', - options: ['sessionId', 'custom', 'none'], - optionLabels: requestPreferenceOptionLabels, - type: 'select', - description: i18n.translate('kbn.advancedSettings.courier.requestPreferenceText', { - defaultMessage: `Allows you to set which shards handle your search requests. -
    -
  • {sessionId}: restricts operations to execute all search requests on the same shards. - This has the benefit of reusing shard caches across requests.
  • -
  • {custom}: allows you to define a your own preference. - Use courier:customRequestPreference to customize your preference value.
  • -
  • {none}: means do not set a preference. - This might provide better performance because requests can be spread across all shard copies. - However, results might be inconsistent because different shards might be in different refresh states.
  • -
`, - values: { - sessionId: requestPreferenceOptionLabels.sessionId, - custom: requestPreferenceOptionLabels.custom, - none: requestPreferenceOptionLabels.none, - }, - }), - category: ['search'], - }, - 'courier:customRequestPreference': { - name: i18n.translate('kbn.advancedSettings.courier.customRequestPreferenceTitle', { - defaultMessage: 'Custom request preference', - }), - value: '_local', - type: 'string', - description: i18n.translate('kbn.advancedSettings.courier.customRequestPreferenceText', { - defaultMessage: - '{requestPreferenceLink} used when {setRequestReferenceSetting} is set to {customSettingValue}.', - description: - 'Part of composite text: kbn.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText + ' + - 'kbn.advancedSettings.courier.customRequestPreferenceText', - values: { - setRequestReferenceSetting: 'courier:setRequestPreference', - customSettingValue: '"custom"', - requestPreferenceLink: - '' + - i18n.translate( - 'kbn.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText', - { - defaultMessage: 'Request Preference', - } - ) + - '', - }, - }), - category: ['search'], - }, - 'courier:maxConcurrentShardRequests': { - name: i18n.translate('kbn.advancedSettings.courier.maxRequestsTitle', { - defaultMessage: 'Max Concurrent Shard Requests', - }), - value: 0, - type: 'number', - description: i18n.translate('kbn.advancedSettings.courier.maxRequestsText', { - defaultMessage: - 'Controls the {maxRequestsLink} setting used for _msearch requests sent by Kibana. ' + - 'Set to 0 to disable this config and use the Elasticsearch default.', - values: { - maxRequestsLink: `max_concurrent_shard_requests`, - }, - }), - category: ['search'], - }, - 'courier:batchSearches': { - name: i18n.translate('kbn.advancedSettings.courier.batchSearchesTitle', { - defaultMessage: 'Batch concurrent searches', - }), - value: false, - type: 'boolean', - description: i18n.translate('kbn.advancedSettings.courier.batchSearchesText', { - defaultMessage: `When disabled, dashboard panels will load individually, and search requests will terminate when users navigate - away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, and - searches will not terminate.`, - }), - deprecation: { - message: i18n.translate('kbn.advancedSettings.courier.batchSearchesTextDeprecation', { - defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.', - }), - docLinksKey: 'kibanaSearchSettings', - }, - category: ['search'], - }, - 'search:includeFrozen': { - name: 'Search in frozen indices', - description: `Will include frozen indices in results if enabled. Searching through frozen indices - might increase the search time.`, - value: false, - category: ['search'], - }, - 'histogram:barTarget': { - name: i18n.translate('kbn.advancedSettings.histogram.barTargetTitle', { - defaultMessage: 'Target bars', - }), - value: 50, - description: i18n.translate('kbn.advancedSettings.histogram.barTargetText', { - defaultMessage: - 'Attempt to generate around this many bars when using "auto" interval in date histograms', - }), - }, - 'histogram:maxBars': { - name: i18n.translate('kbn.advancedSettings.histogram.maxBarsTitle', { - defaultMessage: 'Maximum bars', - }), - value: 100, - description: i18n.translate('kbn.advancedSettings.histogram.maxBarsText', { - defaultMessage: - 'Never show more than this many bars in date histograms, scale values if needed', - }), - }, - 'visualize:enableLabs': { - name: i18n.translate('kbn.advancedSettings.visualizeEnableLabsTitle', { - defaultMessage: 'Enable experimental visualizations', - }), - value: true, - description: i18n.translate('kbn.advancedSettings.visualizeEnableLabsText', { - defaultMessage: `Allows users to create, view, and edit experimental visualizations. If disabled, - only visualizations that are considered production-ready are available to the user.`, - }), - category: ['visualization'], - }, 'visualization:tileMap:maxPrecision': { name: i18n.translate('kbn.advancedSettings.visualization.tileMap.maxPrecisionTitle', { defaultMessage: 'Maximum tile map precision', @@ -493,43 +241,6 @@ export function getUiSettingDefaults() { }), category: ['visualization'], }, - 'csv:separator': { - name: i18n.translate('kbn.advancedSettings.csv.separatorTitle', { - defaultMessage: 'CSV separator', - }), - value: ',', - description: i18n.translate('kbn.advancedSettings.csv.separatorText', { - defaultMessage: 'Separate exported values with this string', - }), - }, - 'csv:quoteValues': { - name: i18n.translate('kbn.advancedSettings.csv.quoteValuesTitle', { - defaultMessage: 'Quote CSV values', - }), - value: true, - description: i18n.translate('kbn.advancedSettings.csv.quoteValuesText', { - defaultMessage: 'Should values be quoted in csv exports?', - }), - }, - 'history:limit': { - name: i18n.translate('kbn.advancedSettings.historyLimitTitle', { - defaultMessage: 'History limit', - }), - value: 10, - description: i18n.translate('kbn.advancedSettings.historyLimitText', { - defaultMessage: - 'In fields that have history (e.g. query inputs), show this many recent values', - }), - }, - 'shortDots:enable': { - name: i18n.translate('kbn.advancedSettings.shortenFieldsTitle', { - defaultMessage: 'Shorten fields', - }), - value: false, - description: i18n.translate('kbn.advancedSettings.shortenFieldsText', { - defaultMessage: 'Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz', - }), - }, 'truncate:maxHeight': { name: i18n.translate('kbn.advancedSettings.maxCellHeightTitle', { defaultMessage: 'Maximum table cell height', @@ -540,138 +251,6 @@ export function getUiSettingDefaults() { 'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation', }), }, - 'format:defaultTypeMap': { - name: i18n.translate('kbn.advancedSettings.format.defaultTypeMapTitle', { - defaultMessage: 'Field type format name', - }), - value: `{ - "ip": { "id": "ip", "params": {} }, - "date": { "id": "date", "params": {} }, - "date_nanos": { "id": "date_nanos", "params": {}, "es": true }, - "number": { "id": "number", "params": {} }, - "boolean": { "id": "boolean", "params": {} }, - "_source": { "id": "_source", "params": {} }, - "_default_": { "id": "string", "params": {} } -}`, - type: 'json', - description: i18n.translate('kbn.advancedSettings.format.defaultTypeMapText', { - defaultMessage: - 'Map of the format name to use by default for each field type. ' + - '{defaultFormat} is used if the field type is not mentioned explicitly', - values: { - defaultFormat: '"_default_"', - }, - }), - }, - 'format:number:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.numberFormatTitle', { - defaultMessage: 'Number format', - }), - value: '0,0.[000]', - type: 'string', - description: i18n.translate('kbn.advancedSettings.format.numberFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "number" format', - description: - 'Part of composite text: kbn.advancedSettings.format.numberFormatText + ' + - 'kbn.advancedSettings.format.numberFormat.numeralFormatLinkText', - values: { - numeralFormatLink: - '' + - i18n.translate('kbn.advancedSettings.format.numberFormat.numeralFormatLinkText', { - defaultMessage: 'numeral format', - }) + - '', - }, - }), - }, - 'format:bytes:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.bytesFormatTitle', { - defaultMessage: 'Bytes format', - }), - value: '0,0.[0]b', - type: 'string', - description: i18n.translate('kbn.advancedSettings.format.bytesFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "bytes" format', - description: - 'Part of composite text: kbn.advancedSettings.format.bytesFormatText + ' + - 'kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText', - values: { - numeralFormatLink: - '' + - i18n.translate('kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText', { - defaultMessage: 'numeral format', - }) + - '', - }, - }), - }, - 'format:percent:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.percentFormatTitle', { - defaultMessage: 'Percent format', - }), - value: '0,0.[000]%', - type: 'string', - description: i18n.translate('kbn.advancedSettings.format.percentFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "percent" format', - description: - 'Part of composite text: kbn.advancedSettings.format.percentFormatText + ' + - 'kbn.advancedSettings.format.percentFormat.numeralFormatLinkText', - values: { - numeralFormatLink: - '' + - i18n.translate('kbn.advancedSettings.format.percentFormat.numeralFormatLinkText', { - defaultMessage: 'numeral format', - }) + - '', - }, - }), - }, - 'format:currency:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.currencyFormatTitle', { - defaultMessage: 'Currency format', - }), - value: '($0,0.[00])', - type: 'string', - description: i18n.translate('kbn.advancedSettings.format.currencyFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "currency" format', - description: - 'Part of composite text: kbn.advancedSettings.format.currencyFormatText + ' + - 'kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText', - values: { - numeralFormatLink: - '' + - i18n.translate('kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText', { - defaultMessage: 'numeral format', - }) + - '', - }, - }), - }, - 'format:number:defaultLocale': { - name: i18n.translate('kbn.advancedSettings.format.formattingLocaleTitle', { - defaultMessage: 'Formatting locale', - }), - value: 'en', - type: 'select', - options: numeralLanguageIds, - optionLabels: Object.fromEntries( - numeralLanguages.map(language => [language.id, language.name]) - ), - description: i18n.translate('kbn.advancedSettings.format.formattingLocaleText', { - defaultMessage: `{numeralLanguageLink} locale`, - description: - 'Part of composite text: kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText + ' + - 'kbn.advancedSettings.format.formattingLocaleText', - values: { - numeralLanguageLink: - '' + - i18n.translate('kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText', { - defaultMessage: 'Numeral language', - }) + - '', - }, - }), - }, 'timepicker:timeDefaults': { name: i18n.translate('kbn.advancedSettings.timepicker.timeDefaultsTitle', { defaultMessage: 'Time filter defaults', @@ -686,120 +265,6 @@ export function getUiSettingDefaults() { }), requiresPageReload: true, }, - 'timepicker:refreshIntervalDefaults': { - name: i18n.translate('kbn.advancedSettings.timepicker.refreshIntervalDefaultsTitle', { - defaultMessage: 'Time filter refresh interval', - }), - value: `{ - "pause": false, - "value": 0 -}`, - type: 'json', - description: i18n.translate('kbn.advancedSettings.timepicker.refreshIntervalDefaultsText', { - defaultMessage: `The timefilter's default refresh interval`, - }), - requiresPageReload: true, - }, - 'timepicker:quickRanges': { - name: i18n.translate('kbn.advancedSettings.timepicker.quickRangesTitle', { - defaultMessage: 'Time filter quick ranges', - }), - value: JSON.stringify( - [ - { - from: 'now/d', - to: 'now/d', - display: i18n.translate('kbn.advancedSettings.timepicker.today', { - defaultMessage: 'Today', - }), - }, - { - from: 'now/w', - to: 'now/w', - display: i18n.translate('kbn.advancedSettings.timepicker.thisWeek', { - defaultMessage: 'This week', - }), - }, - { - from: 'now-15m', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last15Minutes', { - defaultMessage: 'Last 15 minutes', - }), - }, - { - from: 'now-30m', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last30Minutes', { - defaultMessage: 'Last 30 minutes', - }), - }, - { - from: 'now-1h', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last1Hour', { - defaultMessage: 'Last 1 hour', - }), - }, - { - from: 'now-24h', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last24Hours', { - defaultMessage: 'Last 24 hours', - }), - }, - { - from: 'now-7d', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last7Days', { - defaultMessage: 'Last 7 days', - }), - }, - { - from: 'now-30d', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last30Days', { - defaultMessage: 'Last 30 days', - }), - }, - { - from: 'now-90d', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last90Days', { - defaultMessage: 'Last 90 days', - }), - }, - { - from: 'now-1y', - to: 'now', - display: i18n.translate('kbn.advancedSettings.timepicker.last1Year', { - defaultMessage: 'Last 1 year', - }), - }, - ], - null, - 2 - ), - type: 'json', - description: i18n.translate('kbn.advancedSettings.timepicker.quickRangesText', { - defaultMessage: - 'The list of ranges to show in the Quick section of the time filter. This should be an array of objects, ' + - 'with each object containing "from", "to" (see {acceptedFormatsLink}), and ' + - '"display" (the title to be displayed).', - description: - 'Part of composite text: kbn.advancedSettings.timepicker.quickRangesText + ' + - 'kbn.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', - values: { - acceptedFormatsLink: - `` + - i18n.translate('kbn.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', { - defaultMessage: 'accepted formats', - }) + - '', - }, - }), - }, 'theme:darkMode': { name: i18n.translate('kbn.advancedSettings.darkModeTitle', { defaultMessage: 'Dark mode', @@ -822,26 +287,6 @@ export function getUiSettingDefaults() { }), requiresPageReload: true, }, - 'filters:pinnedByDefault': { - name: i18n.translate('kbn.advancedSettings.pinFiltersTitle', { - defaultMessage: 'Pin filters by default', - }), - value: false, - description: i18n.translate('kbn.advancedSettings.pinFiltersText', { - defaultMessage: 'Whether the filters should have a global state (be pinned) by default', - }), - }, - 'filterEditor:suggestValues': { - name: i18n.translate('kbn.advancedSettings.suggestFilterValuesTitle', { - defaultMessage: 'Filter editor suggest values', - description: '"Filter editor" refers to the UI you create filters in.', - }), - value: true, - description: i18n.translate('kbn.advancedSettings.suggestFilterValuesText', { - defaultMessage: - 'Set this property to false to prevent the filter editor from suggesting values for fields.', - }), - }, 'notifications:banner': { name: i18n.translate('kbn.advancedSettings.notifications.bannerTitle', { defaultMessage: 'Custom banner notification', @@ -930,28 +375,6 @@ export function getUiSettingDefaults() { type: 'number', category: ['notifications'], }, - 'state:storeInSessionStorage': { - name: i18n.translate('kbn.advancedSettings.storeUrlTitle', { - defaultMessage: 'Store URLs in session storage', - }), - value: false, - description: i18n.translate('kbn.advancedSettings.storeUrlText', { - defaultMessage: - 'The URL can sometimes grow to be too large for some browsers to handle. ' + - 'To counter-act this we are testing if storing parts of the URL in session storage could help. ' + - 'Please let us know how it goes!', - }), - }, - 'indexPattern:placeholder': { - name: i18n.translate('kbn.advancedSettings.indexPatternPlaceholderTitle', { - defaultMessage: 'Index pattern placeholder', - }), - value: '', - description: i18n.translate('kbn.advancedSettings.indexPatternPlaceholderText', { - defaultMessage: - 'The placeholder for the "Index pattern name" field in "Management > Index Patterns > Create Index Pattern".', - }), - }, 'accessibility:disableAnimations': { name: i18n.translate('kbn.advancedSettings.disableAnimationsTitle', { defaultMessage: 'Disable Animations', diff --git a/src/legacy/core_plugins/status_page/index.js b/src/legacy/core_plugins/status_page/index.js index fc3bfc4c7c6c6..01991d8439a04 100644 --- a/src/legacy/core_plugins/status_page/index.js +++ b/src/legacy/core_plugins/status_page/index.js @@ -17,7 +17,7 @@ * under the License. */ -export default function(kibana) { +export default function (kibana) { return new kibana.Plugin({ uiExports: { app: { diff --git a/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap b/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap index b88210758a00d..7d4b245021c4c 100644 --- a/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap +++ b/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap @@ -4,7 +4,7 @@ exports[`byte metric 1`] = ` `; diff --git a/src/legacy/core_plugins/status_page/public/components/metric_tiles.js b/src/legacy/core_plugins/status_page/public/components/metric_tiles.js index 8500bfea63aba..6cde975875ad1 100644 --- a/src/legacy/core_plugins/status_page/public/components/metric_tiles.js +++ b/src/legacy/core_plugins/status_page/public/components/metric_tiles.js @@ -36,7 +36,7 @@ export class MetricTile extends Component { const metrics = [].concat(value); return metrics - .map(function(metric) { + .map(function (metric) { return formatNumber(metric, type); }) .join(', '); @@ -54,7 +54,7 @@ Wrapper component that simply maps each metric to MetricTile inside a FlexGroup */ const MetricTiles = ({ metrics }) => ( - {metrics.map(metric => ( + {metrics.map((metric) => ( diff --git a/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js b/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js index 74dfbd4119f14..13d0a61bbc96f 100644 --- a/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js +++ b/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js @@ -47,20 +47,20 @@ const MS_METRIC = { test('general metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); test('byte metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); test('float metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); test('millisecond metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); diff --git a/src/legacy/core_plugins/status_page/public/components/status_app.js b/src/legacy/core_plugins/status_page/public/components/status_app.js index 353938e783a47..a6b0321e53a8f 100644 --- a/src/legacy/core_plugins/status_page/public/components/status_app.js +++ b/src/legacy/core_plugins/status_page/public/components/status_app.js @@ -53,7 +53,7 @@ class StatusApp extends Component { }; } - componentDidMount = async function() { + componentDidMount = async function () { const data = await loadStatus(); if (data) { diff --git a/src/legacy/core_plugins/status_page/public/components/status_table.js b/src/legacy/core_plugins/status_page/public/components/status_table.js index 3eb04409f5f89..68b93153951cb 100644 --- a/src/legacy/core_plugins/status_page/public/components/status_table.js +++ b/src/legacy/core_plugins/status_page/public/components/status_table.js @@ -37,7 +37,7 @@ class StatusTable extends Component { { field: 'state', name: '', - render: state => , + render: (state) => , width: '32px', }, { @@ -51,7 +51,7 @@ class StatusTable extends Component { name: i18n.translate('statusPage.statusTable.columns.statusHeader', { defaultMessage: 'Status', }), - render: state => {state.message}, + render: (state) => {state.message}, }, ]; diff --git a/src/legacy/core_plugins/status_page/public/lib/format_number.js b/src/legacy/core_plugins/status_page/public/lib/format_number.js index c5f23a9a9ef6d..4a8be4fc48a15 100644 --- a/src/legacy/core_plugins/status_page/public/lib/format_number.js +++ b/src/legacy/core_plugins/status_page/public/lib/format_number.js @@ -17,7 +17,7 @@ * under the License. */ -import numeral from 'numeral'; +import numeral from '@elastic/numeral'; export default function formatNumber(num, which) { let format = '0.00'; diff --git a/src/legacy/core_plugins/status_page/public/lib/format_number.test.js b/src/legacy/core_plugins/status_page/public/lib/format_number.test.js index 78f17ffa76f39..f70377dcba241 100644 --- a/src/legacy/core_plugins/status_page/public/lib/format_number.test.js +++ b/src/legacy/core_plugins/status_page/public/lib/format_number.test.js @@ -21,42 +21,42 @@ import formatNumber from './format_number'; describe('format byte', () => { test('zero', () => { - expect(formatNumber(0, 'byte')).toEqual('0.00 B'); + expect(formatNumber(0, 'byte')).toMatchInlineSnapshot(`"0.00 B"`); }); test('mb', () => { - expect(formatNumber(181142512, 'byte')).toEqual('181.14 MB'); + expect(formatNumber(181142512, 'byte')).toMatchInlineSnapshot(`"172.75 MB"`); }); test('gb', () => { - expect(formatNumber(273727485000, 'byte')).toEqual('273.73 GB'); + expect(formatNumber(273727485000, 'byte')).toMatchInlineSnapshot(`"254.93 GB"`); }); }); describe('format ms', () => { test('zero', () => { - expect(formatNumber(0, 'ms')).toEqual('0.00 ms'); + expect(formatNumber(0, 'ms')).toMatchInlineSnapshot(`"0.00 ms"`); }); test('sub ms', () => { - expect(formatNumber(0.128, 'ms')).toEqual('0.13 ms'); + expect(formatNumber(0.128, 'ms')).toMatchInlineSnapshot(`"0.13 ms"`); }); test('many ms', () => { - expect(formatNumber(3030.284, 'ms')).toEqual('3030.28 ms'); + expect(formatNumber(3030.284, 'ms')).toMatchInlineSnapshot(`"3030.28 ms"`); }); }); describe('format integer', () => { test('zero', () => { - expect(formatNumber(0, 'integer')).toEqual('0'); + expect(formatNumber(0, 'integer')).toMatchInlineSnapshot(`"0"`); }); test('sub integer', () => { - expect(formatNumber(0.728, 'integer')).toEqual('1'); + expect(formatNumber(0.728, 'integer')).toMatchInlineSnapshot(`"1"`); }); test('many integer', () => { - expect(formatNumber(3030.284, 'integer')).toEqual('3030'); + expect(formatNumber(3030.284, 'integer')).toMatchInlineSnapshot(`"3030"`); }); }); diff --git a/src/legacy/core_plugins/status_page/public/lib/load_status.test.js b/src/legacy/core_plugins/status_page/public/lib/load_status.test.js index 2a37b443b8653..a0f1930ca7667 100644 --- a/src/legacy/core_plugins/status_page/public/lib/load_status.test.js +++ b/src/legacy/core_plugins/status_page/public/lib/load_status.test.js @@ -99,7 +99,7 @@ describe('response processing', () => { test('builds the metrics', async () => { const data = await loadStatus(mockFetch); - const names = data.metrics.map(m => m.name); + const names = data.metrics.map((m) => m.name); expect(names).toEqual([ 'Heap total', 'Heap used', @@ -109,7 +109,7 @@ describe('response processing', () => { 'Requests per second', ]); - const values = data.metrics.map(m => m.value); + const values = data.metrics.map((m) => m.value); expect(values).toEqual([1000000, 100, [4.1, 2.1, 0.1], 4000, 8000, 400]); }); }); diff --git a/src/legacy/core_plugins/status_page/public/status_page.js b/src/legacy/core_plugins/status_page/public/status_page.js index 82cd2aa5f395d..709164caa9e04 100644 --- a/src/legacy/core_plugins/status_page/public/status_page.js +++ b/src/legacy/core_plugins/status_page/public/status_page.js @@ -25,7 +25,7 @@ import template from 'plugins/status_page/status_page.html'; npStart.core.chrome.navLinks.enableForcedAppSwitcherNavigation(); -chrome.setRootTemplate(template).setRootController('ui', function($scope, buildNum, buildSha) { +chrome.setRootTemplate(template).setRootController('ui', function ($scope, buildNum, buildSha) { $scope.$$postDigest(() => { renderStatusPage(buildNum, buildSha.substr(0, 8)); $scope.$on('$destroy', destroyStatusPage); diff --git a/src/legacy/core_plugins/testbed/index.js b/src/legacy/core_plugins/testbed/index.js index 0cbd966100811..f0b61ea0c3de7 100644 --- a/src/legacy/core_plugins/testbed/index.js +++ b/src/legacy/core_plugins/testbed/index.js @@ -19,7 +19,7 @@ import { resolve } from 'path'; -export default function(kibana) { +export default function (kibana) { return new kibana.Plugin({ id: 'testbed', publicDir: resolve(__dirname, 'public'), diff --git a/src/legacy/core_plugins/tests_bundle/find_source_files.js b/src/legacy/core_plugins/tests_bundle/find_source_files.js index a834072b258a3..eed88a5ecb8b0 100644 --- a/src/legacy/core_plugins/tests_bundle/find_source_files.js +++ b/src/legacy/core_plugins/tests_bundle/find_source_files.js @@ -27,7 +27,7 @@ import glob from 'glob-all'; const findSourceFiles = async (patterns, cwd = fromRoot('.')) => { patterns = [].concat(patterns || []); - const matches = await fromNode(cb => { + const matches = await fromNode((cb) => { glob( patterns, { @@ -52,7 +52,7 @@ const findSourceFiles = async (patterns, cwd = fromRoot('.')) => { return chain(matches) .flatten() .uniq() - .map(match => resolve(cwd, match)) + .map((match) => resolve(cwd, match)) .value(); }; diff --git a/src/legacy/core_plugins/tests_bundle/index.js b/src/legacy/core_plugins/tests_bundle/index.js index 3348096c0e2f1..431c161585fe0 100644 --- a/src/legacy/core_plugins/tests_bundle/index.js +++ b/src/legacy/core_plugins/tests_bundle/index.js @@ -30,9 +30,9 @@ import { replacePlaceholder } from '../../../optimize/public_path_placeholder'; import findSourceFiles from './find_source_files'; import { createTestEntryTemplate } from './tests_entry_template'; -export default kibana => { +export default (kibana) => { return new kibana.Plugin({ - config: Joi => { + config: (Joi) => { return Joi.object({ enabled: Joi.boolean().default(true), instrument: Joi.boolean().default(false), @@ -58,8 +58,8 @@ export default kibana => { const testingPluginIds = config.get('tests_bundle.pluginId'); if (testingPluginIds) { - testingPluginIds.split(',').forEach(pluginId => { - const plugin = plugins.find(plugin => plugin.id === pluginId); + testingPluginIds.split(',').forEach((pluginId) => { + const plugin = plugins.find((plugin) => plugin.id === pluginId); if (!plugin) { throw new Error('Invalid testingPluginId :: unknown plugin ' + pluginId); @@ -134,20 +134,17 @@ export default kibana => { async handler(_, h) { const cssFiles = await globby( testingPluginIds - ? testingPluginIds.split(',').map(id => `built_assets/css/plugins/${id}/**/*.css`) + ? testingPluginIds.split(',').map((id) => `built_assets/css/plugins/${id}/**/*.css`) : `built_assets/css/**/*.css`, { cwd: fromRoot('.'), absolute: true } ); const stream = replacePlaceholder( - new MultiStream(cssFiles.map(path => createReadStream(path))), + new MultiStream(cssFiles.map((path) => createReadStream(path))), '/built_assets/css/' ); - return h - .response(stream) - .code(200) - .type('text/css'); + return h.response(stream).code(200).type('text/css'); }, }); diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index f075d8365c299..28c26f08621eb 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -19,7 +19,7 @@ import { Type } from '@kbn/config-schema'; import pkg from '../../../../package.json'; -export const createTestEntryTemplate = defaultUiSettings => bundle => ` +export const createTestEntryTemplate = (defaultUiSettings) => (bundle) => ` /** * Test entry file * diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 6c1dc81dc341b..b5501982cec09 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -70,7 +70,7 @@ routes.when('/:id?', { reloadOnSearch: false, k7Breadcrumbs: ($injector, $route) => $injector.invoke($route.current.params.id ? getSavedSheetBreadcrumbs : getCreateBreadcrumbs), - badge: uiCapabilities => { + badge: (uiCapabilities) => { if (uiCapabilities.timelion.save) { return undefined; } @@ -86,10 +86,10 @@ routes.when('/:id?', { }; }, resolve: { - savedSheet: function(redirectWhenMissing, savedSheets, $route) { + savedSheet: function (redirectWhenMissing, savedSheets, $route) { return savedSheets .get($route.current.params.id) - .then(savedSheet => { + .then((savedSheet) => { if ($route.current.params.id) { npStart.core.chrome.recentlyAccessed.add( savedSheet.getFullPath(), @@ -110,7 +110,7 @@ routes.when('/:id?', { const location = 'Timelion'; -app.controller('timelion', function( +app.controller('timelion', function ( $http, $route, $routeParams, @@ -123,7 +123,7 @@ app.controller('timelion', function( // Keeping this at app scope allows us to keep the current page when the user // switches to say, the timepicker. $scope.page = config.get('timelion:showTutorial', true) ? 1 : 0; - $scope.setPage = page => ($scope.page = page); + $scope.setPage = (page) => ($scope.page = page); timefilter.enableAutoRefreshSelector(); timefilter.enableTimeRangeSelector(); @@ -136,7 +136,7 @@ app.controller('timelion', function( $scope.topNavMenu = getTopNavMenu(); - $timeout(function() { + $timeout(function () { if (config.get('timelion:showTutorial', true)) { $scope.toggleMenu('showHelp'); } @@ -163,7 +163,7 @@ app.controller('timelion', function( description: i18n.translate('timelion.topNavMenu.newSheetButtonAriaLabel', { defaultMessage: 'New Sheet', }), - run: function() { + run: function () { kbnUrl.change('/'); }, testId: 'timelionNewButton', @@ -177,7 +177,7 @@ app.controller('timelion', function( description: i18n.translate('timelion.topNavMenu.addChartButtonAriaLabel', { defaultMessage: 'Add a chart', }), - run: function() { + run: function () { $scope.$evalAsync(() => $scope.newCell()); }, testId: 'timelionAddChartButton', @@ -205,10 +205,10 @@ app.controller('timelion', function( description: i18n.translate('timelion.topNavMenu.deleteSheetButtonAriaLabel', { defaultMessage: 'Delete current sheet', }), - disableButton: function() { + disableButton: function () { return !savedSheet.id; }, - run: function() { + run: function () { const title = savedSheet.title; function doDelete() { savedSheet @@ -222,7 +222,7 @@ app.controller('timelion', function( ); kbnUrl.change('/'); }) - .catch(error => fatalError(error, location)); + .catch((error) => fatalError(error, location)); } const confirmModalOptions = { @@ -243,7 +243,7 @@ app.controller('timelion', function( }), confirmModalOptions ) - .then(isConfirmed => { + .then((isConfirmed) => { if (isConfirmed) { doDelete(); } @@ -310,12 +310,12 @@ app.controller('timelion', function( } let refresher; - const setRefreshData = function() { + const setRefreshData = function () { if (refresher) $timeout.cancel(refresher); const interval = timefilter.getRefreshInterval(); if (interval.value > 0 && !interval.pause) { function startRefresh() { - refresher = $timeout(function() { + refresher = $timeout(function () { if (!$scope.running) $scope.search(); startRefresh(); }, interval.value); @@ -324,7 +324,7 @@ app.controller('timelion', function( } }; - const init = function() { + const init = function () { $scope.running = false; $scope.search(); setRefreshData(); @@ -343,7 +343,7 @@ app.controller('timelion', function( savedSheet: savedSheet, state: $scope.state, search: $scope.search, - dontShowHelp: function() { + dontShowHelp: function () { config.set('timelion:showTutorial', false); $scope.setPage(0); $scope.closeMenus(); @@ -357,27 +357,27 @@ app.controller('timelion', function( showOptions: false, }; - $scope.toggleMenu = menuName => { + $scope.toggleMenu = (menuName) => { const curState = $scope.menus[menuName]; $scope.closeMenus(); $scope.menus[menuName] = !curState; }; $scope.closeMenus = () => { - _.forOwn($scope.menus, function(value, key) { + _.forOwn($scope.menus, function (value, key) { $scope.menus[key] = false; }); }; }; - $scope.onTimeUpdate = function({ dateRange }) { + $scope.onTimeUpdate = function ({ dateRange }) { $scope.model.timeRange = { ...dateRange, }; timefilter.setTime(dateRange); }; - $scope.onRefreshChange = function({ isPaused, refreshInterval }) { + $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { $scope.model.refreshInterval = { pause: isPaused, value: refreshInterval, @@ -391,33 +391,33 @@ app.controller('timelion', function( }; $scope.$watch( - function() { + function () { return savedSheet.lastSavedTitle; }, - function(newTitle) { + function (newTitle) { docTitle.change(savedSheet.id ? newTitle : undefined); } ); - $scope.toggle = function(property) { + $scope.toggle = function (property) { $scope[property] = !$scope[property]; }; - $scope.newSheet = function() { + $scope.newSheet = function () { kbnUrl.change('/', {}); }; - $scope.newCell = function() { + $scope.newCell = function () { $scope.state.sheet.push(defaultExpression); $scope.state.selected = $scope.state.sheet.length - 1; $scope.safeSearch(); }; - $scope.setActiveCell = function(cell) { + $scope.setActiveCell = function (cell) { $scope.state.selected = cell; }; - $scope.search = function() { + $scope.search = function () { $scope.state.save(); $scope.running = true; @@ -438,23 +438,23 @@ app.controller('timelion', function( } ), }) - .then(resp => resp.data) - .catch(resp => { + .then((resp) => resp.data) + .catch((resp) => { throw resp.data; }); httpResult - .then(function(resp) { + .then(function (resp) { $scope.stats = resp.stats; $scope.sheet = resp.sheet; - _.each(resp.sheet, function(cell) { + _.each(resp.sheet, function (cell) { if (cell.exception) { $scope.state.selected = cell.plot; } }); $scope.running = false; }) - .catch(function(resp) { + .catch(function (resp) { $scope.sheet = []; $scope.running = false; @@ -475,7 +475,7 @@ app.controller('timelion', function( savedSheet.timelion_interval = $scope.state.interval; savedSheet.timelion_columns = $scope.state.columns; savedSheet.timelion_rows = $scope.state.rows; - savedSheet.save().then(function(id) { + savedSheet.save().then(function (id) { if (id) { toastNotifications.addSuccess({ title: i18n.translate('timelion.saveSheet.successNotificationText', { @@ -493,14 +493,14 @@ app.controller('timelion', function( } function saveExpression(title) { - savedVisualizations.get({ type: 'timelion' }).then(function(savedExpression) { + savedVisualizations.get({ type: 'timelion' }).then(function (savedExpression) { savedExpression.visState.params = { expression: $scope.state.sheet[$scope.state.selected], interval: $scope.state.interval, }; savedExpression.title = title; savedExpression.visState.title = title; - savedExpression.save().then(function(id) { + savedExpression.save().then(function (id) { if (id) { toastNotifications.addSuccess( i18n.translate('timelion.saveExpression.successNotificationText', { diff --git a/src/legacy/core_plugins/timelion/public/components/timelionhelp_tabs_directive.js b/src/legacy/core_plugins/timelion/public/components/timelionhelp_tabs_directive.js index 16d814a11cc7b..7e77027f750c6 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelionhelp_tabs_directive.js +++ b/src/legacy/core_plugins/timelion/public/components/timelionhelp_tabs_directive.js @@ -25,6 +25,6 @@ const module = uiModules.get('apps/timelion', ['react']); import { TimelionHelpTabs } from './timelionhelp_tabs'; -module.directive('timelionHelpTabs', function(reactDirective) { +module.directive('timelionHelpTabs', function (reactDirective) { return reactDirective(wrapInI18nContext(TimelionHelpTabs), undefined, { restrict: 'E' }); }); diff --git a/src/legacy/core_plugins/timelion/public/directives/cells/cells.js b/src/legacy/core_plugins/timelion/public/directives/cells/cells.js index 3e59025e920b9..104af3b1043d6 100644 --- a/src/legacy/core_plugins/timelion/public/directives/cells/cells.js +++ b/src/legacy/core_plugins/timelion/public/directives/cells/cells.js @@ -27,7 +27,7 @@ require('plugins/timelion/directives/timelion_grid'); const app = require('ui/modules').get('apps/timelion', ['angular-sortable-view']); import html from './cells.html'; -app.directive('timelionCells', function() { +app.directive('timelionCells', function () { return { restrict: 'E', scope: { @@ -38,13 +38,13 @@ app.directive('timelionCells', function() { onSelect: '=', }, template: html, - link: function($scope) { - $scope.removeCell = function(index) { + link: function ($scope) { + $scope.removeCell = function (index) { _.pullAt($scope.state.sheet, index); $scope.onSearch(); }; - $scope.dropCell = function(item, partFrom, partTo, indexFrom, indexTo) { + $scope.dropCell = function (item, partFrom, partTo, indexFrom, indexTo) { $scope.onSelect(indexTo); move($scope.sheet, indexFrom, indexTo); }; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/chart.js b/src/legacy/core_plugins/timelion/public/directives/chart/chart.js index 332567c35b163..14bd3281a683e 100644 --- a/src/legacy/core_plugins/timelion/public/directives/chart/chart.js +++ b/src/legacy/core_plugins/timelion/public/directives/chart/chart.js @@ -28,7 +28,7 @@ export function Chart(timelionPanels) { interval: '=', // Required for formatting x-axis ticks rerenderTrigger: '=', }, - link: function($scope, $elem) { + link: function ($scope, $elem) { let panelScope = $scope.$new(true); function render() { diff --git a/src/legacy/core_plugins/timelion/public/directives/fixed_element.js b/src/legacy/core_plugins/timelion/public/directives/fixed_element.js index 71907c4014a29..e3a8b2184bb20 100644 --- a/src/legacy/core_plugins/timelion/public/directives/fixed_element.js +++ b/src/legacy/core_plugins/timelion/public/directives/fixed_element.js @@ -20,12 +20,12 @@ import $ from 'jquery'; const app = require('ui/modules').get('apps/timelion', []); -app.directive('fixedElementRoot', function() { +app.directive('fixedElementRoot', function () { return { restrict: 'A', - link: function($elem) { + link: function ($elem) { let fixedAt; - $(window).bind('scroll', function() { + $(window).bind('scroll', function () { const fixed = $('[fixed-element]', $elem); const body = $('[fixed-element-body]', $elem); const top = fixed.offset().top; diff --git a/src/legacy/core_plugins/timelion/public/directives/fullscreen/fullscreen.js b/src/legacy/core_plugins/timelion/public/directives/fullscreen/fullscreen.js index 325ed4d5c786e..5c4bd72ceb708 100644 --- a/src/legacy/core_plugins/timelion/public/directives/fullscreen/fullscreen.js +++ b/src/legacy/core_plugins/timelion/public/directives/fullscreen/fullscreen.js @@ -24,7 +24,7 @@ require('plugins/timelion/directives/timelion_grid'); const app = require('ui/modules').get('apps/timelion', ['angular-sortable-view']); import html from './fullscreen.html'; -app.directive('timelionFullscreen', function() { +app.directive('timelionFullscreen', function () { return { restrict: 'E', scope: { diff --git a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js index ee729d2b427ad..879fab206b99d 100644 --- a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js +++ b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js @@ -29,13 +29,14 @@ import { PaginateDirectiveProvider, } from '../../../../../plugins/kibana_legacy/public'; import { PER_PAGE_SETTING } from '../../../../../plugins/saved_objects/common'; +import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../../plugins/visualizations/public'; const module = uiModules.get('kibana'); module .directive('paginate', PaginateDirectiveProvider) .directive('paginateControls', PaginateControlsDirectiveProvider) - .directive('savedObjectFinder', function($location, kbnUrl, Private, config) { + .directive('savedObjectFinder', function ($location, kbnUrl, Private, config) { return { restrict: 'E', scope: { @@ -59,7 +60,7 @@ module }, template: savedObjectFinderTemplate, controllerAs: 'finder', - controller: function($scope, $element) { + controller: function ($scope, $element) { const self = this; // the text input element @@ -97,7 +98,7 @@ module * @param {Array} hits Array of saved finder object hits * @return {Array} Array sorted either ascending or descending */ - self.sortHits = function(hits) { + self.sortHits = function (hits) { self.isAscending = !self.isAscending; self.hits = self.isAscending ? _.sortBy(hits, 'title') @@ -109,7 +110,7 @@ module * hit should have a url in the UI, returns it if so * @return {string|null} - the url or nothing */ - self.makeUrl = function(hit) { + self.makeUrl = function (hit) { if ($scope.userMakeUrl) { return $scope.userMakeUrl(hit); } @@ -121,7 +122,7 @@ module return '#'; }; - self.preventClick = function($event) { + self.preventClick = function ($event) { $event.preventDefault(); }; @@ -129,7 +130,7 @@ module * Called when a hit object is clicked, can override the * url behavior if necessary. */ - self.onChoose = function(hit, $event) { + self.onChoose = function (hit, $event) { if ($scope.userOnChoose) { $scope.userOnChoose(hit, $event); } @@ -143,7 +144,7 @@ module kbnUrl.change(url.substr(1)); }; - $scope.$watch('filter', function(newFilter) { + $scope.$watch('filter', function (newFilter) { // ensure that the currentFilter changes from undefined to '' // which triggers currentFilter = newFilter || ''; @@ -152,7 +153,7 @@ module $scope.pageFirstItem = 0; $scope.pageLastItem = 0; - $scope.onPageChanged = page => { + $scope.onPageChanged = (page) => { $scope.pageFirstItem = page.firstItem; $scope.pageLastItem = page.lastItem; }; @@ -163,14 +164,12 @@ module index: -1, }; - self.getLabel = function() { - return _.words(self.properties.nouns) - .map(_.capitalize) - .join(' '); + self.getLabel = function () { + return _.words(self.properties.nouns).map(_.capitalize).join(' '); }; //key handler for the filter text box - self.filterKeyDown = function($event) { + self.filterKeyDown = function ($event) { switch (keyMap[$event.keyCode]) { case 'enter': if (self.hitCount !== 1) return; @@ -185,7 +184,7 @@ module }; //key handler for the list items - self.hitKeyDown = function($event, page, paginate) { + self.hitKeyDown = function ($event, page, paginate) { switch (keyMap[$event.keyCode]) { case 'tab': if (!self.selector.enabled) break; @@ -263,21 +262,21 @@ module } }; - self.hitBlur = function() { + self.hitBlur = function () { self.selector.index = -1; self.selector.enabled = false; }; - self.manageObjects = function(type) { + self.manageObjects = function (type) { $location.url('/management/kibana/objects?_a=' + rison.encode({ tab: type })); }; - self.hitCountNoun = function() { + self.hitCountNoun = function () { return (self.hitCount === 1 ? self.properties.noun : self.properties.nouns).toLowerCase(); }; function selectTopHit() { - setTimeout(function() { + setTimeout(function () { //triggering a focus event kicks off a new angular digest cycle. $list.find('a:first').focus(); }, 0); @@ -296,10 +295,10 @@ module prevSearch = filter; - const isLabsEnabled = config.get('visualize:enableLabs'); - self.service.find(filter).then(function(hits) { + const isLabsEnabled = config.get(VISUALIZE_ENABLE_LABS_SETTING); + self.service.find(filter).then(function (hits) { hits.hits = hits.hits.filter( - hit => isLabsEnabled || _.get(hit, 'type.stage') !== 'experimental' + (hit) => isLabsEnabled || _.get(hit, 'type.stage') !== 'experimental' ); hits.total = hits.hits.length; diff --git a/src/legacy/core_plugins/timelion/public/directives/saved_object_save_as_checkbox.js b/src/legacy/core_plugins/timelion/public/directives/saved_object_save_as_checkbox.js index ac830092ce670..0671bc3e20123 100644 --- a/src/legacy/core_plugins/timelion/public/directives/saved_object_save_as_checkbox.js +++ b/src/legacy/core_plugins/timelion/public/directives/saved_object_save_as_checkbox.js @@ -20,7 +20,7 @@ import { uiModules } from 'ui/modules'; import saveObjectSaveAsCheckboxTemplate from './saved_object_save_as_checkbox.html'; -uiModules.get('kibana').directive('savedObjectSaveAsCheckBox', function() { +uiModules.get('kibana').directive('savedObjectSaveAsCheckBox', function () { return { restrict: 'E', template: saveObjectSaveAsCheckboxTemplate, diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 35ac883e5d99c..f3fd2fde8f2c5 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -67,7 +67,7 @@ export function TimelionExpInput($http, $timeout) { }, replace: true, template: timelionExpressionInputTemplate, - link: function(scope, elem) { + link: function (scope, elem) { const argValueSuggestions = npStart.plugins.visTypeTimelion.getArgValueSuggestions(); const expressionInput = elem.find('[data-expression-input]'); const functionReference = {}; @@ -76,7 +76,7 @@ export function TimelionExpInput($http, $timeout) { scope.suggestions = new Suggestions(); function init() { - $http.get('../api/timelion/functions').then(function(resp) { + $http.get('../api/timelion/functions').then(function (resp) { Object.assign(functionReference, { byName: _.indexBy(resp.data, 'name'), list: resp.data, @@ -193,7 +193,7 @@ export function TimelionExpInput($http, $timeout) { scope.suggestions.hide(); }; - scope.onKeyDownInput = e => { + scope.onKeyDownInput = (e) => { // If we've pressed any non-navigational keys, then the user has typed something and we // can exit early without doing any navigation. The keyup handler will pull up suggestions. if (!isNavigationalKey(e.keyCode)) { @@ -253,7 +253,7 @@ export function TimelionExpInput($http, $timeout) { } }; - scope.onKeyUpInput = e => { + scope.onKeyUpInput = (e) => { // If the user isn't navigating, then we should update the suggestions based on their input. if (!isNavigationalKey(e.keyCode)) { getSuggestions(); @@ -264,7 +264,7 @@ export function TimelionExpInput($http, $timeout) { getSuggestions(); }; - scope.onClickSuggestion = index => { + scope.onClickSuggestion = (index) => { insertSuggestionIntoExpression(index); }; diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input_helpers.js index 36577fcb00719..20edee82f9486 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input_helpers.js @@ -101,10 +101,10 @@ function getArgumentsHelp(functionHelp, functionArgs = []) { const argsHelp = functionHelp.chainable ? functionHelp.args.slice(1) : functionHelp.args.slice(0); // ignore arguments that are already provided in function declaration - const functionArgNames = functionArgs.map(arg => { + const functionArgNames = functionArgs.map((arg) => { return arg.name; }); - return argsHelp.filter(arg => { + return argsHelp.filter((arg) => { return !functionArgNames.includes(arg.name); }); } @@ -115,7 +115,7 @@ async function extractSuggestionsFromParsedResult( functionList, argValueSuggestions ) { - const activeFunc = result.functions.find(func => { + const activeFunc = result.functions.find((func) => { return cursorPosition >= func.location.min && cursorPosition < func.location.max; }); @@ -123,7 +123,7 @@ async function extractSuggestionsFromParsedResult( return; } - const functionHelp = functionList.find(func => { + const functionHelp = functionList.find((func) => { return func.name === activeFunc.function; }); @@ -135,7 +135,7 @@ async function extractSuggestionsFromParsedResult( } // return argument value suggestions when cursor is inside argument value - const activeArg = activeFunc.arguments.find(argument => { + const activeArg = activeFunc.arguments.find((argument) => { return inLocation(cursorPosition, argument.location); }); if ( @@ -159,7 +159,7 @@ async function extractSuggestionsFromParsedResult( partialInput ); } else { - const { suggestions: staticSuggestions } = functionHelp.args.find(arg => { + const { suggestions: staticSuggestions } = functionHelp.args.find((arg) => { return arg.name === activeArg.name; }); valueSuggestions = argValueSuggestions.getStaticSuggestionsForInput( @@ -176,7 +176,7 @@ async function extractSuggestionsFromParsedResult( // return argument suggestions const argsHelp = getArgumentsHelp(functionHelp, activeFunc.arguments); - const argumentSuggestions = argsHelp.filter(arg => { + const argumentSuggestions = argsHelp.filter((arg) => { if (_.get(activeArg, 'type') === 'namedArg') { return _.startsWith(arg.name, activeArg.name); } else if (activeArg) { @@ -222,7 +222,7 @@ export async function suggest( if (message.function) { // The user has start typing a function name, so we'll filter the list down to only // possible matches. - list = functionList.filter(func => _.startsWith(func.name, message.function)); + list = functionList.filter((func) => _.startsWith(func.name, message.function)); } else { // The user hasn't typed anything yet, so we'll just return the entire list. list = functionList; @@ -231,7 +231,7 @@ export async function suggest( } case 'incompleteArgument': { const { currentFunction: functionName, currentArgs: functionArgs } = message; - const functionHelp = functionList.find(func => func.name === functionName); + const functionHelp = functionList.find((func) => func.name === functionName); return { list: getArgumentsHelp(functionHelp, functionArgs), location: message.location, @@ -248,9 +248,9 @@ export async function suggest( functionArgs ); } else { - const functionHelp = functionList.find(func => func.name === functionName); + const functionHelp = functionList.find((func) => func.name === functionName); if (functionHelp) { - const argHelp = functionHelp.args.find(arg => arg.name === argName); + const argHelp = functionHelp.args.find((arg) => arg.name === argName); if (argHelp && argHelp.suggestions) { valueSuggestions = argHelp.suggestions; } diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/__tests__/timelion_expression_suggestions.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/__tests__/timelion_expression_suggestions.js index a74cf19df29e3..8a35a72ed19e6 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/__tests__/timelion_expression_suggestions.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/__tests__/timelion_expression_suggestions.js @@ -21,34 +21,34 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import '../timelion_expression_suggestions'; -describe('Timelion expression suggestions directive', function() { +describe('Timelion expression suggestions directive', function () { let scope; let $compile; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $compile = $injector.get('$compile'); scope = $injector.get('$rootScope').$new(); }) ); - describe('attributes', function() { - describe('suggestions', function() { + describe('attributes', function () { + describe('suggestions', function () { let element = null; const template = ``; - beforeEach(function() { + beforeEach(function () { element = $compile(template)(scope); scope.$apply(() => { scope.list = [{ name: 'suggestion1' }, { name: 'suggestion2' }, { name: 'suggestion3' }]; }); }); - it('are rendered', function() { + it('are rendered', function () { expect(element.find('[data-test-subj="timelionSuggestionListItem"]').length).to.be( scope.list.length ); diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js index c560b213731e3..5d8168a3197ca 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js @@ -31,9 +31,9 @@ export function TimelionExpressionSuggestions() { }, replace: true, template, - link: function(scope) { + link: function (scope) { // This will prevent the expression input from losing focus. - scope.onMouseDown = e => e.preventDefault(); + scope.onMouseDown = (e) => e.preventDefault(); }, }; } diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_grid.js b/src/legacy/core_plugins/timelion/public/directives/timelion_grid.js index 3ef65f60d2ef4..256c35331d016 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_grid.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_grid.js @@ -20,27 +20,27 @@ import $ from 'jquery'; const app = require('ui/modules').get('apps/timelion', []); -app.directive('timelionGrid', function() { +app.directive('timelionGrid', function () { return { restrict: 'A', scope: { timelionGridRows: '=', timelionGridColumns: '=', }, - link: function($scope, $elem) { + link: function ($scope, $elem) { function init() { setDimensions(); } - $scope.$on('$destroy', function() { + $scope.$on('$destroy', function () { $(window).off('resize'); //remove the handler added earlier }); - $(window).resize(function() { + $(window).resize(function () { setDimensions(); }); - $scope.$watchMulti(['timelionGridColumns', 'timelionGridRows'], function() { + $scope.$watchMulti(['timelionGridColumns', 'timelionGridRows'], function () { setDimensions(); }); diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_help/timelion_help.js b/src/legacy/core_plugins/timelion/public/directives/timelion_help/timelion_help.js index e152ca165c810..25f3df13153ba 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_help/timelion_help.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_help/timelion_help.js @@ -26,18 +26,18 @@ import '../../components/timelionhelp_tabs_directive'; const app = uiModules.get('apps/timelion', []); -app.directive('timelionHelp', function($http) { +app.directive('timelionHelp', function ($http) { return { restrict: 'E', template, - controller: function($scope) { + controller: function ($scope) { $scope.functions = { list: [], details: null, }; $scope.activeTab = 'funcref'; - $scope.activateTab = function(tabName) { + $scope.activateTab = function (tabName) { $scope.activeTab = tabName; }; @@ -123,19 +123,19 @@ app.directive('timelionHelp', function($http) { } function getFunctions() { - return $http.get('../api/timelion/functions').then(function(resp) { + return $http.get('../api/timelion/functions').then(function (resp) { $scope.functions.list = resp.data; }); } - $scope.recheckElasticsearch = function() { + $scope.recheckElasticsearch = function () { $scope.es.valid = null; - checkElasticsearch().then(function(valid) { + checkElasticsearch().then(function (valid) { if (!valid) $scope.es.invalidCount++; }); }; function checkElasticsearch() { - return $http.get('../api/timelion/validate/es').then(function(resp) { + return $http.get('../api/timelion/validate/es').then(function (resp) { if (resp.data.ok) { $scope.es.valid = true; $scope.es.stats = { @@ -145,7 +145,7 @@ app.directive('timelionHelp', function($http) { }; } else { $scope.es.valid = false; - $scope.es.invalidReason = (function() { + $scope.es.invalidReason = (function () { try { const esResp = JSON.parse(resp.data.resp.response); return _.get(esResp, 'error.root_cause[0].reason'); diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js b/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js index 4031916ce9708..577ee984e05c6 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js @@ -29,7 +29,7 @@ export function TimelionInterval($timeout) { model: '=', }, template, - link: function($scope, $elem) { + link: function ($scope, $elem) { $scope.intervalOptions = ['auto', '1s', '1m', '1h', '1d', '1w', '1M', '1y', 'other']; $scope.intervalLabels = { auto: 'auto', @@ -43,7 +43,7 @@ export function TimelionInterval($timeout) { other: 'other', }; - $scope.$watch('model', function(newVal, oldVal) { + $scope.$watch('model', function (newVal, oldVal) { // Only run this on initialization if (newVal !== oldVal || oldVal == null) return; @@ -58,13 +58,13 @@ export function TimelionInterval($timeout) { } }); - $scope.$watch('interval', function(newVal, oldVal) { + $scope.$watch('interval', function (newVal, oldVal) { if (newVal === oldVal) return; if (newVal === 'other') { $scope.otherInterval = oldVal; $scope.model = $scope.otherInterval; - $timeout(function() { + $timeout(function () { $('input', $elem).select(); }, 0); } else { @@ -73,7 +73,7 @@ export function TimelionInterval($timeout) { } }); - $scope.$watch('otherInterval', function(newVal, oldVal) { + $scope.$watch('otherInterval', function (newVal, oldVal) { if (newVal === oldVal) return; $scope.model = newVal; }); diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_load_sheet.js b/src/legacy/core_plugins/timelion/public/directives/timelion_load_sheet.js index cf74026791ef1..d80770cbc2ae1 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_load_sheet.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_load_sheet.js @@ -21,7 +21,7 @@ import { uiModules } from 'ui/modules'; import template from 'plugins/timelion/partials/load_sheet.html'; const app = uiModules.get('apps/timelion', []); -app.directive('timelionLoad', function() { +app.directive('timelionLoad', function () { return { replace: true, restrict: 'E', diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_options_sheet.js b/src/legacy/core_plugins/timelion/public/directives/timelion_options_sheet.js index f41138fe5f382..067c831f09de5 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_options_sheet.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_options_sheet.js @@ -21,7 +21,7 @@ import { uiModules } from 'ui/modules'; import template from 'plugins/timelion/partials/sheet_options.html'; const app = uiModules.get('apps/timelion', []); -app.directive('timelionOptions', function() { +app.directive('timelionOptions', function () { return { replace: true, restrict: 'E', diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_save_sheet.js b/src/legacy/core_plugins/timelion/public/directives/timelion_save_sheet.js index 03d17d95b43d1..6dd44a10dc48c 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_save_sheet.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_save_sheet.js @@ -21,7 +21,7 @@ import { uiModules } from 'ui/modules'; import saveTemplate from 'plugins/timelion/partials/save_sheet.html'; const app = uiModules.get('apps/timelion', []); -app.directive('timelionSave', function() { +app.directive('timelionSave', function () { return { replace: true, restrict: 'E', diff --git a/src/legacy/core_plugins/timelion/public/lib/observe_resize.js b/src/legacy/core_plugins/timelion/public/lib/observe_resize.js index a75f2d7c6a713..62962e38c196e 100644 --- a/src/legacy/core_plugins/timelion/public/lib/observe_resize.js +++ b/src/legacy/core_plugins/timelion/public/lib/observe_resize.js @@ -17,7 +17,7 @@ * under the License. */ -export default function($elem, fn, frequency) { +export default function ($elem, fn, frequency) { frequency = frequency || 500; let currentHeight = $elem.height(); let currentWidth = $elem.width(); @@ -25,7 +25,7 @@ export default function($elem, fn, frequency) { let timeout; function checkLoop() { - timeout = setTimeout(function() { + timeout = setTimeout(function () { if (currentHeight !== $elem.height() || currentWidth !== $elem.width()) { currentHeight = $elem.height(); currentWidth = $elem.width(); @@ -38,7 +38,7 @@ export default function($elem, fn, frequency) { checkLoop(); - return function() { + return function () { clearTimeout(timeout); }; } diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 34b389f5ff4ce..b1999eb4b483c 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -43,7 +43,7 @@ const DEBOUNCE_DELAY = 50; export function timechartFn(dependencies: TimelionVisualizationDependencies) { const { $rootScope, $compile, uiSettings } = dependencies; - return function() { + return function () { return { help: 'Draw a timeseries chart', render($scope: any, $elem: any) { @@ -157,7 +157,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { }); drawPlot($scope.chart); } - $scope.highlightSeries = _.debounce(function(id: any) { + $scope.highlightSeries = _.debounce(function (id: any) { if (highlightedSeries === id) { return; } @@ -172,50 +172,50 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { }); drawPlot($scope.chart); }, DEBOUNCE_DELAY); - $scope.focusSeries = function(id: any) { + $scope.focusSeries = function (id: any) { focusedSeries = id; $scope.highlightSeries(id); }; - $scope.toggleSeries = function(id: any) { + $scope.toggleSeries = function (id: any) { const series = $scope.chart[id]; series._hide = !series._hide; drawPlot($scope.chart); }; - const cancelResize = observeResize($elem, function() { + const cancelResize = observeResize($elem, function () { drawPlot($scope.chart); }); - $scope.$on('$destroy', function() { + $scope.$on('$destroy', function () { cancelResize(); $elem.off('plothover'); $elem.off('plotselected'); $elem.off('mouseleave'); }); - $elem.on('plothover', function(event: any, pos: any, item: any) { + $elem.on('plothover', function (event: any, pos: any, item: any) { $rootScope.$broadcast('timelionPlotHover', event, pos, item); }); - $elem.on('plotselected', function(event: any, ranges: any) { + $elem.on('plotselected', function (event: any, ranges: any) { timefilter.setTime({ from: moment(ranges.xaxis.from), to: moment(ranges.xaxis.to), }); }); - $elem.on('mouseleave', function() { + $elem.on('mouseleave', function () { $rootScope.$broadcast('timelionPlotLeave'); }); - $scope.$on('timelionPlotHover', function(angularEvent: any, flotEvent: any, pos: any) { + $scope.$on('timelionPlotHover', function (angularEvent: any, flotEvent: any, pos: any) { if (!$scope.plot) return; $scope.plot.setCrosshair(pos); debouncedSetLegendNumbers(pos); }); - $scope.$on('timelionPlotLeave', function() { + $scope.$on('timelionPlotLeave', function () { if (!$scope.plot) return; $scope.plot.clearCrosshair(); clearLegendNumbers(); @@ -277,7 +277,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { if (legendCaption) { legendCaption.html(emptyCaption); } - _.each(legendValueNumbers, function(num) { + _.each(legendValueNumbers, function (num) { $(num).empty(); }); } @@ -293,10 +293,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { return; } - const title = _(plotConfig) - .map('_title') - .compact() - .last() as any; + const title = _(plotConfig).map('_title').compact().last() as any; $('.chart-top-title', $elem).text(title == null ? '' : title); const options = _.cloneDeep(defaultOptions) as any; @@ -313,7 +310,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { const format = getxAxisFormatter(interval); // Use moment to format ticks so we get timezone correction - options.xaxis.tickFormatter = function(val: any) { + options.xaxis.tickFormatter = function (val: any) { return moment(val).format(format); }; @@ -324,7 +321,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { $elem.width() / (format.length * tickLetterWidth + tickPadding) ); - const series = _.map(plotConfig, function(serie: any, index) { + const series = _.map(plotConfig, function (serie: any, index) { serie = _.cloneDeep( _.defaults(serie, { shadowSize: 0, @@ -349,7 +346,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { } if (serie._global) { - _.merge(options, serie._global, function(objVal, srcVal) { + _.merge(options, serie._global, function (objVal, srcVal) { // This is kind of gross, it means that you can't replace a global value with a null // best you can do is an empty string. Deal with it. if (objVal == null) return srcVal; @@ -383,7 +380,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { legendScope = $scope.$new(); // Used to toggle the series, and for displaying values on hover legendValueNumbers = canvasElem.find('.ngLegendValueNumber'); - _.each(canvasElem.find('.ngLegendValue'), function(elem) { + _.each(canvasElem.find('.ngLegendValue'), function (elem) { $compile(elem)(legendScope); }); diff --git a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts b/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts index e7f431a178ea0..1fb29de83d3d7 100644 --- a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts +++ b/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts @@ -40,7 +40,7 @@ export const savedSheetLoader = new SavedObjectLoader( savedObjectsClient, npStart.core.chrome ); -savedSheetLoader.urlFor = id => `#/${encodeURIComponent(id)}`; +savedSheetLoader.urlFor = (id) => `#/${encodeURIComponent(id)}`; // Customize loader properties since adding an 's' on type doesn't work for type 'timelion-sheet'. savedSheetLoader.loaderProperties = { name: 'timelion-sheet', diff --git a/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts b/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts index 8fadf223e1807..8122259f1c991 100644 --- a/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts +++ b/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts @@ -41,7 +41,7 @@ export const initTimelionLegacyModule = once((timelionPanels: Map uiModules .get('apps/timelion', []) - .controller('TimelionVisController', function($scope: any) { + .controller('TimelionVisController', function ($scope: any) { $scope.$on('timelionChartRendered', (event: any) => { event.stopPropagation(); $scope.renderComplete(); diff --git a/src/legacy/deprecation/__tests__/create_transform.js b/src/legacy/deprecation/__tests__/create_transform.js index 5e0616b729c06..d3838da5c3399 100644 --- a/src/legacy/deprecation/__tests__/create_transform.js +++ b/src/legacy/deprecation/__tests__/create_transform.js @@ -21,14 +21,14 @@ import { createTransform } from '../create_transform'; import expect from '@kbn/expect'; import sinon from 'sinon'; -describe('deprecation', function() { - describe('createTransform', function() { - it(`doesn't modify settings parameter`, function() { +describe('deprecation', function () { + describe('createTransform', function () { + it(`doesn't modify settings parameter`, function () { const settings = { original: true, }; const deprecations = [ - settings => { + (settings) => { settings.original = false; }, ]; @@ -36,22 +36,22 @@ describe('deprecation', function() { expect(settings.original).to.be(true); }); - it('calls single deprecation in array', function() { + it('calls single deprecation in array', function () { const deprecations = [sinon.spy()]; createTransform(deprecations)({}); expect(deprecations[0].calledOnce).to.be(true); }); - it('calls multiple deprecations in array', function() { + it('calls multiple deprecations in array', function () { const deprecations = [sinon.spy(), sinon.spy()]; createTransform(deprecations)({}); expect(deprecations[0].calledOnce).to.be(true); expect(deprecations[1].calledOnce).to.be(true); }); - it('passes log function to deprecation', function() { + it('passes log function to deprecation', function () { const deprecation = sinon.spy(); - const log = function() {}; + const log = function () {}; createTransform([deprecation])({}, log); expect(deprecation.args[0][1]).to.be(log); }); diff --git a/src/legacy/deprecation/create_transform.js b/src/legacy/deprecation/create_transform.js index 58418e3f50def..72e8e153ed819 100644 --- a/src/legacy/deprecation/create_transform.js +++ b/src/legacy/deprecation/create_transform.js @@ -24,7 +24,7 @@ export function createTransform(deprecations) { return (settings, log = noop) => { const result = clone(settings); - forEach(deprecations, deprecation => { + forEach(deprecations, (deprecation) => { deprecation(result, log); }); diff --git a/src/legacy/deprecation/deprecations/__tests__/rename.js b/src/legacy/deprecation/deprecations/__tests__/rename.js index 56b1c16d1607b..47c6b3257ff69 100644 --- a/src/legacy/deprecation/deprecations/__tests__/rename.js +++ b/src/legacy/deprecation/deprecations/__tests__/rename.js @@ -21,9 +21,9 @@ import expect from '@kbn/expect'; import { rename } from '../rename'; import sinon from 'sinon'; -describe('deprecation/deprecations', function() { - describe('rename', function() { - it('should rename simple property', function() { +describe('deprecation/deprecations', function () { + describe('rename', function () { + it('should rename simple property', function () { const value = 'value'; const settings = { before: value, @@ -34,7 +34,7 @@ describe('deprecation/deprecations', function() { expect(settings.after).to.be(value); }); - it('should rename nested property', function() { + it('should rename nested property', function () { const value = 'value'; const settings = { someObject: { @@ -47,7 +47,7 @@ describe('deprecation/deprecations', function() { expect(settings.someObject.after).to.be(value); }); - it('should rename property, even when the value is null', function() { + it('should rename property, even when the value is null', function () { const value = null; const settings = { before: value, @@ -58,7 +58,7 @@ describe('deprecation/deprecations', function() { expect(settings.after).to.be(null); }); - it(`shouldn't log when a rename doesn't occur`, function() { + it(`shouldn't log when a rename doesn't occur`, function () { const settings = { exists: true, }; @@ -68,7 +68,7 @@ describe('deprecation/deprecations', function() { expect(log.called).to.be(false); }); - it('should log when a rename does occur', function() { + it('should log when a rename does occur', function () { const settings = { exists: true, }; diff --git a/src/legacy/deprecation/deprecations/__tests__/unused.js b/src/legacy/deprecation/deprecations/__tests__/unused.js index 3f049a4ff678c..4907c2b166989 100644 --- a/src/legacy/deprecation/deprecations/__tests__/unused.js +++ b/src/legacy/deprecation/deprecations/__tests__/unused.js @@ -21,9 +21,9 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; import { unused } from '../unused'; -describe('deprecation/deprecations', function() { - describe('unused', function() { - it('should remove unused setting', function() { +describe('deprecation/deprecations', function () { + describe('unused', function () { + it('should remove unused setting', function () { const settings = { old: true, }; @@ -32,7 +32,7 @@ describe('deprecation/deprecations', function() { expect(settings.old).to.be(undefined); }); - it(`shouldn't remove used setting`, function() { + it(`shouldn't remove used setting`, function () { const value = 'value'; const settings = { new: value, @@ -42,7 +42,7 @@ describe('deprecation/deprecations', function() { expect(settings.new).to.be(value); }); - it('should remove unused setting, even when null', function() { + it('should remove unused setting, even when null', function () { const settings = { old: null, }; @@ -51,7 +51,7 @@ describe('deprecation/deprecations', function() { expect(settings.old).to.be(undefined); }); - it('should log when removing unused setting', function() { + it('should log when removing unused setting', function () { const settings = { old: true, }; @@ -63,7 +63,7 @@ describe('deprecation/deprecations', function() { expect(log.args[0][0]).to.match(/old.+deprecated/); }); - it(`shouldn't log when no setting is unused`, function() { + it(`shouldn't log when no setting is unused`, function () { const settings = { new: true, }; diff --git a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js b/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js index 5544c0b483aa9..e6af23d69c549 100644 --- a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js +++ b/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js @@ -29,7 +29,7 @@ const PLUGIN_FIXTURES = resolve(__dirname, 'fixtures/plugins'); const CONFLICT_FIXTURES = resolve(__dirname, 'fixtures/conflicts'); describe('plugin discovery', () => { - describe('findPluginSpecs()', function() { + describe('findPluginSpecs()', function () { this.timeout(10000); describe('spec$', () => { @@ -46,10 +46,10 @@ describe('plugin discovery', () => { const specs = await spec$.pipe(toArray()).toPromise(); expect(specs).to.have.length(3); - specs.forEach(spec => { + specs.forEach((spec) => { expect(spec).to.be.a(PluginSpec); }); - expect(specs.map(s => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); + expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); }); it('finds all specs in scanDirs', async () => { @@ -64,10 +64,10 @@ describe('plugin discovery', () => { const specs = await spec$.pipe(toArray()).toPromise(); expect(specs).to.have.length(3); - specs.forEach(spec => { + specs.forEach((spec) => { expect(spec).to.be.a(PluginSpec); }); - expect(specs.map(s => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); + expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); }); it('does not find disabled plugins', async () => { @@ -87,10 +87,10 @@ describe('plugin discovery', () => { const specs = await spec$.pipe(toArray()).toPromise(); expect(specs).to.have.length(2); - specs.forEach(spec => { + specs.forEach((spec) => { expect(spec).to.be.a(PluginSpec); }); - expect(specs.map(s => s.getId()).sort()).to.eql(['bar:two', 'foo']); + expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:two', 'foo']); }); it('dedupes duplicate packs', async () => { @@ -110,10 +110,10 @@ describe('plugin discovery', () => { const specs = await spec$.pipe(toArray()).toPromise(); expect(specs).to.have.length(3); - specs.forEach(spec => { + specs.forEach((spec) => { expect(spec).to.be.a(PluginSpec); }); - expect(specs.map(s => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); + expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); }); describe('conflicting plugin spec ids', () => { @@ -137,9 +137,9 @@ describe('plugin discovery', () => { }); describe('packageJson$', () => { - const checkPackageJsons = packageJsons => { + const checkPackageJsons = (packageJsons) => { expect(packageJsons).to.have.length(2); - const package1 = packageJsons.find(packageJson => + const package1 = packageJsons.find((packageJson) => isEqual( { directoryPath: resolve(PLUGIN_FIXTURES, 'foo'), @@ -152,7 +152,7 @@ describe('plugin discovery', () => { ) ); expect(package1).to.be.an(Object); - const package2 = packageJsons.find(packageJson => + const package2 = packageJsons.find((packageJson) => isEqual( { directoryPath: resolve(PLUGIN_FIXTURES, 'bar'), diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js index 1ad8b1a277b84..fcbe3487463b7 100644 --- a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js +++ b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js @@ -17,7 +17,7 @@ * under the License. */ -export default function(kibana) { +export default function (kibana) { return [ // two plugins exported without ids will both inherit // the id of the pack and conflict diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js index bcc9619a517cf..0eef126f2255a 100644 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js +++ b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js @@ -17,7 +17,7 @@ * under the License. */ -export default function(kibana) { +export default function (kibana) { return [ new kibana.Plugin({ id: 'bar:one', diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js index 2ccd8f438eec7..e43a1dcedb372 100644 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js +++ b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js @@ -17,7 +17,7 @@ * under the License. */ -module.exports = function(kibana) { +module.exports = function (kibana) { return new kibana.Plugin({ id: 'foo', }); diff --git a/src/legacy/plugin_discovery/find_plugin_specs.js b/src/legacy/plugin_discovery/find_plugin_specs.js index efb9bf47ab71c..b97476bb456a5 100644 --- a/src/legacy/plugin_discovery/find_plugin_specs.js +++ b/src/legacy/plugin_discovery/find_plugin_specs.js @@ -52,7 +52,7 @@ function bufferAllResults(observable) { // buffer all results into a single array toArray(), // merge the array back into the stream when complete - mergeMap(array => array) + mergeMap((array) => array) ); } @@ -110,7 +110,7 @@ export function findPluginSpecs(settings, configToMutate) { // find plugin packs in configured paths/dirs const packageJson$ = config$.pipe( - mergeMap(config => + mergeMap((config) => Rx.merge( ...config.get('plugins.paths').map(createPackageJsonAtPath$), ...config.get('plugins.scanDirs').map(createPackageJsonsInDirectory$) @@ -123,19 +123,19 @@ export function findPluginSpecs(settings, configToMutate) { const pack$ = createPack$(packageJson$).pipe(share()); const extendConfig$ = config$.pipe( - mergeMap(config => + mergeMap((config) => pack$.pipe( // get the specs for each found plugin pack mergeMap(({ pack }) => (pack ? pack.getPluginSpecs() : [])), // make sure that none of the plugin specs have conflicting ids, fail // early if conflicts detected or merge the specs back into the stream toArray(), - mergeMap(allSpecs => { + mergeMap((allSpecs) => { for (const [id, specs] of groupSpecsById(allSpecs)) { if (specs.length > 1) { throw new Error( `Multiple plugins found with the id "${id}":\n${specs - .map(spec => ` - ${id} at ${spec.getPath()}`) + .map((spec) => ` - ${id} at ${spec.getPath()}`) .join('\n')}` ); } @@ -143,12 +143,12 @@ export function findPluginSpecs(settings, configToMutate) { return allSpecs; }), - mergeMap(async spec => { + mergeMap(async (spec) => { // extend the config service with this plugin spec and // collect its deprecations messages if some of its // settings are outdated const deprecations = []; - await extendConfigService(spec, config, settings, message => { + await extendConfigService(spec, config, settings, (message) => { deprecations.push({ spec, message }); }); @@ -173,7 +173,7 @@ export function findPluginSpecs(settings, configToMutate) { }), // determine which plugins are disabled before actually removing things from the config bufferAllResults, - tap(result => { + tap((result) => { for (const spec of result.disabledSpecs) { disableConfigExtension(spec, config); } @@ -186,46 +186,46 @@ export function findPluginSpecs(settings, configToMutate) { return { // package JSONs found when searching configure paths packageJson$: packageJson$.pipe( - mergeMap(result => (result.packageJson ? [result.packageJson] : [])) + mergeMap((result) => (result.packageJson ? [result.packageJson] : [])) ), // plugin packs found when searching configured paths - pack$: pack$.pipe(mergeMap(result => (result.pack ? [result.pack] : []))), + pack$: pack$.pipe(mergeMap((result) => (result.pack ? [result.pack] : []))), // errors caused by invalid directories of plugin directories invalidDirectoryError$: pack$.pipe( - mergeMap(result => (isInvalidDirectoryError(result.error) ? [result.error] : [])) + mergeMap((result) => (isInvalidDirectoryError(result.error) ? [result.error] : [])) ), // errors caused by directories that we expected to be plugin but were invalid invalidPackError$: pack$.pipe( - mergeMap(result => (isInvalidPackError(result.error) ? [result.error] : [])) + mergeMap((result) => (isInvalidPackError(result.error) ? [result.error] : [])) ), otherError$: pack$.pipe( - mergeMap(result => (isUnhandledError(result.error) ? [result.error] : [])) + mergeMap((result) => (isUnhandledError(result.error) ? [result.error] : [])) ), // { spec, message } objects produced when transforming deprecated // settings for a plugin spec - deprecation$: extendConfig$.pipe(mergeMap(result => result.deprecations)), + deprecation$: extendConfig$.pipe(mergeMap((result) => result.deprecations)), // the config service we extended with all of the plugin specs, // only emitted once it is fully extended by all extendedConfig$: extendConfig$.pipe( - mergeMap(result => result.config), + mergeMap((result) => result.config), filter(Boolean), last() ), // all enabled PluginSpec objects - spec$: extendConfig$.pipe(mergeMap(result => result.enabledSpecs)), + spec$: extendConfig$.pipe(mergeMap((result) => result.enabledSpecs)), // all disabled PluginSpec objects - disabledSpec$: extendConfig$.pipe(mergeMap(result => result.disabledSpecs)), + disabledSpec$: extendConfig$.pipe(mergeMap((result) => result.disabledSpecs)), // all PluginSpec objects that were disabled because their version was incompatible - invalidVersionSpec$: extendConfig$.pipe(mergeMap(result => result.invalidVersionSpecs)), + invalidVersionSpec$: extendConfig$.pipe(mergeMap((result) => result.invalidVersionSpecs)), }; } diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js index 21f427a545b25..a74bfb872e99c 100644 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js +++ b/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js @@ -40,7 +40,7 @@ describe('plugin discovery/extend config service', () => { new Plugin({ configPrefix: 'foo.bar.baz', - config: Joi => + config: (Joi) => Joi.object({ enabled: Joi.boolean().default(true), test: Joi.string().default('bonk'), diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js b/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js index e359b20be491d..78adb1e680e20 100644 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js +++ b/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js @@ -54,9 +54,7 @@ describe('plugin discovery/schema', () => { it('uses default schema when no config provider', async () => { const schema = await getSchema(createPluginSpec()); expect(schema).to.be.an('object'); - expect(schema) - .to.have.property('validate') - .a('function'); + expect(schema).to.have.property('validate').a('function'); expect(schema.validate({}).value).to.eql({ enabled: true, }); @@ -65,9 +63,7 @@ describe('plugin discovery/schema', () => { it('uses default schema when config returns falsy value', async () => { const schema = await getSchema(createPluginSpec(() => null)); expect(schema).to.be.an('object'); - expect(schema) - .to.have.property('validate') - .a('function'); + expect(schema).to.have.property('validate').a('function'); expect(schema.validate({}).value).to.eql({ enabled: true, }); @@ -76,9 +72,7 @@ describe('plugin discovery/schema', () => { it('uses default schema when config promise resolves to falsy value', async () => { const schema = await getSchema(createPluginSpec(() => Promise.resolve(null))); expect(schema).to.be.an('object'); - expect(schema) - .to.have.property('validate') - .a('function'); + expect(schema).to.have.property('validate').a('function'); expect(schema.validate({}).value).to.eql({ enabled: true, }); @@ -89,9 +83,7 @@ describe('plugin discovery/schema', () => { it('returns schema with enabled: false', async () => { const schema = await getStubSchema(); expect(schema).to.be.an('object'); - expect(schema) - .to.have.property('validate') - .a('function'); + expect(schema).to.have.property('validate').a('function'); expect(schema.validate({}).value).to.eql({ enabled: false, }); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js index d724b9add190c..b17bd69479ffa 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js +++ b/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js @@ -39,9 +39,7 @@ describe('plugin discovery/create pack', () => { }, }, ]); - const results = await createPack$(packageJson$) - .pipe(toArray()) - .toPromise(); + const results = await createPack$(packageJson$).pipe(toArray()).toPromise(); expect(results).to.have.length(1); expect(results[0]).to.only.have.keys(['pack']); const { pack } = results[0]; @@ -58,31 +56,29 @@ describe('plugin discovery/create pack', () => { }, ]); - const results = await createPack$(packageJson$) - .pipe(toArray()) - .toPromise(); + const results = await createPack$(packageJson$).pipe(toArray()).toPromise(); expect(results).to.have.length(1); expect(results[0]).to.only.have.keys(['error']); const { error } = results[0]; await check(error); } it('default export is an object', () => - checkError(resolve(PLUGINS_DIR, 'exports_object'), error => { + checkError(resolve(PLUGINS_DIR, 'exports_object'), (error) => { assertInvalidPackError(error); expect(error.message).to.contain('must export a function'); })); it('default export is an number', () => - checkError(resolve(PLUGINS_DIR, 'exports_number'), error => { + checkError(resolve(PLUGINS_DIR, 'exports_number'), (error) => { assertInvalidPackError(error); expect(error.message).to.contain('must export a function'); })); it('default export is an string', () => - checkError(resolve(PLUGINS_DIR, 'exports_string'), error => { + checkError(resolve(PLUGINS_DIR, 'exports_string'), (error) => { assertInvalidPackError(error); expect(error.message).to.contain('must export a function'); })); it('directory with code that fails when required', () => - checkError(resolve(PLUGINS_DIR, 'broken_code'), error => { + checkError(resolve(PLUGINS_DIR, 'broken_code'), (error) => { expect(error.message).to.contain("Cannot find module 'does-not-exist'"); })); }); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js index 29bfa89e20399..bdb26504d6b6e 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js +++ b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js @@ -1,6 +1,6 @@ const brokenRequire = require('does-not-exist'); // eslint-disable-line -module.exports = function(kibana) { +module.exports = function (kibana) { return new kibana.Plugin({ id: 'foo', }); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js index 2ccd8f438eec7..e43a1dcedb372 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js +++ b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js @@ -17,7 +17,7 @@ * under the License. */ -module.exports = function(kibana) { +module.exports = function (kibana) { return new kibana.Plugin({ id: 'foo', }); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js index a8913c0be531b..fa1033180954e 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js +++ b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js @@ -28,9 +28,7 @@ import { PLUGINS_DIR, assertInvalidPackError, assertInvalidDirectoryError } from describe('plugin discovery/plugin_pack', () => { describe('createPackageJsonAtPath$()', () => { it('returns an observable', () => { - expect(createPackageJsonAtPath$()) - .to.have.property('subscribe') - .a('function'); + expect(createPackageJsonAtPath$()).to.have.property('subscribe').a('function'); }); it('gets the default provider from prebuilt babel modules', async () => { const results = await createPackageJsonAtPath$(resolve(PLUGINS_DIR, 'prebuilt')) @@ -44,46 +42,44 @@ describe('plugin discovery/plugin_pack', () => { }); describe('errors emitted as { error } results', () => { async function checkError(path, check) { - const results = await createPackageJsonAtPath$(path) - .pipe(toArray()) - .toPromise(); + const results = await createPackageJsonAtPath$(path).pipe(toArray()).toPromise(); expect(results).to.have.length(1); expect(results[0]).to.only.have.keys(['error']); const { error } = results[0]; await check(error); } it('undefined path', () => - checkError(undefined, error => { + checkError(undefined, (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('path must be a string'); })); it('relative path', () => - checkError('plugins/foo', error => { + checkError('plugins/foo', (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('path must be absolute'); })); it('./relative path', () => - checkError('./plugins/foo', error => { + checkError('./plugins/foo', (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('path must be absolute'); })); it('non-existent path', () => - checkError(resolve(PLUGINS_DIR, 'baz'), error => { + checkError(resolve(PLUGINS_DIR, 'baz'), (error) => { assertInvalidPackError(error); expect(error.message).to.contain('must be a directory'); })); it('path to a file', () => - checkError(resolve(PLUGINS_DIR, 'index.js'), error => { + checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => { assertInvalidPackError(error); expect(error.message).to.contain('must be a directory'); })); it('directory without a package.json', () => - checkError(resolve(PLUGINS_DIR, 'lib'), error => { + checkError(resolve(PLUGINS_DIR, 'lib'), (error) => { assertInvalidPackError(error); expect(error.message).to.contain('must have a package.json file'); })); it('directory with an invalid package.json', () => - checkError(resolve(PLUGINS_DIR, 'broken'), error => { + checkError(resolve(PLUGINS_DIR, 'broken'), (error) => { assertInvalidPackError(error); expect(error.message).to.contain('must have a valid package.json file'); })); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js index ea42d4d876bb9..37cb4cc064da7 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js +++ b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js @@ -30,9 +30,7 @@ describe('plugin discovery/packs in directory', () => { describe('createPackageJsonsInDirectory$()', () => { describe('errors emitted as { error } results', () => { async function checkError(path, check) { - const results = await createPackageJsonsInDirectory$(path) - .pipe(toArray()) - .toPromise(); + const results = await createPackageJsonsInDirectory$(path).pipe(toArray()).toPromise(); expect(results).to.have.length(1); expect(results[0]).to.only.have.keys('error'); const { error } = results[0]; @@ -40,42 +38,40 @@ describe('plugin discovery/packs in directory', () => { } it('undefined path', () => - checkError(undefined, error => { + checkError(undefined, (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('path must be a string'); })); it('relative path', () => - checkError('my/plugins', error => { + checkError('my/plugins', (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('path must be absolute'); })); it('./relative path', () => - checkError('./my/pluginsd', error => { + checkError('./my/pluginsd', (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('path must be absolute'); })); it('non-existent path', () => - checkError(resolve(PLUGINS_DIR, 'notreal'), error => { + checkError(resolve(PLUGINS_DIR, 'notreal'), (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('no such file or directory'); })); it('path to a file', () => - checkError(resolve(PLUGINS_DIR, 'index.js'), error => { + checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => { assertInvalidDirectoryError(error); expect(error.message).to.contain('not a directory'); })); }); it('includes child errors for invalid packageJsons within a valid directory', async () => { - const results = await createPackageJsonsInDirectory$(PLUGINS_DIR) - .pipe(toArray()) - .toPromise(); + const results = await createPackageJsonsInDirectory$(PLUGINS_DIR).pipe(toArray()).toPromise(); - const errors = results.map(result => result.error).filter(Boolean); + const errors = results.map((result) => result.error).filter(Boolean); - const packageJsons = results.map(result => result.packageJson).filter(Boolean); + const packageJsons = results.map((result) => result.packageJson).filter(Boolean); - packageJsons.forEach(pack => expect(pack).to.be.an(Object)); + packageJsons.forEach((pack) => expect(pack).to.be.an(Object)); // there should be one result for each item in PLUGINS_DIR expect(results).to.have.length(8); // three of the fixtures are errors of some sort diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js index 3ab08f138b184..769fcd74ce6fb 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js +++ b/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js @@ -53,7 +53,7 @@ describe('plugin discovery/plugin pack', () => { pack.getPluginSpecs(); sinon.assert.calledOnce(provider); sinon.assert.calledWithExactly(provider, { - Plugin: sinon.match(Class => { + Plugin: sinon.match((Class) => { return Class.prototype instanceof PluginSpec; }, 'Subclass of PluginSpec'), }); @@ -96,7 +96,7 @@ describe('plugin discovery/plugin pack', () => { const otherPack = new PluginPack({ path: '/dev/null', pkg: { name: 'foo', version: 'kibana' }, - provider: api => { + provider: (api) => { OtherPluginSpecClass = api.Plugin; }, }); @@ -112,12 +112,12 @@ describe('plugin discovery/plugin pack', () => { new PluginPack({ provider: () => true }), new PluginPack({ provider: () => new Date() }), new PluginPack({ provider: () => /foo.*bar/ }), - new PluginPack({ provider: () => function() {} }), + new PluginPack({ provider: () => function () {} }), new PluginPack({ provider: () => new OtherPluginSpecClass({}) }), ]; for (const pack of badPacks) { - expect(() => pack.getPluginSpecs()).to.throwError(error => { + expect(() => pack.getPluginSpecs()).to.throwError((error) => { expect(error.message).to.contain('unexpected plugin export'); }); } diff --git a/src/legacy/plugin_discovery/plugin_pack/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/create_pack.js index 62e7d9f1914bf..189c2ea324103 100644 --- a/src/legacy/plugin_discovery/plugin_pack/create_pack.js +++ b/src/legacy/plugin_discovery/plugin_pack/create_pack.js @@ -33,7 +33,7 @@ function createPack(packageJson) { return new PluginPack({ path: packageJson.directoryPath, pkg: packageJson.contents, provider }); } -export const createPack$ = packageJson$ => +export const createPack$ = (packageJson$) => packageJson$.pipe( map(({ error, packageJson }) => { if (error) { @@ -50,5 +50,5 @@ export const createPack$ = packageJson$ => }), // createPack can throw errors, and we want them to be represented // like the errors we consume from createPackageJsonAtPath/Directory - catchError(error => [{ error }]) + catchError((error) => [{ error }]) ); diff --git a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js b/src/legacy/plugin_discovery/plugin_pack/lib/fs.js index e2af5fd7e5b10..2b531e314df52 100644 --- a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js +++ b/src/legacy/plugin_discovery/plugin_pack/lib/fs.js @@ -38,7 +38,7 @@ function assertAbsolutePath(path) { async function statTest(path, test) { try { - const stats = await fcb(cb => stat(path, cb)); + const stats = await fcb((cb) => stat(path, cb)); return Boolean(test(stats)); } catch (error) { if (error.code !== 'ENOENT') { @@ -55,7 +55,7 @@ async function statTest(path, test) { */ export async function isDirectory(path) { assertAbsolutePath(path); - return await statTest(path, stat => stat.isDirectory()); + return await statTest(path, (stat) => stat.isDirectory()); } /** @@ -63,18 +63,18 @@ export async function isDirectory(path) { * @param {string} path * @return {Promise>} */ -export const createChildDirectory$ = path => +export const createChildDirectory$ = (path) => Rx.defer(() => { assertAbsolutePath(path); - return fcb(cb => readdir(path, cb)); + return fcb((cb) => readdir(path, cb)); }).pipe( - catchError(error => { + catchError((error) => { throw createInvalidDirectoryError(error, path); }), mergeAll(), - filter(name => !name.startsWith('.')), - map(name => resolve(path, name)), - mergeMap(async absolute => { + filter((name) => !name.startsWith('.')), + map((name) => resolve(path, name)), + mergeMap(async (absolute) => { if (await isDirectory(absolute)) { return [absolute]; } else { diff --git a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js index aa21d8242f3fa..18629ef3ea802 100644 --- a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js +++ b/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js @@ -52,11 +52,11 @@ async function createPackageJsonAtPath(path) { }; } -export const createPackageJsonAtPath$ = path => +export const createPackageJsonAtPath$ = (path) => // If plugin directory contains manifest file, we should skip it since it // should have been handled by the core plugin system already. Rx.defer(() => isNewPlatformPlugin(path)).pipe( - mergeMap(isNewPlatformPlugin => (isNewPlatformPlugin ? [] : createPackageJsonAtPath(path))), - map(packageJson => ({ packageJson })), - catchError(error => [{ error }]) + mergeMap((isNewPlatformPlugin) => (isNewPlatformPlugin ? [] : createPackageJsonAtPath(path))), + map((packageJson) => ({ packageJson })), + catchError((error) => [{ error }]) ); diff --git a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js index 2873f01d90c16..5f0977f4829b8 100644 --- a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js +++ b/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js @@ -36,10 +36,10 @@ import { createPackageJsonAtPath$ } from './package_json_at_path'; * @param {String} path * @return {Array<{pack}|{error}>} */ -export const createPackageJsonsInDirectory$ = path => +export const createPackageJsonsInDirectory$ = (path) => createChildDirectory$(path).pipe( mergeMap(createPackageJsonAtPath$), - catchError(error => { + catchError((error) => { // this error is produced by createChildDirectory$() when the path // is invalid, we return them as an error result similar to how // createPackAtPath$ works when it finds invalid packs in a directory diff --git a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js index 0734a85d587a9..1baf3d104ca84 100644 --- a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js +++ b/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js @@ -63,7 +63,7 @@ export class PluginPack { const specs = [].concat(result === undefined ? [] : result); // verify that all specs are instances of passed "Plugin" class - specs.forEach(spec => { + specs.forEach((spec) => { if (!(spec instanceof api.Plugin)) { throw new TypeError('unexpected plugin export ' + inspect(spec)); } diff --git a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js b/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js index 3649de165f0aa..02675f0bd60f8 100644 --- a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js +++ b/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js @@ -36,21 +36,21 @@ describe('plugin discovery/plugin spec', () => { describe('validation', () => { it('throws if missing spec.id AND Pack has no name', () => { const pack = new PluginPack({ pkg: {} }); - expect(() => new PluginSpec(pack, {})).to.throwError(error => { + expect(() => new PluginSpec(pack, {})).to.throwError((error) => { expect(error.message).to.contain('Unable to determine plugin id'); }); }); it('throws if missing spec.kibanaVersion AND Pack has no version', () => { const pack = new PluginPack({ pkg: { name: 'foo' } }); - expect(() => new PluginSpec(pack, {})).to.throwError(error => { + expect(() => new PluginSpec(pack, {})).to.throwError((error) => { expect(error.message).to.contain('Unable to determine plugin version'); }); }); it('throws if spec.require is defined, but not an array', () => { function assert(require) { - expect(() => new PluginSpec(fooPack, { require })).to.throwError(error => { + expect(() => new PluginSpec(fooPack, { require })).to.throwError((error) => { expect(error.message).to.contain('"plugin.require" must be an array of plugin ids'); }); } @@ -65,7 +65,7 @@ describe('plugin discovery/plugin spec', () => { it('throws if spec.publicDir is truthy and not a string', () => { function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError(error => { + expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { expect(error.message).to.contain( `The "path" argument must be of type string. Received type ${typeof publicDir}` ); @@ -73,14 +73,14 @@ describe('plugin discovery/plugin spec', () => { } assert(1); - assert(function() {}); + assert(function () {}); assert([]); assert(/a.*b/); }); it('throws if spec.publicDir is not an absolute path', () => { function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError(error => { + expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { expect(error.message).to.contain('plugin.publicDir must be an absolute path'); }); } @@ -91,7 +91,7 @@ describe('plugin discovery/plugin spec', () => { it('throws if spec.publicDir basename is not `public`', () => { function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError(error => { + expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { expect(error.message).to.contain('must end with a "public" directory'); }); } @@ -171,13 +171,13 @@ describe('plugin discovery/plugin spec', () => { it('throws if not passed a config service', () => { const { spec } = setup('a.b.c', () => true); - expect(() => spec.isEnabled()).to.throwError(error => { + expect(() => spec.isEnabled()).to.throwError((error) => { expect(error.message).to.contain('must be called with a config service'); }); - expect(() => spec.isEnabled(null)).to.throwError(error => { + expect(() => spec.isEnabled(null)).to.throwError((error) => { expect(error.message).to.contain('must be called with a config service'); }); - expect(() => spec.isEnabled({ get: () => {} })).to.throwError(error => { + expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => { expect(error.message).to.contain('must be called with a config service'); }); }); @@ -214,13 +214,13 @@ describe('plugin discovery/plugin spec', () => { it('throws if not passed a config service', () => { const { spec } = setup(() => true); - expect(() => spec.isEnabled()).to.throwError(error => { + expect(() => spec.isEnabled()).to.throwError((error) => { expect(error.message).to.contain('must be called with a config service'); }); - expect(() => spec.isEnabled(null)).to.throwError(error => { + expect(() => spec.isEnabled(null)).to.throwError((error) => { expect(error.message).to.contain('must be called with a config service'); }); - expect(() => spec.isEnabled({ get: () => {} })).to.throwError(error => { + expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => { expect(error.message).to.contain('must be called with a config service'); }); }); diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index 23a0c35414ae6..1f8c869f17f66 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -24,12 +24,12 @@ export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { const registerLegacyCapabilities = async () => { const capabilitiesList = await Promise.all( kbnServer.pluginSpecs - .map(spec => spec.getUiCapabilitiesProvider()) - .filter(provider => !!provider) - .map(provider => provider(server)) + .map((spec) => spec.getUiCapabilitiesProvider()) + .filter((provider) => !!provider) + .map((provider) => provider(server)) ); - capabilitiesList.forEach(capabilities => { + capabilitiesList.forEach((capabilities) => { kbnServer.newPlatform.setup.core.capabilities.registerProvider(() => capabilities); }); }; diff --git a/src/legacy/server/config/complete.js b/src/legacy/server/config/complete.js index 2cdd8c32bcbeb..7dbb3a722e38f 100644 --- a/src/legacy/server/config/complete.js +++ b/src/legacy/server/config/complete.js @@ -17,8 +17,8 @@ * under the License. */ -export default function(kbnServer, server) { - server.decorate('server', 'config', function() { +export default function (kbnServer, server) { + server.decorate('server', 'config', function () { return kbnServer.config; }); } diff --git a/src/legacy/server/config/complete.test.js b/src/legacy/server/config/complete.test.js index 122c60e103b50..e5484693ae55f 100644 --- a/src/legacy/server/config/complete.test.js +++ b/src/legacy/server/config/complete.test.js @@ -20,7 +20,7 @@ import completeMixin from './complete'; import sinon from 'sinon'; -describe('server/config completeMixin()', function() { +describe('server/config completeMixin()', function () { const sandbox = sinon.createSandbox(); afterEach(() => sandbox.restore()); diff --git a/src/legacy/server/config/config.js b/src/legacy/server/config/config.js index b186071edeaf7..d32ec29e6d701 100644 --- a/src/legacy/server/config/config.js +++ b/src/legacy/server/config/config.js @@ -47,7 +47,7 @@ export class Config { } if (!key) { - return _.each(extension._inner.children, child => { + return _.each(extension._inner.children, (child) => { this.extendSchema(child.schema, _.get(settings, child.key), child.key); }); } @@ -193,9 +193,7 @@ export class Config { getSchema() { if (!this[schema]) { this[schema] = (function convertToSchema(children) { - let schema = Joi.object() - .keys({}) - .default(); + let schema = Joi.object().keys({}).default(); for (const key of Object.keys(children)) { const child = children[key]; diff --git a/src/legacy/server/config/config.test.js b/src/legacy/server/config/config.test.js index e71cd9f0e6ac9..d7dec19b7ca6e 100644 --- a/src/legacy/server/config/config.test.js +++ b/src/legacy/server/config/config.test.js @@ -56,55 +56,55 @@ const schema = Joi.object({ }).default(), }).default(); -describe('lib/config/config', function() { - describe('class Config()', function() { - describe('constructor', function() { - it('should not allow any config if the schema is not passed', function() { +describe('lib/config/config', function () { + describe('class Config()', function () { + describe('constructor', function () { + it('should not allow any config if the schema is not passed', function () { const config = new Config(); - const run = function() { + const run = function () { config.set('something.enable', true); }; expect(run).toThrow(); }); - it('should allow keys in the schema', function() { + it('should allow keys in the schema', function () { const config = new Config(schema); - const run = function() { + const run = function () { config.set('test.client.host', 'http://localhost'); }; expect(run).not.toThrow(); }); - it('should not allow keys not in the schema', function() { + it('should not allow keys not in the schema', function () { const config = new Config(schema); - const run = function() { + const run = function () { config.set('paramNotDefinedInTheSchema', true); }; expect(run).toThrow(); }); - it('should not allow child keys not in the schema', function() { + it('should not allow child keys not in the schema', function () { const config = new Config(schema); - const run = function() { + const run = function () { config.set('test.client.paramNotDefinedInTheSchema', true); }; expect(run).toThrow(); }); - it('should set defaults', function() { + it('should set defaults', function () { const config = new Config(schema); expect(config.get('test.enable')).toBe(true); expect(config.get('test.client.type')).toBe('datastore'); }); }); - describe('#resetTo(object)', function() { + describe('#resetTo(object)', function () { let config; - beforeEach(function() { + beforeEach(function () { config = new Config(schema); }); - it('should reset the config object with new values', function() { + it('should reset the config object with new values', function () { config.set(data); const newData = config.get(); newData.test.enable = false; @@ -113,52 +113,52 @@ describe('lib/config/config', function() { }); }); - describe('#has(key)', function() { + describe('#has(key)', function () { let config; - beforeEach(function() { + beforeEach(function () { config = new Config(schema); }); - it('should return true for fields that exist in the schema', function() { + it('should return true for fields that exist in the schema', function () { expect(config.has('test.undefValue')).toBe(true); }); - it('should return true for partial objects that exist in the schema', function() { + it('should return true for partial objects that exist in the schema', function () { expect(config.has('test.client')).toBe(true); }); - it('should return false for fields that do not exist in the schema', function() { + it('should return false for fields that do not exist in the schema', function () { expect(config.has('test.client.pool')).toBe(false); }); }); - describe('#set(key, value)', function() { + describe('#set(key, value)', function () { let config; - beforeEach(function() { + beforeEach(function () { config = new Config(schema); }); - it('should use a key and value to set a config value', function() { + it('should use a key and value to set a config value', function () { config.set('test.enable', false); expect(config.get('test.enable')).toBe(false); }); - it('should use an object to set config values', function() { + it('should use an object to set config values', function () { const hosts = ['host-01', 'host-02']; config.set({ test: { enable: false, hosts: hosts } }); expect(config.get('test.enable')).toBe(false); expect(config.get('test.hosts')).toEqual(hosts); }); - it('should use a flatten object to set config values', function() { + it('should use a flatten object to set config values', function () { const hosts = ['host-01', 'host-02']; config.set({ 'test.enable': false, 'test.hosts': hosts }); expect(config.get('test.enable')).toBe(false); expect(config.get('test.hosts')).toEqual(hosts); }); - it('should override values with just the values present', function() { + it('should override values with just the values present', function () { const newData = _.cloneDeep(data); config.set(data); newData.test.enable = false; @@ -166,10 +166,10 @@ describe('lib/config/config', function() { expect(config.get()).toEqual(newData); }); - it('should thow an exception when setting a value with the wrong type', function(done) { + it('should thow an exception when setting a value with the wrong type', function (done) { expect.assertions(4); - const run = function() { + const run = function () { config.set('test.enable', 'something'); }; @@ -189,37 +189,37 @@ describe('lib/config/config', function() { }); }); - describe('#get(key)', function() { + describe('#get(key)', function () { let config; - beforeEach(function() { + beforeEach(function () { config = new Config(schema); config.set(data); }); - it('should return the whole config object when called without a key', function() { + it('should return the whole config object when called without a key', function () { const newData = _.cloneDeep(data); newData.test.enable = true; expect(config.get()).toEqual(newData); }); - it('should return the value using dot notation', function() { + it('should return the value using dot notation', function () { expect(config.get('test.enable')).toBe(true); }); - it('should return the clone of partial object using dot notation', function() { + it('should return the clone of partial object using dot notation', function () { expect(config.get('test.client')).not.toBe(data.test.client); expect(config.get('test.client')).toEqual(data.test.client); }); - it('should throw exception for unknown config values', function() { - const run = function() { + it('should throw exception for unknown config values', function () { + const run = function () { config.get('test.does.not.exist'); }; expect(run).toThrowError(/Unknown config key: test.does.not.exist/); }); - it('should not throw exception for undefined known config values', function() { + it('should not throw exception for undefined known config values', function () { const run = function getUndefValue() { config.get('test.undefValue'); }; @@ -227,56 +227,54 @@ describe('lib/config/config', function() { }); }); - describe('#getDefault(key)', function() { + describe('#getDefault(key)', function () { let config; - beforeEach(function() { + beforeEach(function () { config = new Config(schema); config.set(data); }); - describe('dot notation key', function() { - it('should return undefined if there is no default', function() { + describe('dot notation key', function () { + it('should return undefined if there is no default', function () { const hostDefault = config.getDefault('test.client.host'); expect(hostDefault).toBeUndefined(); }); - it('should return default if specified', function() { + it('should return default if specified', function () { const typeDefault = config.getDefault('test.client.type'); expect(typeDefault).toBe('datastore'); }); - it('should throw exception for unknown key', function() { + it('should throw exception for unknown key', function () { expect(() => { config.getDefault('foo.bar'); }).toThrowErrorMatchingSnapshot(); }); }); - describe('array key', function() { - it('should return undefined if there is no default', function() { + describe('array key', function () { + it('should return undefined if there is no default', function () { const hostDefault = config.getDefault(['test', 'client', 'host']); expect(hostDefault).toBeUndefined(); }); - it('should return default if specified', function() { + it('should return default if specified', function () { const typeDefault = config.getDefault(['test', 'client', 'type']); expect(typeDefault).toBe('datastore'); }); - it('should throw exception for unknown key', function() { + it('should throw exception for unknown key', function () { expect(() => { config.getDefault(['foo', 'bar']); }).toThrowErrorMatchingSnapshot(); }); }); - it('object schema with no default should return default value for property', function() { + it('object schema with no default should return default value for property', function () { const noDefaultSchema = Joi.object() .keys({ - foo: Joi.array() - .items(Joi.string().min(1)) - .default(['bar']), + foo: Joi.array().items(Joi.string().min(1)).default(['bar']), }) .required(); @@ -289,12 +287,10 @@ describe('lib/config/config', function() { expect(fooDefault).toEqual(['bar']); }); - it('should return clone of the default', function() { + it('should return clone of the default', function () { const schemaWithArrayDefault = Joi.object() .keys({ - foo: Joi.array() - .items(Joi.string().min(1)) - .default(['bar']), + foo: Joi.array().items(Joi.string().min(1)).default(['bar']), }) .default(); @@ -308,19 +304,19 @@ describe('lib/config/config', function() { }); }); - describe('#extendSchema(key, schema)', function() { + describe('#extendSchema(key, schema)', function () { let config; - beforeEach(function() { + beforeEach(function () { config = new Config(schema); }); - it('should allow you to extend the schema at the top level', function() { + it('should allow you to extend the schema at the top level', function () { const newSchema = Joi.object({ test: Joi.boolean().default(true) }).default(); config.extendSchema(newSchema, {}, 'myTest'); expect(config.get('myTest.test')).toBe(true); }); - it('should allow you to extend the schema with a prefix', function() { + it('should allow you to extend the schema with a prefix', function () { const newSchema = Joi.object({ test: Joi.boolean().default(true) }).default(); config.extendSchema(newSchema, {}, 'prefix.myTest'); expect(config.get('prefix')).toEqual({ myTest: { test: true } }); @@ -328,17 +324,17 @@ describe('lib/config/config', function() { expect(config.get('prefix.myTest.test')).toBe(true); }); - it('should NOT allow you to extend the schema if something else is there', function() { + it('should NOT allow you to extend the schema if something else is there', function () { const newSchema = Joi.object({ test: Joi.boolean().default(true) }).default(); - const run = function() { + const run = function () { config.extendSchema('test', newSchema); }; expect(run).toThrow(); }); }); - describe('#removeSchema(key)', function() { - it('should completely remove the key', function() { + describe('#removeSchema(key)', function () { + it('should completely remove the key', function () { const config = new Config( Joi.object().keys({ a: Joi.number().default(1), @@ -350,7 +346,7 @@ describe('lib/config/config', function() { expect(() => config.get('a')).toThrowError('Unknown config key'); }); - it('only removes existing keys', function() { + it('only removes existing keys', function () { const config = new Config(Joi.object()); expect(() => config.removeSchema('b')).toThrowError('Unknown schema'); diff --git a/src/legacy/server/config/override.test.ts b/src/legacy/server/config/override.test.ts index 4e21a88e79e61..31b01004f72ad 100644 --- a/src/legacy/server/config/override.test.ts +++ b/src/legacy/server/config/override.test.ts @@ -19,8 +19,8 @@ import { override } from './override'; -describe('override(target, source)', function() { - it('should override the values form source to target', function() { +describe('override(target, source)', function () { + it('should override the values form source to target', function () { const target = { test: { enable: true, diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 87db8c184ad36..53f5185442688 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -77,9 +77,7 @@ export default () => .default('') .allow('') .regex(/(^$|^\/.*[^\/]$)/, `start with a slash, don't end with one`), - host: Joi.string() - .hostname() - .default('localhost'), + host: Joi.string().hostname().default('localhost'), port: Joi.number().default(5601), rewriteBasePath: Joi.boolean().when('basePath', { is: '', @@ -139,14 +137,8 @@ export default () => .less(1073741825) // 10MB .default(10485760), - keepFiles: Joi.number() - .greater(2) - .less(1024) - .default(7), - pollingInterval: Joi.number() - .greater(5000) - .less(3600000) - .default(10000), + keepFiles: Joi.number().greater(2).less(1024).default(7), + pollingInterval: Joi.number().greater(5000).less(3600000).default(10000), usePolling: Joi.boolean().default(false), }) .default(), @@ -158,12 +150,8 @@ export default () => }).default(), plugins: Joi.object({ - paths: Joi.array() - .items(Joi.string()) - .default([]), - scanDirs: Joi.array() - .items(Joi.string()) - .default([]), + paths: Joi.array().items(Joi.string()).default([]), + scanDirs: Joi.array().items(Joi.string()).default([]), initialize: Joi.boolean().default(true), }).default(), @@ -180,9 +168,7 @@ export default () => viewCaching: Joi.boolean().default(Joi.ref('$prod')), watch: Joi.boolean().default(false), watchPort: Joi.number().default(5602), - watchHost: Joi.string() - .hostname() - .default('localhost'), + watchHost: Joi.string().hostname().default('localhost'), watchPrebuild: Joi.boolean().default(false), watchProxyTimeout: Joi.number().default(10 * 60000), useBundleCache: Joi.boolean().default(!!process.env.CODE_COVERAGE ? true : Joi.ref('$prod')), @@ -207,25 +193,14 @@ export default () => url: Joi.string(), options: Joi.object({ attribution: Joi.string(), - minZoom: Joi.number() - .min(0, 'Must be 0 or higher') - .default(0), + minZoom: Joi.number().min(0, 'Must be 0 or higher').default(0), maxZoom: Joi.number().default(10), tileSize: Joi.number(), - subdomains: Joi.array() - .items(Joi.string()) - .single(), + subdomains: Joi.array().items(Joi.string()).single(), errorTileUrl: Joi.string().uri(), tms: Joi.boolean(), reuseTiles: Joi.boolean(), - bounds: Joi.array() - .items( - Joi.array() - .items(Joi.number()) - .min(2) - .required() - ) - .min(2), + bounds: Joi.array().items(Joi.array().items(Joi.number()).min(2).required()).min(2), default: Joi.boolean(), }).default({ default: true, @@ -259,9 +234,7 @@ export default () => ) .default([]), }).default(), - manifestServiceUrl: Joi.string() - .default('') - .allow(''), + manifestServiceUrl: Joi.string().default('').allow(''), emsFileApiUrl: Joi.string().default('https://vector.maps.elastic.co'), emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'), emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.8'), diff --git a/src/legacy/server/config/schema.test.js b/src/legacy/server/config/schema.test.js index 03d2fe53c2ce7..aa09c15e9324d 100644 --- a/src/legacy/server/config/schema.test.js +++ b/src/legacy/server/config/schema.test.js @@ -20,7 +20,7 @@ import schemaProvider from './schema'; import Joi from 'joi'; -describe('Config schema', function() { +describe('Config schema', function () { let schema; beforeEach(async () => (schema = await schemaProvider())); @@ -28,38 +28,38 @@ describe('Config schema', function() { return Joi.validate(data, schema, options); } - describe('server', function() { - it('everything is optional', function() { + describe('server', function () { + it('everything is optional', function () { const { error } = validate({}); expect(error).toBe(null); }); - describe('basePath', function() { - it('accepts empty strings', function() { + describe('basePath', function () { + it('accepts empty strings', function () { const { error, value } = validate({ server: { basePath: '' } }); expect(error).toBe(null); expect(value.server.basePath).toBe(''); }); - it('accepts strings with leading slashes', function() { + it('accepts strings with leading slashes', function () { const { error, value } = validate({ server: { basePath: '/path' } }); expect(error).toBe(null); expect(value.server.basePath).toBe('/path'); }); - it('rejects strings with trailing slashes', function() { + it('rejects strings with trailing slashes', function () { const { error } = validate({ server: { basePath: '/path/' } }); expect(error).toHaveProperty('details'); expect(error.details[0]).toHaveProperty('path', ['server', 'basePath']); }); - it('rejects strings without leading slashes', function() { + it('rejects strings without leading slashes', function () { const { error } = validate({ server: { basePath: 'path' } }); expect(error).toHaveProperty('details'); expect(error.details[0]).toHaveProperty('path', ['server', 'basePath']); }); - it('rejects things that are not strings', function() { + it('rejects things that are not strings', function () { for (const value of [1, true, {}, [], /foo/]) { const { error } = validate({ server: { basePath: value } }); expect(error).toHaveProperty('details'); @@ -68,32 +68,32 @@ describe('Config schema', function() { }); }); - describe('rewriteBasePath', function() { + describe('rewriteBasePath', function () { it('defaults to false', () => { const { error, value } = validate({}); expect(error).toBe(null); expect(value.server.rewriteBasePath).toBe(false); }); - it('accepts false', function() { + it('accepts false', function () { const { error, value } = validate({ server: { rewriteBasePath: false } }); expect(error).toBe(null); expect(value.server.rewriteBasePath).toBe(false); }); - it('accepts true if basePath set', function() { + it('accepts true if basePath set', function () { const { error, value } = validate({ server: { basePath: '/foo', rewriteBasePath: true } }); expect(error).toBe(null); expect(value.server.rewriteBasePath).toBe(true); }); - it('rejects true if basePath not set', function() { + it('rejects true if basePath not set', function () { const { error } = validate({ server: { rewriteBasePath: true } }); expect(error).toHaveProperty('details'); expect(error.details[0]).toHaveProperty('path', ['server', 'rewriteBasePath']); }); - it('rejects strings', function() { + it('rejects strings', function () { const { error } = validate({ server: { rewriteBasePath: 'foo' } }); expect(error).toHaveProperty('details'); expect(error.details[0]).toHaveProperty('path', ['server', 'rewriteBasePath']); diff --git a/src/legacy/server/http/index.js b/src/legacy/server/http/index.js index 3649987d89b9a..2d62d12dfd9f3 100644 --- a/src/legacy/server/http/index.js +++ b/src/legacy/server/http/index.js @@ -24,7 +24,7 @@ import Boom from 'boom'; import { registerHapiPlugins } from './register_hapi_plugins'; import { setupBasePathProvider } from './setup_base_path_provider'; -export default async function(kbnServer, server, config) { +export default async function (kbnServer, server, config) { server = kbnServer.server; setupBasePathProvider(kbnServer); @@ -32,7 +32,7 @@ export default async function(kbnServer, server, config) { await registerHapiPlugins(server); // helper for creating view managers for servers - server.decorate('server', 'setupViews', function(path, engines) { + server.decorate('server', 'setupViews', function (path, engines) { this.views({ path: path, isCached: config.get('optimize.viewCaching'), @@ -43,7 +43,7 @@ export default async function(kbnServer, server, config) { server.route({ method: 'GET', path: '/{p*}', - handler: function(req, h) { + handler: function (req, h) { const path = req.path; if (path === '/' || path.charAt(path.length - 1) !== '/') { throw Boom.notFound(); diff --git a/src/legacy/server/http/integration_tests/max_payload_size.test.js b/src/legacy/server/http/integration_tests/max_payload_size.test.js index 7f22f83c78f0e..a019220ca7a2a 100644 --- a/src/legacy/server/http/integration_tests/max_payload_size.test.js +++ b/src/legacy/server/http/integration_tests/max_payload_size.test.js @@ -30,7 +30,7 @@ beforeAll(async () => { path: '/payload_size_check/test/route', method: 'POST', config: { payload: { maxBytes: 200 } }, - handler: req => req.payload.data.slice(0, 5), + handler: (req) => req.payload.data.slice(0, 5), }); }, 30000); @@ -40,9 +40,7 @@ test('accepts payload with a size larger than default but smaller than route con await kbnTestServer.request .post(root, '/payload_size_check/test/route') .send({ - data: Array(150) - .fill('+') - .join(''), + data: Array(150).fill('+').join(''), }) .expect(200, '+++++'); }); @@ -51,9 +49,7 @@ test('fails with 413 if payload size is larger than default and route config all await kbnTestServer.request .post(root, '/payload_size_check/test/route') .send({ - data: Array(250) - .fill('+') - .join(''), + data: Array(250).fill('+').join(''), }) .expect(413, { statusCode: 413, diff --git a/src/legacy/server/http/setup_base_path_provider.js b/src/legacy/server/http/setup_base_path_provider.js index 07917ebd63895..6949d7e2eebd0 100644 --- a/src/legacy/server/http/setup_base_path_provider.js +++ b/src/legacy/server/http/setup_base_path_provider.js @@ -18,7 +18,7 @@ */ export function setupBasePathProvider(kbnServer) { - kbnServer.server.decorate('request', 'getBasePath', function() { + kbnServer.server.decorate('request', 'getBasePath', function () { const request = this; return kbnServer.newPlatform.setup.core.http.basePath.get(request); }); diff --git a/src/legacy/server/i18n/get_translations_path.ts b/src/legacy/server/i18n/get_translations_path.ts index ac7c61dcf8543..a2a292e2278be 100644 --- a/src/legacy/server/i18n/get_translations_path.ts +++ b/src/legacy/server/i18n/get_translations_path.ts @@ -39,7 +39,7 @@ export async function getTranslationPaths({ cwd, glob }: { cwd: string; glob: st const content = await readFileAsync(entryFullPath, 'utf8'); const { translations } = JSON.parse(content) as I18NRCFileStructure; if (translations && translations.length) { - translations.forEach(translation => { + translations.forEach((translation) => { const translationFullPath = resolve(pluginBasePath, translation); translationPaths.push(translationFullPath); }); diff --git a/src/legacy/server/i18n/index.ts b/src/legacy/server/i18n/index.ts index 9902aaa1e8914..09f7022436049 100644 --- a/src/legacy/server/i18n/index.ts +++ b/src/legacy/server/i18n/index.ts @@ -35,10 +35,10 @@ export async function i18nMixin(kbnServer: KbnServer, server: Server, config: Ki cwd: fromRoot('.'), glob: I18N_RC, }), - ...(config.get('plugins.paths') as string[]).map(cwd => + ...(config.get('plugins.paths') as string[]).map((cwd) => getTranslationPaths({ cwd, glob: I18N_RC }) ), - ...(config.get('plugins.scanDirs') as string[]).map(cwd => + ...(config.get('plugins.scanDirs') as string[]).map((cwd) => getTranslationPaths({ cwd, glob: `*/${I18N_RC}` }) ), getTranslationPaths({ @@ -49,7 +49,7 @@ export async function i18nMixin(kbnServer: KbnServer, server: Server, config: Ki const currentTranslationPaths = ([] as string[]) .concat(...translationPaths) - .filter(translationPath => basename(translationPath, '.json') === locale); + .filter((translationPath) => basename(translationPath, '.json') === locale); i18nLoader.registerTranslationFiles(currentTranslationPaths); const translations = await i18nLoader.getTranslationsByLocale(locale); diff --git a/src/legacy/server/i18n/localization/file_integrity.test.mocks.ts b/src/legacy/server/i18n/localization/file_integrity.test.mocks.ts index cb77ce581eff2..9495098ede1a8 100644 --- a/src/legacy/server/i18n/localization/file_integrity.test.mocks.ts +++ b/src/legacy/server/i18n/localization/file_integrity.test.mocks.ts @@ -28,7 +28,7 @@ jest.doMock('fs', () => ({ const streamData = filepath.split(''); let cursor = 0; - readableStream._read = function(size) { + readableStream._read = function (size) { const current = streamData[cursor++]; if (typeof current === 'undefined') { return this.push(null); diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 02491ff872981..40996500bfbe0 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -113,7 +113,6 @@ export interface KibanaCore { legacy: ILegacyInternals; rendering: LegacyServiceSetupDeps['core']['rendering']; uiPlugins: UiPlugins; - uiSettings: LegacyServiceSetupDeps['core']['uiSettings']; savedObjectsClientProvider: LegacyServiceStartDeps['core']['savedObjects']['clientProvider']; }; env: { diff --git a/src/legacy/server/keystore/keystore.test.js b/src/legacy/server/keystore/keystore.test.js index db5276958c41c..0897ce55d086b 100644 --- a/src/legacy/server/keystore/keystore.test.js +++ b/src/legacy/server/keystore/keystore.test.js @@ -28,7 +28,7 @@ const mockUnprotectedKeystoreData = 'I4lzJ9MRy21UcAJki2qFUTj4TYuvhta3LId+RM5UX/dJ2468hQ=='; jest.mock('fs', () => ({ - readFileSync: jest.fn().mockImplementation(path => { + readFileSync: jest.fn().mockImplementation((path) => { if (path.includes('data/unprotected')) { return JSON.stringify(mockUnprotectedKeystoreData); } @@ -43,7 +43,7 @@ jest.mock('fs', () => ({ throw { code: 'EACCES' }; }), - existsSync: jest.fn().mockImplementation(path => { + existsSync: jest.fn().mockImplementation((path) => { return ( path.includes('data/unprotected') || path.includes('data/protected') || diff --git a/src/legacy/server/logging/apply_filters_to_keys.js b/src/legacy/server/logging/apply_filters_to_keys.js index 62e287ec9a286..63e5ab4c62f29 100644 --- a/src/legacy/server/logging/apply_filters_to_keys.js +++ b/src/legacy/server/logging/apply_filters_to_keys.js @@ -51,7 +51,7 @@ function apply(obj, key, action) { return obj; } -export default function(obj, actionsByKey) { +export default function (obj, actionsByKey) { return Object.keys(actionsByKey).reduce((output, key) => { return apply(output, key, actionsByKey[key]); }, toPojo(obj)); diff --git a/src/legacy/server/logging/apply_filters_to_keys.test.js b/src/legacy/server/logging/apply_filters_to_keys.test.js index 7ca26ed0f3bd6..e007157e9488b 100644 --- a/src/legacy/server/logging/apply_filters_to_keys.test.js +++ b/src/legacy/server/logging/apply_filters_to_keys.test.js @@ -19,8 +19,8 @@ import applyFiltersToKeys from './apply_filters_to_keys'; -describe('applyFiltersToKeys(obj, actionsByKey)', function() { - it('applies for each key+prop in actionsByKey', function() { +describe('applyFiltersToKeys(obj, actionsByKey)', function () { + it('applies for each key+prop in actionsByKey', function () { const data = applyFiltersToKeys( { a: { diff --git a/src/legacy/server/logging/configuration.js b/src/legacy/server/logging/configuration.js index 45a17d96a77cf..267dc9a334de8 100644 --- a/src/legacy/server/logging/configuration.js +++ b/src/legacy/server/logging/configuration.js @@ -64,7 +64,7 @@ export default function loggingConfiguration(config) { }, events: _.transform( events, - function(filtered, val, key) { + function (filtered, val, key) { // provide a string compatible way to remove events if (val !== '!') filtered[key] = val; }, diff --git a/src/legacy/server/logging/log_format.js b/src/legacy/server/logging/log_format.js index ca1d756704dd0..9bc1d67dd5857 100644 --- a/src/legacy/server/logging/log_format.js +++ b/src/legacy/server/logging/log_format.js @@ -38,7 +38,7 @@ function serializeError(err = {}) { }; } -const levelColor = function(code) { +const levelColor = function (code) { if (code < 299) return chalk.green(code); if (code < 399) return chalk.yellow(code); if (code < 499) return chalk.magentaBright(code); @@ -128,7 +128,7 @@ export default class TransformObjStream extends Stream.Transform { data.message += ' '; data.message += chalk.gray('load: ['); data.message += get(data, 'os.load', []) - .map(function(val) { + .map(function (val) { return numeral(val).format('0.00'); }) .join(' '); diff --git a/src/legacy/server/logging/log_format_json.js b/src/legacy/server/logging/log_format_json.js index bc9c9e6746f8a..bfceb78b24504 100644 --- a/src/legacy/server/logging/log_format_json.js +++ b/src/legacy/server/logging/log_format_json.js @@ -20,7 +20,7 @@ import LogFormat from './log_format'; import stringify from 'json-stringify-safe'; -const stripColors = function(string) { +const stripColors = function (string) { return string.replace(/\u001b[^m]+m/g, ''); }; diff --git a/src/legacy/server/logging/log_format_json.test.js b/src/legacy/server/logging/log_format_json.test.js index b39891440a75e..31e622ecae611 100644 --- a/src/legacy/server/logging/log_format_json.test.js +++ b/src/legacy/server/logging/log_format_json.test.js @@ -27,7 +27,7 @@ import KbnLoggerJsonFormat from './log_format_json'; const time = +moment('2010-01-01T05:15:59Z', moment.ISO_8601); -const makeEvent = eventType => ({ +const makeEvent = (eventType) => ({ event: eventType, timestamp: time, }); diff --git a/src/legacy/server/logging/log_format_string.js b/src/legacy/server/logging/log_format_string.js index 9cbbbbee33d91..3c18aab2e3d09 100644 --- a/src/legacy/server/logging/log_format_string.js +++ b/src/legacy/server/logging/log_format_string.js @@ -47,11 +47,11 @@ const typeColors = { scss: 'magentaBright', }; -const color = _.memoize(function(name) { +const color = _.memoize(function (name) { return chalk[typeColors[name]] || _.identity; }); -const type = _.memoize(function(t) { +const type = _.memoize(function (t) { return color(t)(_.pad(t, 7).slice(0, 7)); }); @@ -63,12 +63,12 @@ export default class KbnLoggerStringFormat extends LogFormat { const msg = data.error ? color('error')(data.error.stack) : color('message')(data.message); const tags = _(data.tags) - .sortBy(function(tag) { + .sortBy(function (tag) { if (color(tag) === _.identity) return `2${tag}`; if (_.includes(statuses, tag)) return `0${tag}`; return `1${tag}`; }) - .reduce(function(s, t) { + .reduce(function (s, t) { return s + `[${color(t)(t)}]`; }, ''); diff --git a/src/legacy/server/logging/log_reporter.js b/src/legacy/server/logging/log_reporter.js index b784d03a5b86e..4afb00b568844 100644 --- a/src/legacy/server/logging/log_reporter.js +++ b/src/legacy/server/logging/log_reporter.js @@ -51,10 +51,7 @@ export function getLoggerStream({ events, config }) { }); } - logInterceptor - .pipe(squeeze) - .pipe(format) - .pipe(dest); + logInterceptor.pipe(squeeze).pipe(format).pipe(dest); return logInterceptor; } diff --git a/src/legacy/server/logging/rotate/log_rotator.ts b/src/legacy/server/logging/rotate/log_rotator.ts index eeb91fd0f2636..22183b2f0777a 100644 --- a/src/legacy/server/logging/rotate/log_rotator.ts +++ b/src/legacy/server/logging/rotate/log_rotator.ts @@ -115,7 +115,7 @@ export class LogRotator { // await writeFileAsync(tempFile, 'test'); - const usePollingTest$ = new Observable(observer => { + const usePollingTest$ = new Observable((observer) => { // observable complete function const completeFn = (completeStatus: boolean) => { if (this.stalkerUsePollingPolicyTestTimeout) { @@ -297,10 +297,10 @@ export class LogRotator { return ( foundLogFiles - .filter(file => new RegExp(`${logFileBaseName}\\.\\d`).test(file)) + .filter((file) => new RegExp(`${logFileBaseName}\\.\\d`).test(file)) // we use .slice(-1) here in order to retrieve the last number match in the read filenames .sort((a, b) => Number(a.match(/(\d+)/g)!.slice(-1)) - Number(b.match(/(\d+)/g)!.slice(-1))) - .map(filename => `${logFilesFolder}${sep}${filename}`) + .map((filename) => `${logFilesFolder}${sep}${filename}`) ); } diff --git a/src/legacy/server/pid/index.js b/src/legacy/server/pid/index.js index c4402b0542eaa..d7b9da1292252 100644 --- a/src/legacy/server/pid/index.js +++ b/src/legacy/server/pid/index.js @@ -23,14 +23,14 @@ import Bluebird from 'bluebird'; import { unlinkSync as unlink } from 'fs'; const writeFile = Bluebird.promisify(require('fs').writeFile); -export default Bluebird.method(function(kbnServer, server, config) { +export default Bluebird.method(function (kbnServer, server, config) { const path = config.get('pid.file'); if (!path) return; const pid = String(process.pid); return writeFile(path, pid, { flag: 'wx' }) - .catch(function(err) { + .catch(function (err) { if (err.code !== 'EEXIST') throw err; const message = `pid file already exists at ${path}`; @@ -47,18 +47,18 @@ export default Bluebird.method(function(kbnServer, server, config) { return writeFile(path, pid); }) - .then(function() { + .then(function () { server.logWithMetadata(['pid', 'debug'], `wrote pid file to ${path}`, { path: path, pid: pid, }); - const clean = _.once(function() { + const clean = _.once(function () { unlink(path); }); process.once('exit', clean); // for "natural" exits - process.once('SIGINT', function() { + process.once('SIGINT', function () { // for Ctrl-C exits clean(); @@ -66,7 +66,7 @@ export default Bluebird.method(function(kbnServer, server, config) { process.kill(process.pid, 'SIGINT'); }); - process.on('unhandledRejection', function(reason) { + process.on('unhandledRejection', function (reason) { server.log(['warning'], `Detected an unhandled Promise rejection.\n${reason}`); }); }); diff --git a/src/legacy/server/plugins/initialize_mixin.js b/src/legacy/server/plugins/initialize_mixin.js index 9cc317f002c5a..ccf4cd1c1a404 100644 --- a/src/legacy/server/plugins/initialize_mixin.js +++ b/src/legacy/server/plugins/initialize_mixin.js @@ -34,7 +34,7 @@ export async function initializeMixin(kbnServer, server, config) { async function callHookOnPlugins(hookName) { const { plugins } = kbnServer; - const ids = plugins.map(p => p.id); + const ids = plugins.map((p) => p.id); for (const id of ids) { await callPluginHook(hookName, plugins, id, []); diff --git a/src/legacy/server/plugins/lib/call_plugin_hook.js b/src/legacy/server/plugins/lib/call_plugin_hook.js index c62a3460fa3e2..b665869f5d25f 100644 --- a/src/legacy/server/plugins/lib/call_plugin_hook.js +++ b/src/legacy/server/plugins/lib/call_plugin_hook.js @@ -20,7 +20,7 @@ import { last } from 'lodash'; export async function callPluginHook(hookName, plugins, id, history) { - const plugin = plugins.find(plugin => plugin.id === id); + const plugin = plugins.find((plugin) => plugin.id === id); // make sure this is a valid plugin id if (!plugin) { diff --git a/src/legacy/server/plugins/scan_mixin.js b/src/legacy/server/plugins/scan_mixin.js index c66ce8da3a07a..89ebaf920d9d1 100644 --- a/src/legacy/server/plugins/scan_mixin.js +++ b/src/legacy/server/plugins/scan_mixin.js @@ -19,5 +19,5 @@ import { Plugin } from './lib'; export async function scanMixin(kbnServer) { - kbnServer.plugins = kbnServer.pluginSpecs.map(spec => new Plugin(kbnServer, spec)); + kbnServer.plugins = kbnServer.pluginSpecs.map((spec) => new Plugin(kbnServer, spec)); } diff --git a/src/legacy/server/plugins/wait_for_plugins_init.js b/src/legacy/server/plugins/wait_for_plugins_init.js index 1625ee127f56c..144eb5ef803cc 100644 --- a/src/legacy/server/plugins/wait_for_plugins_init.js +++ b/src/legacy/server/plugins/wait_for_plugins_init.js @@ -27,7 +27,7 @@ const queues = new WeakMap(); export function waitForInitSetupMixin(kbnServer) { queues.set(kbnServer, []); - kbnServer.afterPluginsInit = function(callback) { + kbnServer.afterPluginsInit = function (callback) { const queue = queues.get(kbnServer); if (!queue) { diff --git a/src/legacy/server/sass/build.js b/src/legacy/server/sass/build.js index 1ec656786ccc5..2c0a2d84be2c0 100644 --- a/src/legacy/server/sass/build.js +++ b/src/legacy/server/sass/build.js @@ -35,7 +35,7 @@ const copyFile = promisify(fs.copyFile); const mkdirAsync = promisify(fs.mkdir); const UI_ASSETS_DIR = resolve(__dirname, '../../../core/server/core_app/assets'); -const DARK_THEME_IMPORTER = url => { +const DARK_THEME_IMPORTER = (url) => { if (url.includes('eui_colors_light')) { return { file: url.replace('eui_colors_light', 'eui_colors_dark') }; } @@ -101,7 +101,7 @@ export class Build { if (this.urlImports) { processor.use( postcssUrl({ - url: request => { + url: (request) => { if (!request.pathname) { return request.url; } @@ -144,7 +144,7 @@ export class Build { // verify that asset sources exist and import is valid before writing anything await Promise.all( - urlAssets.map(async asset => { + urlAssets.map(async (asset) => { try { await access(asset.path); } catch (e) { @@ -171,7 +171,7 @@ export class Build { // copy non-shared urlAssets await Promise.all( - urlAssets.map(async asset => { + urlAssets.map(async (asset) => { if (!asset.copyTo) { return; } diff --git a/src/legacy/server/sass/build_all.js b/src/legacy/server/sass/build_all.js index 1d3d76d1cb01a..dac6ac87a40d3 100644 --- a/src/legacy/server/sass/build_all.js +++ b/src/legacy/server/sass/build_all.js @@ -23,7 +23,7 @@ import { Build } from './build'; export async function buildAll({ styleSheets, log, buildDir }) { const bundles = await Promise.all( - styleSheets.map(async styleSheet => { + styleSheets.map(async (styleSheet) => { if (!styleSheet.localPath.endsWith('.scss')) { return; } @@ -41,5 +41,5 @@ export async function buildAll({ styleSheets, log, buildDir }) { }) ); - return bundles.filter(v => v); + return bundles.filter((v) => v); } diff --git a/src/legacy/server/sass/index.js b/src/legacy/server/sass/index.js index 9109e1b1dcea7..001457d110276 100644 --- a/src/legacy/server/sass/index.js +++ b/src/legacy/server/sass/index.js @@ -41,9 +41,9 @@ export async function sassMixin(kbnServer, server, config) { let trackedFiles = new Set(); const log = { - info: msg => server.log(['info', 'scss'], msg), - warn: msg => server.log(['warn', 'scss'], msg), - error: msg => server.log(['error', 'scss'], msg), + info: (msg) => server.log(['info', 'scss'], msg), + warn: (msg) => server.log(['warn', 'scss'], msg), + error: (msg) => server.log(['error', 'scss'], msg), }; try { @@ -53,8 +53,8 @@ export async function sassMixin(kbnServer, server, config) { buildDir: fromRoot('built_assets/css'), }); - scssBundles.forEach(bundle => { - bundle.includedFiles.forEach(file => trackedFiles.add(file)); + scssBundles.forEach((bundle) => { + bundle.includedFiles.forEach((file) => trackedFiles.add(file)); server.log(['info', 'scss'], `Compiled CSS: ${bundle.sourcePath} (theme=${bundle.theme})`); }); } catch (error) { @@ -89,13 +89,13 @@ export async function sassMixin(kbnServer, server, config) { // build bundles containing the changed file await Promise.all( - scssBundles.map(async bundle => { + scssBundles.map(async (bundle) => { try { if (await bundle.buildIfIncluded(path)) { server.log(['info', 'scss'], `Compiled ${bundle.sourcePath} due to change in ${path}`); } // if the bundle rebuilt, includedFiles is the new set; otherwise includedFiles is unchanged and remains tracked - bundle.includedFiles.forEach(file => currentlyTrackedFiles.add(file)); + bundle.includedFiles.forEach((file) => currentlyTrackedFiles.add(file)); } catch (error) { const { message, line, file } = error; if (!file) { @@ -113,7 +113,7 @@ export async function sassMixin(kbnServer, server, config) { */ // un-watch files no longer included in any bundle - trackedFiles.forEach(file => { + trackedFiles.forEach((file) => { if (currentlyTrackedFiles.has(file)) { return; } @@ -123,7 +123,7 @@ export async function sassMixin(kbnServer, server, config) { }); // watch files not previously included in any bundle - currentlyTrackedFiles.forEach(file => { + currentlyTrackedFiles.forEach((file) => { if (trackedFiles.has(file)) { return; } diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.js b/src/legacy/server/saved_objects/saved_objects_mixin.js index 26fecc68fda4b..7d84c27bd1ef0 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.js @@ -36,14 +36,14 @@ export function savedObjectsMixin(kbnServer, server) { const mappings = migrator.getActiveMappings(); const allTypes = Object.keys(getRootPropertiesObjects(mappings)); const schema = new SavedObjectsSchema(convertTypesToLegacySchema(typeRegistry.getAllTypes())); - const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type)); + const visibleTypes = allTypes.filter((type) => !schema.isHiddenType(type)); server.decorate('server', 'kibanaMigrator', migrator); - const warn = message => server.log(['warning', 'saved-objects'], message); + const warn = (message) => server.log(['warning', 'saved-objects'], message); // we use kibana.index which is technically defined in the kibana plugin, so if // we don't have the plugin (mainly tests) we can't initialize the saved objects - if (!kbnServer.pluginSpecs.some(p => p.getId() === 'kibana')) { + if (!kbnServer.pluginSpecs.some((p) => p.getId() === 'kibana')) { warn('Saved Objects uninitialized because the Kibana plugin is disabled.'); return; } @@ -55,7 +55,7 @@ export function savedObjectsMixin(kbnServer, server) { throw new TypeError('Repository requires a "callCluster" function to be provided.'); } // throw an exception if an extraType is not defined. - includedHiddenTypes.forEach(type => { + includedHiddenTypes.forEach((type) => { if (!allTypes.includes(type)) { throw new Error(`Missing mappings for saved objects type '${type}'`); } @@ -98,7 +98,7 @@ export function savedObjectsMixin(kbnServer, server) { server.decorate('server', 'savedObjects', service); const savedObjectsClientCache = new WeakMap(); - server.decorate('request', 'getSavedObjectsClient', function(options) { + server.decorate('request', 'getSavedObjectsClient', function (options) { const request = this; if (savedObjectsClientCache.has(request)) { diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.test.js b/src/legacy/server/saved_objects/saved_objects_mixin.test.js index d49b18ee2ce6c..63e4a632ab5e0 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.test.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.test.js @@ -88,7 +88,7 @@ const savedObjectTypes = convertLegacyTypes( ); const typeRegistry = new SavedObjectTypeRegistry(); -savedObjectTypes.forEach(type => typeRegistry.registerType(type)); +savedObjectTypes.forEach((type) => typeRegistry.registerType(type)); const migrator = mockKibanaMigrator.create({ types: savedObjectTypes, @@ -103,7 +103,7 @@ describe('Saved Objects Mixin', () => { 'kibana.index': 'kibana.index', 'savedObjects.maxImportExportSize': 10000, }; - const stubConfig = jest.fn(key => { + const stubConfig = jest.fn((key) => { return config[key]; }); @@ -129,13 +129,6 @@ describe('Saved Objects Mixin', () => { waitUntilReady: jest.fn(), }, }, - newPlatform: { - __internals: { - elasticsearch: { - adminClient: { callAsInternalUser: mockCallCluster }, - }, - }, - }, }; const coreStart = coreMock.createStart(); diff --git a/src/legacy/server/server_extensions/add_memoized_factory_to_request.test.js b/src/legacy/server/server_extensions/add_memoized_factory_to_request.test.js index cbae6450ea405..48bd082468061 100644 --- a/src/legacy/server/server_extensions/add_memoized_factory_to_request.test.js +++ b/src/legacy/server/server_extensions/add_memoized_factory_to_request.test.js @@ -104,7 +104,7 @@ describe('server.addMemoizedFactoryToRequest()', () => { expect(() => server.addMemoizedFactoryToRequest('name', () => {})).not.toThrowError( 'more than one argument' ); - expect(() => server.addMemoizedFactoryToRequest('name', a => {})).not.toThrowError( + expect(() => server.addMemoizedFactoryToRequest('name', (a) => {})).not.toThrowError( 'more than one argument' ); expect(() => server.addMemoizedFactoryToRequest('name', (a, b) => {})).toThrowError( diff --git a/src/legacy/server/server_extensions/server_extensions_mixin.js b/src/legacy/server/server_extensions/server_extensions_mixin.js index fac8eda4adfb1..19c0b24ae15a1 100644 --- a/src/legacy/server/server_extensions/server_extensions_mixin.js +++ b/src/legacy/server/server_extensions/server_extensions_mixin.js @@ -51,7 +51,7 @@ export function serverExtensionsMixin(kbnServer, server) { } const requestCache = new WeakMap(); - server.decorate('request', methodName, function() { + server.decorate('request', methodName, function () { const request = this; if (!requestCache.has(request)) { diff --git a/src/legacy/server/status/index.js b/src/legacy/server/status/index.js index 5bd1efa99eb2c..377a5d74610a9 100644 --- a/src/legacy/server/status/index.js +++ b/src/legacy/server/status/index.js @@ -31,7 +31,7 @@ export function statusMixin(kbnServer, server, config) { const metrics = new Metrics(config, server); const oppsy = new Oppsy(server); - oppsy.on('ops', event => { + oppsy.on('ops', (event) => { // Oppsy has a bad race condition that will modify this data before // we ship it off to the buffer. Let's create our copy first. event = cloneDeep(event); @@ -41,7 +41,7 @@ export function statusMixin(kbnServer, server, config) { // captures (performs transforms on) the latest event data and stashes // the metrics for status/stats API payload - metrics.capture(event).then(data => { + metrics.capture(event).then((data) => { kbnServer.metrics = data; }); }); diff --git a/src/legacy/server/status/lib/__mocks__/_fs_stubs.js b/src/legacy/server/status/lib/__mocks__/_fs_stubs.js index a22822776f461..2be6402baa5fe 100644 --- a/src/legacy/server/status/lib/__mocks__/_fs_stubs.js +++ b/src/legacy/server/status/lib/__mocks__/_fs_stubs.js @@ -19,9 +19,7 @@ export function cGroups(hierarchy) { if (!hierarchy) { - hierarchy = Math.random() - .toString(36) - .substring(7); + hierarchy = Math.random().toString(36).substring(7); } const cpuAcctDir = `/sys/fs/cgroup/cpuacct/${hierarchy}`; @@ -68,7 +66,7 @@ class FSError extends Error { let _mockFiles = Object.create({}); -export const setMockFiles = mockFiles => { +export const setMockFiles = (mockFiles) => { _mockFiles = Object.create({}); if (mockFiles) { const files = Object.keys(mockFiles); diff --git a/src/legacy/server/status/lib/cgroup.js b/src/legacy/server/status/lib/cgroup.js index 8f59ce6d84d0e..4d21cafbedcaa 100644 --- a/src/legacy/server/status/lib/cgroup.js +++ b/src/legacy/server/status/lib/cgroup.js @@ -41,13 +41,13 @@ const CPU_STATS_FILE = 'cpu.stat'; const readFile = promisify(fs.readFile); export function readControlGroups() { - return readFile(PROC_SELF_CGROUP_FILE).then(data => { + return readFile(PROC_SELF_CGROUP_FILE).then((data) => { const response = {}; data .toString() .split(/\n/) - .forEach(line => { + .forEach((line) => { const matches = line.match(CONTROL_GROUP_RE); if (matches === null) { @@ -55,7 +55,7 @@ export function readControlGroups() { } const controllers = matches[1].split(CONTROLLER_SEPARATOR_RE); - controllers.forEach(controller => { + controllers.forEach((controller) => { response[controller] = matches[2]; }); }); @@ -65,7 +65,7 @@ export function readControlGroups() { } function fileContentsToInteger(path) { - return readFile(path).then(data => { + return readFile(path).then((data) => { return parseInt(data.toString(), 10); }); } @@ -91,11 +91,11 @@ export function readCPUStat(controlGroup) { }; readFile(joinPath(PROC_CGROUP_CPU_DIR, controlGroup, CPU_STATS_FILE)) - .then(data => { + .then((data) => { data .toString() .split(/\n/) - .forEach(line => { + .forEach((line) => { const fields = line.split(/\s+/); switch (fields[0]) { @@ -115,7 +115,7 @@ export function readCPUStat(controlGroup) { resolve(stat); }) - .catch(err => { + .catch((err) => { if (err.code === 'ENOENT') { return resolve(stat); } @@ -128,7 +128,7 @@ export function readCPUStat(controlGroup) { export function getAllStats(options = {}) { return new Promise((resolve, reject) => { readControlGroups() - .then(groups => { + .then((groups) => { const cpuPath = options.cpuPath || groups[GROUP_CPU]; const cpuAcctPath = options.cpuAcctPath || groups[GROUP_CPUACCT]; diff --git a/src/legacy/server/status/lib/cgroup.test.js b/src/legacy/server/status/lib/cgroup.test.js index 1134dd89f752e..62feba45d1b3c 100644 --- a/src/legacy/server/status/lib/cgroup.test.js +++ b/src/legacy/server/status/lib/cgroup.test.js @@ -25,7 +25,7 @@ import fs from 'fs'; import { cGroups as cGroupsFsStub, setMockFiles, readFileMock } from './__mocks__/_fs_stubs'; import { getAllStats, readControlGroups, readCPUStat } from './cgroup'; -describe('Control Group', function() { +describe('Control Group', function () { const fsStub = cGroupsFsStub(); beforeAll(() => { diff --git a/src/legacy/server/status/lib/get_os_info.test.js b/src/legacy/server/status/lib/get_os_info.test.js index eaa310f603fa2..11af7e1588090 100644 --- a/src/legacy/server/status/lib/get_os_info.test.js +++ b/src/legacy/server/status/lib/get_os_info.test.js @@ -46,7 +46,7 @@ describe('getOSInfo', () => { os.release.mockImplementation(() => '4.9.93-linuxkit-aufs'); // Mock getos response - getos.mockImplementation(cb => + getos.mockImplementation((cb) => cb(null, { os: 'linux', dist: 'Ubuntu Linux', diff --git a/src/legacy/server/status/lib/metrics.test.js b/src/legacy/server/status/lib/metrics.test.js index 4798ae7c5c888..6a734941eb70c 100644 --- a/src/legacy/server/status/lib/metrics.test.js +++ b/src/legacy/server/status/lib/metrics.test.js @@ -40,7 +40,7 @@ import sinon from 'sinon'; import { cGroups as cGroupsFsStub, setMockFiles, readFileMock } from './__mocks__/_fs_stubs'; import { Metrics } from './metrics'; -describe('Metrics', function() { +describe('Metrics', function () { fs.readFile.mockImplementation(readFileMock); const sampleConfig = { @@ -51,7 +51,7 @@ describe('Metrics', function() { port: 5603, }, }; - const config = { get: path => _.get(sampleConfig, path) }; + const config = { get: (path) => _.get(sampleConfig, path) }; let metrics; diff --git a/src/legacy/server/status/routes/api/register_stats.js b/src/legacy/server/status/routes/api/register_stats.js index 2dd66cb8caff7..09957e61f74d3 100644 --- a/src/legacy/server/status/routes/api/register_stats.js +++ b/src/legacy/server/status/routes/api/register_stats.js @@ -40,12 +40,12 @@ const STATS_NOT_READY_MESSAGE = i18n.translate('server.stats.notReadyMessage', { export function registerStatsApi(usageCollection, server, config, kbnServer) { const wrapAuth = wrapAuthConfig(config.get('status.allowAnonymous')); - const getClusterUuid = async callCluster => { + const getClusterUuid = async (callCluster) => { const { cluster_uuid: uuid } = await callCluster('info', { filterPath: 'cluster_uuid' }); return uuid; }; - const getUsage = async callCluster => { + const getUsage = async (callCluster) => { const usage = await usageCollection.bulkFetchUsage(callCluster); return usageCollection.toObject(usage); }; @@ -54,7 +54,7 @@ export function registerStatsApi(usageCollection, server, config, kbnServer) { /* kibana_stats gets singled out from the collector set as it is used * for health-checking Kibana and fetch does not rely on fetching data * from ES */ - server.newPlatform.setup.core.metrics.getOpsMetrics$().subscribe(metrics => { + server.newPlatform.setup.core.metrics.getOpsMetrics$().subscribe((metrics) => { lastMetrics = { ...metrics, timestamp: new Date().toISOString(), diff --git a/src/legacy/server/status/routes/page/register_status.js b/src/legacy/server/status/routes/page/register_status.js index 08d266e6dbf84..47bd3c34eba59 100644 --- a/src/legacy/server/status/routes/page/register_status.js +++ b/src/legacy/server/status/routes/page/register_status.js @@ -23,7 +23,7 @@ export function registerStatusPage(kbnServer, server, config) { const allowAnonymous = config.get('status.allowAnonymous'); const wrapAuth = wrapAuthConfig(allowAnonymous); - server.decorate('toolkit', 'renderStatusPage', async function() { + server.decorate('toolkit', 'renderStatusPage', async function () { const app = server.getHiddenUiAppById('status_page'); const h = this; diff --git a/src/legacy/server/status/samples.js b/src/legacy/server/status/samples.js index 69f25125e2666..9c41e29945a77 100644 --- a/src/legacy/server/status/samples.js +++ b/src/legacy/server/status/samples.js @@ -25,11 +25,11 @@ function Samples(max) { this.length = 0; } -Samples.prototype.add = function(sample) { +Samples.prototype.add = function (sample) { const vals = this.vals; const length = (this.length = Math.min(this.length + 1, this.max)); - _.forOwn(sample, function(val, name) { + _.forOwn(sample, function (val, name) { if (val == null) val = null; if (!vals[name]) vals[name] = new Array(length); @@ -38,7 +38,7 @@ Samples.prototype.add = function(sample) { }); }; -Samples.prototype.toJSON = function() { +Samples.prototype.toJSON = function () { return this.vals; }; diff --git a/src/legacy/server/status/server_status.js b/src/legacy/server/status/server_status.js index 3be1c17110711..3ee4d37d0b823 100644 --- a/src/legacy/server/status/server_status.js +++ b/src/legacy/server/status/server_status.js @@ -45,7 +45,7 @@ export default class ServerStatus { each(fn) { const self = this; - _.forOwn(self._created, function(status, i, list) { + _.forOwn(self._created, function (status, i, list) { if (status.state !== 'disabled') { fn.call(self, status, i, list); } @@ -57,7 +57,7 @@ export default class ServerStatus { } getForPluginId(pluginId) { - return _.find(this._created, s => s.plugin && s.plugin.id === pluginId); + return _.find(this._created, (s) => s.plugin && s.plugin.id === pluginId); } getState(id) { @@ -77,7 +77,7 @@ export default class ServerStatus { // take all created status objects .values(this._created) // get the state descriptor for each status - .map(status => states.get(status.state)) + .map((status) => states.get(status.state)) // reduce to the state with the highest severity, defaulting to green .reduce((a, b) => (a.severity > b.severity ? a : b), states.get('green')); diff --git a/src/legacy/server/status/server_status.test.js b/src/legacy/server/status/server_status.test.js index 7155e3df69a4f..bf94d693b1310 100644 --- a/src/legacy/server/status/server_status.test.js +++ b/src/legacy/server/status/server_status.test.js @@ -24,13 +24,13 @@ import * as states from './states'; import Status from './status'; import ServerStatus from './server_status'; -describe('ServerStatus class', function() { +describe('ServerStatus class', function () { const plugin = { id: 'name', version: '1.2.3' }; let server; let serverStatus; - beforeEach(function() { + beforeEach(function () { server = { expose: sinon.stub(), logWithMetadata: sinon.stub() }; serverStatus = new ServerStatus(server); }); @@ -42,8 +42,8 @@ describe('ServerStatus class', function() { }); }); - describe('#createForPlugin(plugin)', function() { - it('should create a new status by plugin', function() { + describe('#createForPlugin(plugin)', function () { + it('should create a new status by plugin', function () { const status = serverStatus.createForPlugin(plugin); expect(status).toBeInstanceOf(Status); }); @@ -61,41 +61,41 @@ describe('ServerStatus class', function() { }); }); - describe('#getForPluginId(plugin)', function() { - it('exposes plugin status for the plugin', function() { + describe('#getForPluginId(plugin)', function () { + it('exposes plugin status for the plugin', function () { const status = serverStatus.createForPlugin(plugin); expect(serverStatus.getForPluginId(plugin.id)).toBe(status); }); - it('does not get plain statuses by their id', function() { + it('does not get plain statuses by their id', function () { serverStatus.create('someid'); expect(serverStatus.getForPluginId('someid')).toBe(undefined); }); }); - describe('#getState(id)', function() { - it('should expose the state of a status by id', function() { + describe('#getState(id)', function () { + it('should expose the state of a status by id', function () { const status = serverStatus.create('someid'); status.green(); expect(serverStatus.getState('someid')).toBe('green'); }); }); - describe('#getStateForPluginId(plugin)', function() { - it('should expose the state of a plugin by id', function() { + describe('#getStateForPluginId(plugin)', function () { + it('should expose the state of a plugin by id', function () { const status = serverStatus.createForPlugin(plugin); status.green(); expect(serverStatus.getStateForPluginId(plugin.id)).toBe('green'); }); }); - describe('#overall()', function() { - it('considers each status to produce a summary', function() { + describe('#overall()', function () { + it('considers each status to produce a summary', function () { const status = serverStatus.createForPlugin(plugin); expect(serverStatus.overall().state).toBe('uninitialized'); - const match = function(overall, state) { + const match = function (overall, state) { expect(overall).toHaveProperty('state', state.id); expect(overall).toHaveProperty('title', state.title); expect(overall).toHaveProperty('icon', state.icon); @@ -114,8 +114,8 @@ describe('ServerStatus class', function() { }); }); - describe('#toJSON()', function() { - it('serializes to overall status and individuals', function() { + describe('#toJSON()', function () { + it('serializes to overall status and individuals', function () { const pluginOne = { id: 'one', version: '1.0.0' }; const pluginTwo = { id: 'two', version: '2.0.0' }; const pluginThree = { id: 'three', version: 'kibana' }; @@ -134,7 +134,7 @@ describe('ServerStatus class', function() { expect(json.overall.state).toEqual(serverStatus.overall().state); expect(json.statuses).toHaveLength(4); - const out = status => find(json.statuses, { id: status.id }); + const out = (status) => find(json.statuses, { id: status.id }); expect(out(service)).toHaveProperty('state', 'green'); expect(out(p1)).toHaveProperty('state', 'yellow'); expect(out(p2)).toHaveProperty('state', 'red'); diff --git a/src/legacy/server/status/status.js b/src/legacy/server/status/status.js index 373f3b316b993..10e94da3ac352 100644 --- a/src/legacy/server/status/status.js +++ b/src/legacy/server/status/status.js @@ -33,7 +33,7 @@ export default class Status extends EventEmitter { this.state = 'uninitialized'; this.message = 'uninitialized'; - this.on('change', function(previous, previousMsg) { + this.on('change', function (previous, previousMsg) { this.since = new Date(); const tags = ['status', this.id, this.state === 'red' ? 'error' : 'info']; @@ -81,8 +81,8 @@ export default class Status extends EventEmitter { } } -states.getAll().forEach(function(state) { - Status.prototype[state.id] = function(message) { +states.getAll().forEach(function (state) { + Status.prototype[state.id] = function (message) { if (this.state === 'disabled') return; const previous = this.state; diff --git a/src/legacy/server/status/status.test.js b/src/legacy/server/status/status.test.js index 89c0da74febe9..def7b5a2182e1 100644 --- a/src/legacy/server/status/status.test.js +++ b/src/legacy/server/status/status.test.js @@ -20,13 +20,13 @@ import sinon from 'sinon'; import ServerStatus from './server_status'; -describe('Status class', function() { +describe('Status class', function () { const plugin = { id: 'test', version: '1.2.3' }; let server; let serverStatus; - beforeEach(function() { + beforeEach(function () { server = { expose: sinon.stub(), logWithMetadata: sinon.stub() }; serverStatus = new ServerStatus(server); }); @@ -35,15 +35,15 @@ describe('Status class', function() { expect(serverStatus.createForPlugin(plugin)).toHaveProperty('state', 'uninitialized'); }); - it('emits change when the status is set', function(done) { + it('emits change when the status is set', function (done) { const status = serverStatus.createForPlugin(plugin); - status.once('change', function(prevState, prevMsg, newState, newMsg) { + status.once('change', function (prevState, prevMsg, newState, newMsg) { expect(newState).toBe('green'); expect(newMsg).toBe('GREEN'); expect(prevState).toBe('uninitialized'); - status.once('change', function(prevState, prevMsg, newState, newMsg) { + status.once('change', function (prevState, prevMsg, newState, newMsg) { expect(newState).toBe('red'); expect(newMsg).toBe('RED'); expect(prevState).toBe('green'); @@ -58,7 +58,7 @@ describe('Status class', function() { status.green('GREEN'); }); - it('should only trigger the change listener when something changes', function() { + it('should only trigger the change listener when something changes', function () { const status = serverStatus.createForPlugin(plugin); const stub = sinon.stub(); status.on('change', stub); @@ -68,7 +68,7 @@ describe('Status class', function() { sinon.assert.calledTwice(stub); }); - it('should create a JSON representation of the status', function() { + it('should create a JSON representation of the status', function () { const status = serverStatus.createForPlugin(plugin); status.green('Ready'); @@ -78,12 +78,12 @@ describe('Status class', function() { expect(json.message).toEqual('Ready'); }); - it('should call on handler if status is already matched', function(done) { + it('should call on handler if status is already matched', function (done) { const status = serverStatus.createForPlugin(plugin); const msg = 'Test Ready'; status.green(msg); - status.on('green', function(prev, prevMsg) { + status.on('green', function (prev, prevMsg) { expect(arguments.length).toBe(2); expect(prev).toBe('green'); expect(prevMsg).toBe(msg); @@ -92,12 +92,12 @@ describe('Status class', function() { }); }); - it('should call once handler if status is already matched', function(done) { + it('should call once handler if status is already matched', function (done) { const status = serverStatus.createForPlugin(plugin); const msg = 'Test Ready'; status.green(msg); - status.once('green', function(prev, prevMsg) { + status.once('green', function (prev, prevMsg) { expect(arguments.length).toBe(2); expect(prev).toBe('green'); expect(prevMsg).toBe(msg); @@ -107,7 +107,7 @@ describe('Status class', function() { }); function testState(color) { - it(`should change the state to ${color} when #${color}() is called`, function() { + it(`should change the state to ${color} when #${color}() is called`, function () { const status = serverStatus.createForPlugin(plugin); const message = 'testing ' + color; status[color](message); @@ -115,10 +115,10 @@ describe('Status class', function() { expect(status).toHaveProperty('message', message); }); - it(`should trigger the "change" listener when #${color}() is called`, function(done) { + it(`should trigger the "change" listener when #${color}() is called`, function (done) { const status = serverStatus.createForPlugin(plugin); const message = 'testing ' + color; - status.on('change', function(prev, prevMsg) { + status.on('change', function (prev, prevMsg) { expect(status.state).toBe(color); expect(status.message).toBe(message); @@ -129,10 +129,10 @@ describe('Status class', function() { status[color](message); }); - it(`should trigger the "${color}" listener when #${color}() is called`, function(done) { + it(`should trigger the "${color}" listener when #${color}() is called`, function (done) { const status = serverStatus.createForPlugin(plugin); const message = 'testing ' + color; - status.on(color, function() { + status.on(color, function () { expect(status.state).toBe(color); expect(status.message).toBe(message); done(); diff --git a/src/legacy/server/status/wrap_auth_config.js b/src/legacy/server/status/wrap_auth_config.js index a55318ee49582..04e71a02d30de 100644 --- a/src/legacy/server/status/wrap_auth_config.js +++ b/src/legacy/server/status/wrap_auth_config.js @@ -19,9 +19,9 @@ import { assign, identity } from 'lodash'; -export const wrapAuthConfig = allowAnonymous => { +export const wrapAuthConfig = (allowAnonymous) => { if (allowAnonymous) { - return options => assign(options, { config: { auth: false } }); + return (options) => assign(options, { config: { auth: false } }); } return identity; }; diff --git a/src/legacy/server/status/wrap_auth_config.test.js b/src/legacy/server/status/wrap_auth_config.test.js index 9b2a1c33d4ff1..fa0230a96a587 100644 --- a/src/legacy/server/status/wrap_auth_config.test.js +++ b/src/legacy/server/status/wrap_auth_config.test.js @@ -26,7 +26,7 @@ describe('Status wrapAuthConfig', () => { options = { method: 'GET', path: '/status', - handler: function(request, h) { + handler: function (request, h) { return h.response(); }, }; diff --git a/src/legacy/server/utils/prompt.js b/src/legacy/server/utils/prompt.js index 3fdac1b685808..2e53558213140 100644 --- a/src/legacy/server/utils/prompt.js +++ b/src/legacy/server/utils/prompt.js @@ -33,11 +33,11 @@ export function confirm(question, options = {}) { output: options.output || process.stdout, }); - return new Promise(resolve => { + return new Promise((resolve) => { const defaultValue = options.default ? true : false; const defaultPrompt = defaultValue ? 'Y/n' : 'y/N'; - rl.question(`${question} [${defaultPrompt}] `, input => { + rl.question(`${question} [${defaultPrompt}] `, (input) => { let value = defaultValue; if (input != null && input !== '') { @@ -65,8 +65,8 @@ export function question(question, options = {}) { const questionPrompt = `${question}: `; const rl = createInterface({ input, output }); - return new Promise(resolve => { - input.on('data', char => { + return new Promise((resolve) => { + input.on('data', (char) => { char = char + ''; switch (char) { @@ -85,7 +85,7 @@ export function question(question, options = {}) { } }); - rl.question(questionPrompt, value => { + rl.question(questionPrompt, (value) => { resolve(value); }); }); diff --git a/src/legacy/server/warnings/index.js b/src/legacy/server/warnings/index.js index 6fa29d0f407c6..e96366893076a 100644 --- a/src/legacy/server/warnings/index.js +++ b/src/legacy/server/warnings/index.js @@ -17,8 +17,8 @@ * under the License. */ -export default function(kbnServer, server) { - process.on('warning', warning => { +export default function (kbnServer, server) { + process.on('warning', (warning) => { // deprecation warnings do no reflect a current problem for // the user and therefor should be filtered out. if (warning.name === 'DeprecationWarning') { diff --git a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js index 1c2d08c968dc0..afe618c6d3d9c 100644 --- a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js +++ b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js @@ -19,7 +19,7 @@ import Bluebird from 'bluebird'; -export default kibana => +export default (kibana) => new kibana.Plugin({ config(Joi) { return Joi.object() diff --git a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js b/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js index 8c11a2ee160c0..975a1dc7c92e7 100644 --- a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js +++ b/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js @@ -17,7 +17,7 @@ * under the License. */ -export default kibana => +export default (kibana) => new kibana.Plugin({ config(Joi) { return Joi.object() diff --git a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js index 8c11a2ee160c0..975a1dc7c92e7 100644 --- a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js +++ b/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js @@ -17,7 +17,7 @@ * under the License. */ -export default kibana => +export default (kibana) => new kibana.Plugin({ config(Joi) { return Joi.object() diff --git a/src/legacy/ui/__tests__/fixtures/test_app/index.js b/src/legacy/ui/__tests__/fixtures/test_app/index.js index 1491e6135c096..3eddefd618ce0 100644 --- a/src/legacy/ui/__tests__/fixtures/test_app/index.js +++ b/src/legacy/ui/__tests__/fixtures/test_app/index.js @@ -17,7 +17,7 @@ * under the License. */ -export default kibana => +export default (kibana) => new kibana.Plugin({ uiExports: { app: { diff --git a/src/legacy/ui/apm/index.js b/src/legacy/ui/apm/index.js index e2ff415865b56..f1074091e2624 100644 --- a/src/legacy/ui/apm/index.js +++ b/src/legacy/ui/apm/index.js @@ -30,22 +30,21 @@ export function apmInit(config) { return apmEnabled ? `init(${config})` : ''; } -export function getApmConfig(appMetadata) { +export function getApmConfig(app) { if (!apmEnabled) { - return {}; + return null; } /** * we use the injected app metadata from the server to extract the - * app URL path to be used for page-load transaction + * app id to be used for page-load transaction */ - const navLink = appMetadata.getNavLink(); - const pageUrl = navLink ? navLink.toJSON().url : appMetadata._url; + const appId = app.getId(); const config = { ...getConfig('kibana-frontend'), ...{ active: true, - pageLoadTransactionName: pageUrl, + pageLoadTransactionName: appId, }, }; /** diff --git a/src/legacy/ui/public/__tests__/events.js b/src/legacy/ui/public/__tests__/events.js index aff756cd1807d..c225c2a8ac1c0 100644 --- a/src/legacy/ui/public/__tests__/events.js +++ b/src/legacy/ui/public/__tests__/events.js @@ -26,7 +26,7 @@ import '../private'; import { createDefer } from 'ui/promises'; import { createLegacyClass } from '../utils/legacy_class'; -describe('Events', function() { +describe('Events', function () { require('test_utils/no_digest_promises').activateForSuite(); let Events; @@ -35,16 +35,16 @@ describe('Events', function() { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector, Private) { + ngMock.inject(function ($injector, Private) { Promise = $injector.get('Promise'); Events = Private(EventsProvider); eventsInstance = new Events(); }) ); - it('should handle on events', function() { + it('should handle on events', function () { const obj = new Events(); - const prom = obj.on('test', function(message) { + const prom = obj.on('test', function (message) { expect(message).to.equal('Hello World'); }); @@ -53,14 +53,14 @@ describe('Events', function() { return prom; }); - it('should work with inherited objects', function() { + it('should work with inherited objects', function () { createLegacyClass(MyEventedObject).inherits(Events); function MyEventedObject() { MyEventedObject.Super.call(this); } const obj = new MyEventedObject(); - const prom = obj.on('test', function(message) { + const prom = obj.on('test', function (message) { expect(message).to.equal('Hello World'); }); @@ -69,7 +69,7 @@ describe('Events', function() { return prom; }); - it('should clear events when off is called', function() { + it('should clear events when off is called', function () { const obj = new Events(); obj.on('test', _.noop); expect(obj._listeners).to.have.property('test'); @@ -78,7 +78,7 @@ describe('Events', function() { expect(obj._listeners).to.not.have.property('test'); }); - it('should clear a specific handler when off is called for an event', function() { + it('should clear a specific handler when off is called for an event', function () { const obj = new Events(); const handler1 = sinon.stub(); const handler2 = sinon.stub(); @@ -87,13 +87,13 @@ describe('Events', function() { expect(obj._listeners).to.have.property('test'); obj.off('test', handler1); - return obj.emit('test', 'Hello World').then(function() { + return obj.emit('test', 'Hello World').then(function () { sinon.assert.calledOnce(handler2); sinon.assert.notCalled(handler1); }); }); - it('should clear a all handlers when off is called for an event', function() { + it('should clear a all handlers when off is called for an event', function () { const obj = new Events(); const handler1 = sinon.stub(); obj.on('test', handler1); @@ -101,19 +101,19 @@ describe('Events', function() { obj.off('test'); expect(obj._listeners).to.not.have.property('test'); - return obj.emit('test', 'Hello World').then(function() { + return obj.emit('test', 'Hello World').then(function () { sinon.assert.notCalled(handler1); }); }); - it('should handle multiple identical emits in the same tick', function() { + it('should handle multiple identical emits in the same tick', function () { const obj = new Events(); const handler1 = sinon.stub(); obj.on('test', handler1); const emits = [obj.emit('test', 'one'), obj.emit('test', 'two'), obj.emit('test', 'three')]; - return Promise.all(emits).then(function() { + return Promise.all(emits).then(function () { expect(handler1.callCount).to.be(emits.length); expect(handler1.getCall(0).calledWith('one')).to.be(true); expect(handler1.getCall(1).calledWith('two')).to.be(true); @@ -121,11 +121,11 @@ describe('Events', function() { }); }); - it('should handle emits from the handler', function() { + it('should handle emits from the handler', function () { const obj = new Events(); const secondEmit = createDefer(Promise); - const handler1 = sinon.spy(function() { + const handler1 = sinon.spy(function () { if (handler1.calledTwice) { return; } @@ -134,12 +134,12 @@ describe('Events', function() { obj.on('test', handler1); - return Promise.all([obj.emit('test'), secondEmit.promise]).then(function() { + return Promise.all([obj.emit('test'), secondEmit.promise]).then(function () { expect(handler1.callCount).to.be(2); }); }); - it('should only emit to handlers registered before emit is called', function() { + it('should only emit to handlers registered before emit is called', function () { const obj = new Events(); const handler1 = sinon.stub(); const handler2 = sinon.stub(); @@ -147,34 +147,34 @@ describe('Events', function() { obj.on('test', handler1); const emits = [obj.emit('test', 'one'), obj.emit('test', 'two'), obj.emit('test', 'three')]; - return Promise.all(emits).then(function() { + return Promise.all(emits).then(function () { expect(handler1.callCount).to.be(emits.length); obj.on('test', handler2); const emits2 = [obj.emit('test', 'four'), obj.emit('test', 'five'), obj.emit('test', 'six')]; - return Promise.all(emits2).then(function() { + return Promise.all(emits2).then(function () { expect(handler1.callCount).to.be(emits.length + emits2.length); expect(handler2.callCount).to.be(emits2.length); }); }); }); - it('should pass multiple arguments from the emitter', function() { + it('should pass multiple arguments from the emitter', function () { const obj = new Events(); const handler = sinon.stub(); const payload = ['one', { hello: 'tests' }, null]; obj.on('test', handler); - return obj.emit('test', payload[0], payload[1], payload[2]).then(function() { + return obj.emit('test', payload[0], payload[1], payload[2]).then(function () { expect(handler.callCount).to.be(1); expect(handler.calledWithExactly(payload[0], payload[1], payload[2])).to.be(true); }); }); - it('should preserve the scope of the handler', function() { + it('should preserve the scope of the handler', function () { const obj = new Events(); const expected = 'some value'; let testValue; @@ -185,12 +185,12 @@ describe('Events', function() { handler.getVal = _.constant(expected); obj.on('test', handler); - return obj.emit('test').then(function() { + return obj.emit('test').then(function () { expect(testValue).to.equal(expected); }); }); - it('should always emit in the same order', function() { + it('should always emit in the same order', function () { const handler = sinon.stub(); const obj = new Events(); @@ -208,15 +208,15 @@ describe('Events', function() { obj.emit('block'), obj.emit('block'), obj.emit('last'), - ]).then(function() { + ]).then(function () { expect(handler.callCount).to.be(10); - handler.args.forEach(function(args, i) { + handler.args.forEach(function (args, i) { expect(args[0]).to.be(i < 9 ? 'block' : 'last'); }); }); }); - it('calls emitted handlers asynchronously', done => { + it('calls emitted handlers asynchronously', (done) => { const listenerStub = sinon.stub(); eventsInstance.on('test', listenerStub); eventsInstance.emit('test'); @@ -228,7 +228,7 @@ describe('Events', function() { }, 100); }); - it('calling off after an emit that has not yet triggered the handler, will not call the handler', done => { + it('calling off after an emit that has not yet triggered the handler, will not call the handler', (done) => { const listenerStub = sinon.stub(); eventsInstance.on('test', listenerStub); eventsInstance.emit('test'); diff --git a/src/legacy/ui/public/accessibility/__tests__/kbn_accessible_click.js b/src/legacy/ui/public/accessibility/__tests__/kbn_accessible_click.js index 381b8655ac374..5466e7d43f566 100644 --- a/src/legacy/ui/public/accessibility/__tests__/kbn_accessible_click.js +++ b/src/legacy/ui/public/accessibility/__tests__/kbn_accessible_click.js @@ -31,7 +31,7 @@ describe('kbnAccessibleClick directive', () => { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function(_$compile_, _$rootScope_) { + ngMock.inject(function (_$compile_, _$rootScope_) { $compile = _$compile_; $rootScope = _$rootScope_; }) @@ -103,7 +103,7 @@ describe('kbnAccessibleClick directive', () => { let scope; let element; - beforeEach(function() { + beforeEach(function () { scope = $rootScope.$new(); scope.handleClick = sinon.stub(); const html = `
`; diff --git a/src/legacy/ui/public/accessibility/angular_aria.js b/src/legacy/ui/public/accessibility/angular_aria.js index 00e773619aa50..4335eddf0d8cd 100644 --- a/src/legacy/ui/public/accessibility/angular_aria.js +++ b/src/legacy/ui/public/accessibility/angular_aria.js @@ -30,7 +30,7 @@ import { uiModules } from '../modules'; * handling keyboard events for `ngClick` directives. Kibana uses `kbnAccessibleClick` to handle * those cases where you need an `ngClick` non button element to have keyboard access. */ -uiModules.get('kibana', ['ngAria']).config($ariaProvider => { +uiModules.get('kibana', ['ngAria']).config(($ariaProvider) => { $ariaProvider.config({ bindKeydown: false, bindRoleForClick: false, diff --git a/src/legacy/ui/public/accessibility/kbn_ui_ace_keyboard_mode.js b/src/legacy/ui/public/accessibility/kbn_ui_ace_keyboard_mode.js index b18464b9ab90f..9ffcbc426e49c 100644 --- a/src/legacy/ui/public/accessibility/kbn_ui_ace_keyboard_mode.js +++ b/src/legacy/ui/public/accessibility/kbn_ui_ace_keyboard_mode.js @@ -71,7 +71,7 @@ uiModules hint.removeClass('kbnUiAceKeyboardHint-isInactive'); } - hint.keydown(ev => { + hint.keydown((ev) => { if (ev.keyCode === keyCodes.ENTER) { ev.preventDefault(); startEditing(); @@ -102,7 +102,7 @@ uiModules { capture: true } ); - uiAceTextbox.keydown(ev => { + uiAceTextbox.keydown((ev) => { if (ev.keyCode === keyCodes.ESCAPE) { // If the autocompletion context menu is open then we want to let ESC close it but // **not** exit out of editing mode. @@ -121,7 +121,7 @@ uiModules element.prepend(hint); }, })) - .directive('kbnUiAceKeyboardMode', kbnUiAceKeyboardModeService => ({ + .directive('kbnUiAceKeyboardMode', (kbnUiAceKeyboardModeService) => ({ restrict: 'A', link(scope, element) { kbnUiAceKeyboardModeService.initialize(scope, element); diff --git a/src/legacy/ui/public/accessibility/scrollto_activedescendant.js b/src/legacy/ui/public/accessibility/scrollto_activedescendant.js index d8883bd5d427b..1034cb1df3dda 100644 --- a/src/legacy/ui/public/accessibility/scrollto_activedescendant.js +++ b/src/legacy/ui/public/accessibility/scrollto_activedescendant.js @@ -30,7 +30,7 @@ uiModules.get('kibana').directive('scrolltoActivedescendant', () => ({ link(scope, element, attrs) { scope.$watch( () => attrs.ariaActivedescendant, - val => { + (val) => { if (val) { const activeDescendant = element.find(`#${val}`); if (activeDescendant.length) { diff --git a/src/legacy/ui/public/binder/__tests__/binder.js b/src/legacy/ui/public/binder/__tests__/binder.js index f30442c86d6ab..de30df36f6b2b 100644 --- a/src/legacy/ui/public/binder/__tests__/binder.js +++ b/src/legacy/ui/public/binder/__tests__/binder.js @@ -24,25 +24,25 @@ import ngMock from 'ng_mock'; import { Binder } from '..'; import $ from 'jquery'; -describe('Binder class', function() { +describe('Binder class', function () { let $scope; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($rootScope) { + ngMock.inject(function ($rootScope) { $scope = $rootScope.$new(); }) ); - describe('Constructing with a $scope', function() { - it('accepts a $scope and listens for $destroy', function() { + describe('Constructing with a $scope', function () { + it('accepts a $scope and listens for $destroy', function () { sinon.stub($scope, '$on'); new Binder($scope); expect($scope.$on.callCount).to.be(1); expect($scope.$on.args[0][0]).to.be('$destroy'); }); - it('unbinds when the $scope is destroyed', function() { + it('unbinds when the $scope is destroyed', function () { const binder = new Binder($scope); sinon.stub(binder, 'destroy'); $scope.$destroy(); @@ -50,8 +50,8 @@ describe('Binder class', function() { }); }); - describe('Binder#on', function() { - it('binds to normal event emitters', function() { + describe('Binder#on', function () { + it('binds to normal event emitters', function () { const binder = new Binder(); const emitter = { on: sinon.stub(), @@ -71,8 +71,8 @@ describe('Binder class', function() { }); }); - describe('Binder#jqOn', function() { - it('binds jquery event handlers', function() { + describe('Binder#jqOn', function () { + it('binds jquery event handlers', function () { const binder = new Binder(); const el = document.createElement('div'); const handler = sinon.stub(); diff --git a/src/legacy/ui/public/binder/binder.js b/src/legacy/ui/public/binder/binder.js index 8b0f95ab87fef..0d535d3bdcb0e 100644 --- a/src/legacy/ui/public/binder/binder.js +++ b/src/legacy/ui/public/binder/binder.js @@ -39,7 +39,7 @@ export class Binder extends BinderBase { } fakeD3Bind(el, event, handler) { - this.jqOn(el, event, e => { + this.jqOn(el, event, (e) => { // mimic https://github.com/mbostock/d3/blob/3abb00113662463e5c19eb87cd33f6d0ddc23bc0/src/selection/on.js#L87-L94 const o = d3.event; // Events can be reentrant (e.g., focus). d3.event = e; diff --git a/src/legacy/ui/public/bound_to_config_obj.js b/src/legacy/ui/public/bound_to_config_obj.js index 1bf13b4da951c..dc1eedebe2b77 100644 --- a/src/legacy/ui/public/bound_to_config_obj.js +++ b/src/legacy/ui/public/bound_to_config_obj.js @@ -37,7 +37,7 @@ export function BoundToConfigObjProvider(config) { function BoundToConfigObj(input) { const self = this; - _.forOwn(input, function(value, prop) { + _.forOwn(input, function (value, prop) { if (!_.isString(value) || value.charAt(0) !== '=') { self[prop] = value; return; diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx index b6ffca350239c..90da657cc93e0 100644 --- a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx +++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { UICapabilitiesContext } from './ui_capabilities_context'; import { capabilities } from '..'; -export const UICapabilitiesProvider: React.FC = props => ( +export const UICapabilitiesProvider: React.FC = (props) => ( {props.children} diff --git a/src/legacy/ui/public/chrome/__mocks__/index.js b/src/legacy/ui/public/chrome/__mocks__/index.js index 8f88cf5c65ecf..d6f0df83a0e3d 100644 --- a/src/legacy/ui/public/chrome/__mocks__/index.js +++ b/src/legacy/ui/public/chrome/__mocks__/index.js @@ -27,7 +27,7 @@ const uiSettingsClient = { }; const chrome = { - addBasePath: path => (path ? path : 'test/base/path'), + addBasePath: (path) => (path ? path : 'test/base/path'), breadcrumbs: { set: () => ({}), }, @@ -58,8 +58,8 @@ const internals = _.defaults(_.cloneDeep(metadata), { applicationClasses: [], }); -const waitForBootstrap = new Promise(resolve => { - chrome.bootstrap = function(targetDomElement) { +const waitForBootstrap = new Promise((resolve) => { + chrome.bootstrap = function (targetDomElement) { // import chrome nav controls and hacks now so that they are executed after // everything else, can safely import the chrome, and interact with services // and such setup by all other modules @@ -79,7 +79,7 @@ const waitForBootstrap = new Promise(resolve => { }); chrome.dangerouslyGetActiveInjector = () => { - return waitForBootstrap.then(targetDomElement => { + return waitForBootstrap.then((targetDomElement) => { const $injector = angular.element(targetDomElement).injector(); if (!$injector) { return Promise.reject('targetDomElement had no angular context after bootstrapping'); diff --git a/src/legacy/ui/public/chrome/api/__tests__/apps.js b/src/legacy/ui/public/chrome/api/__tests__/apps.js index 7158b7ef22263..98da8db52bad1 100644 --- a/src/legacy/ui/public/chrome/api/__tests__/apps.js +++ b/src/legacy/ui/public/chrome/api/__tests__/apps.js @@ -21,43 +21,43 @@ import expect from '@kbn/expect'; import setup from '../apps'; -describe('Chrome API :: apps', function() { - describe('#get/setShowAppsLink()', function() { - describe('defaults to false if there are less than two apps', function() { - it('appCount = 0', function() { +describe('Chrome API :: apps', function () { + describe('#get/setShowAppsLink()', function () { + describe('defaults to false if there are less than two apps', function () { + it('appCount = 0', function () { const chrome = {}; setup(chrome, { nav: [] }); expect(chrome.getShowAppsLink()).to.equal(false); }); - it('appCount = 1', function() { + it('appCount = 1', function () { const chrome = {}; setup(chrome, { nav: [{ url: '/' }] }); expect(chrome.getShowAppsLink()).to.equal(false); }); }); - describe('defaults to true if there are two or more apps', function() { - it('appCount = 2', function() { + describe('defaults to true if there are two or more apps', function () { + it('appCount = 2', function () { const chrome = {}; setup(chrome, { nav: [{ url: '/' }, { url: '/2' }] }); expect(chrome.getShowAppsLink()).to.equal(true); }); - it('appCount = 3', function() { + it('appCount = 3', function () { const chrome = {}; setup(chrome, { nav: [{ url: '/' }, { url: '/2' }, { url: '/3' }] }); expect(chrome.getShowAppsLink()).to.equal(true); }); }); - it('is chainable', function() { + it('is chainable', function () { const chrome = {}; setup(chrome, { nav: [{ url: '/' }] }); expect(chrome.setShowAppsLink(true)).to.equal(chrome); }); - it('can be changed', function() { + it('can be changed', function () { const chrome = {}; setup(chrome, { nav: [{ url: '/' }] }); @@ -69,8 +69,8 @@ describe('Chrome API :: apps', function() { }); }); - describe('#getApp()', function() { - it('returns a clone of the current app', function() { + describe('#getApp()', function () { + it('returns a clone of the current app', function () { const chrome = {}; const app = { url: '/' }; setup(chrome, { app }); @@ -79,30 +79,30 @@ describe('Chrome API :: apps', function() { expect(chrome.getApp()).to.not.equal(app); }); - it('returns undefined if no active app', function() { + it('returns undefined if no active app', function () { const chrome = {}; setup(chrome, {}); expect(chrome.getApp()).to.equal(undefined); }); }); - describe('#getAppTitle()', function() { - it('returns the title property of the current app', function() { + describe('#getAppTitle()', function () { + it('returns the title property of the current app', function () { const chrome = {}; const app = { url: '/', title: 'foo' }; setup(chrome, { app }); expect(chrome.getAppTitle()).to.eql('foo'); }); - it('returns undefined if no active app', function() { + it('returns undefined if no active app', function () { const chrome = {}; setup(chrome, {}); expect(chrome.getAppTitle()).to.equal(undefined); }); }); - describe('#getAppUrl()', function() { - it('returns the resolved url of the current app', function() { + describe('#getAppUrl()', function () { + it('returns the resolved url of the current app', function () { const chrome = {}; const app = { navLink: { url: '/foo' } }; setup(chrome, { app }); @@ -112,7 +112,7 @@ describe('Chrome API :: apps', function() { expect(chrome.getAppUrl()).to.equal(a.href); }); - it('returns undefined if no active app', function() { + it('returns undefined if no active app', function () { const chrome = {}; setup(chrome, {}); expect(chrome.getAppUrl()).to.equal(undefined); diff --git a/src/legacy/ui/public/chrome/api/__tests__/nav.js b/src/legacy/ui/public/chrome/api/__tests__/nav.js index 877da3539828f..accb56dd42aa3 100644 --- a/src/legacy/ui/public/chrome/api/__tests__/nav.js +++ b/src/legacy/ui/public/chrome/api/__tests__/nav.js @@ -29,7 +29,7 @@ const basePath = '/someBasePath'; function init(customInternals = { basePath }) { const chrome = { - addBasePath: path => path, + addBasePath: (path) => path, getBasePath: () => customInternals.basePath || '', }; const internals = { @@ -40,11 +40,11 @@ function init(customInternals = { basePath }) { return { chrome, internals }; } -describe('chrome nav apis', function() { +describe('chrome nav apis', function () { let coreNavLinks; let fakedLinks = []; - const baseUrl = (function() { + const baseUrl = (function () { const a = document.createElement('a'); a.setAttribute('href', '/'); return a.href.slice(0, a.href.length - 1); @@ -60,7 +60,9 @@ describe('chrome nav apis', function() { return link; }); sinon.stub(coreNavLinks, 'getAll').callsFake(() => fakedLinks); - sinon.stub(coreNavLinks, 'get').callsFake(linkId => fakedLinks.find(({ id }) => id === linkId)); + sinon + .stub(coreNavLinks, 'get') + .callsFake((linkId) => fakedLinks.find(({ id }) => id === linkId)); }); afterEach(() => { @@ -69,12 +71,12 @@ describe('chrome nav apis', function() { coreNavLinks.get.restore(); }); - describe('#untrackNavLinksForDeletedSavedObjects', function() { + describe('#untrackNavLinksForDeletedSavedObjects', function () { const appId = 'appId'; const appUrl = `${baseUrl}/app/kibana#test`; const deletedId = 'IAMDELETED'; - it('should clear last url when last url contains link to deleted saved object', function() { + it('should clear last url when last url contains link to deleted saved object', function () { const appUrlStore = new StubBrowserStorage(); fakedLinks = [ { @@ -92,7 +94,7 @@ describe('chrome nav apis', function() { expect(coreNavLinks.update.calledWith(appId, { url: appUrl })).to.be(true); }); - it('should not clear last url when last url does not contains link to deleted saved object', function() { + it('should not clear last url when last url does not contains link to deleted saved object', function () { const lastUrl = `${appUrl}?id=anotherSavedObjectId`; const appUrlStore = new StubBrowserStorage(); fakedLinks = [ @@ -112,8 +114,8 @@ describe('chrome nav apis', function() { }); }); - describe('chrome.trackSubUrlForApp()', function() { - it('injects a manual app url', function() { + describe('chrome.trackSubUrlForApp()', function () { + it('injects a manual app url', function () { const appUrlStore = new StubBrowserStorage(); fakedLinks = [ { diff --git a/src/legacy/ui/public/chrome/api/__tests__/sub_url_route_filter.js b/src/legacy/ui/public/chrome/api/__tests__/sub_url_route_filter.js index 0fc4d9c1cdccd..901fbd1d0edf6 100644 --- a/src/legacy/ui/public/chrome/api/__tests__/sub_url_route_filter.js +++ b/src/legacy/ui/public/chrome/api/__tests__/sub_url_route_filter.js @@ -26,14 +26,14 @@ describe('kbn-chrome subUrlRouteFilter()', () => { describe('no ngRoute', () => { beforeEach(ngMock.module('kibana/private')); beforeEach( - ngMock.inject($injector => { + ngMock.inject(($injector) => { expect($injector.has('$route')).to.be(false); }) ); it( 'always returns true when there is no $route service', - ngMock.inject(Private => { + ngMock.inject((Private) => { const subUrlRouteFilter = Private(SubUrlRouteFilterProvider); expect(subUrlRouteFilter()).to.be(true); }) @@ -42,7 +42,7 @@ describe('kbn-chrome subUrlRouteFilter()', () => { describe('with ngRoute', () => { beforeEach( - ngMock.module('kibana/private', 'ngRoute', $routeProvider => { + ngMock.module('kibana/private', 'ngRoute', ($routeProvider) => { $routeProvider.when('/foo', { redirectTo: '/bar', }); diff --git a/src/legacy/ui/public/chrome/api/__tests__/xsrf.js b/src/legacy/ui/public/chrome/api/__tests__/xsrf.js index 54ecd4ee2ca1c..3197b79f407da 100644 --- a/src/legacy/ui/public/chrome/api/__tests__/xsrf.js +++ b/src/legacy/ui/public/chrome/api/__tests__/xsrf.js @@ -24,15 +24,15 @@ import { initChromeXsrfApi } from '../xsrf'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { version } from '../../../../../../core/server/utils/package_json'; -describe('chrome xsrf apis', function() { +describe('chrome xsrf apis', function () { const sandbox = sinon.createSandbox(); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); - describe('#getXsrfToken()', function() { - it('exposes the token', function() { + describe('#getXsrfToken()', function () { + it('exposes the token', function () { const chrome = {}; initChromeXsrfApi(chrome, { version }); expect(chrome.getXsrfToken()).to.be(version); diff --git a/src/legacy/ui/public/chrome/api/angular.js b/src/legacy/ui/public/chrome/api/angular.js index bad6e5f12f0d7..a9113b2df2ed7 100644 --- a/src/legacy/ui/public/chrome/api/angular.js +++ b/src/legacy/ui/public/chrome/api/angular.js @@ -25,7 +25,7 @@ import { configureAppAngularModule } from 'ui/legacy_compat'; import { npStart } from '../../new_platform/new_platform'; export function initAngularApi(chrome, internals) { - chrome.setupAngular = function() { + chrome.setupAngular = function () { const kibana = uiModules.get('kibana'); configureAppAngularModule(kibana, npStart.core, false); diff --git a/src/legacy/ui/public/chrome/api/apps.js b/src/legacy/ui/public/chrome/api/apps.js index 23eb84fda97a3..c4cbe7be6f1c3 100644 --- a/src/legacy/ui/public/chrome/api/apps.js +++ b/src/legacy/ui/public/chrome/api/apps.js @@ -21,7 +21,7 @@ import { clone, get } from 'lodash'; import { resolve } from 'url'; // eslint-disable-next-line import/no-default-export -export default function(chrome, internals) { +export default function (chrome, internals) { if (get(internals, 'app.navLink.url')) { internals.app.navLink.url = resolve(window.location.href, internals.app.navLink.url); } @@ -48,36 +48,36 @@ export default function(chrome, internals) { * than one app installed. */ - chrome.setShowAppsLink = function(val) { + chrome.setShowAppsLink = function (val) { internals.showAppsLink = !!val; return chrome; }; - chrome.getShowAppsLink = function() { + chrome.getShowAppsLink = function () { return internals.showAppsLink == null ? internals.nav.length > 1 : internals.showAppsLink; }; - chrome.getKibanaVersion = function() { + chrome.getKibanaVersion = function () { return internals.version; }; - chrome.getApp = function() { + chrome.getApp = function () { return clone(internals.app); }; - chrome.getAppTitle = function() { + chrome.getAppTitle = function () { return get(internals, ['app', 'title']); }; - chrome.getAppUrl = function() { + chrome.getAppUrl = function () { return get(internals, ['app', 'navLink', 'url']); }; - chrome.getLastUrlFor = function(appId) { + chrome.getLastUrlFor = function (appId) { return internals.appUrlStore.getItem(`appLastUrl:${appId}`); }; - chrome.setLastUrlFor = function(appId, url) { + chrome.setLastUrlFor = function (appId, url) { internals.appUrlStore.setItem(`appLastUrl:${appId}`, url); }; } diff --git a/src/legacy/ui/public/chrome/api/nav.ts b/src/legacy/ui/public/chrome/api/nav.ts index ae32473e451b7..2b655419d98aa 100644 --- a/src/legacy/ui/public/chrome/api/nav.ts +++ b/src/legacy/ui/public/chrome/api/nav.ts @@ -41,13 +41,13 @@ export function initChromeNavApi(chrome: any, internals: NavInternals) { */ chrome.untrackNavLinksForDeletedSavedObjects = (deletedIds: string[]) => { function urlContainsDeletedId(url: string) { - const includedId = deletedIds.find(deletedId => { + const includedId = deletedIds.find((deletedId) => { return url.includes(deletedId); }); return includedId !== undefined; } - coreNavLinks.getAll().forEach(link => { + coreNavLinks.getAll().forEach((link) => { if (link.linkToLastSubUrl && urlContainsDeletedId(link.url!)) { setLastUrl(link, link.baseUrl); } @@ -72,14 +72,14 @@ export function initChromeNavApi(chrome: any, internals: NavInternals) { } }; - internals.trackPossibleSubUrl = async function(url: string) { + internals.trackPossibleSubUrl = async function (url: string) { const kibanaParsedUrl = absoluteToParsedUrl(url, chrome.getBasePath()); coreNavLinks .getAll() // Filter only legacy links - .filter(link => link.legacy && !link.disableSubUrlTracking) - .forEach(link => { + .filter((link) => link.legacy && !link.disableSubUrlTracking) + .forEach((link) => { const active = url.startsWith(link.subUrlBase!); link = coreNavLinks.update(link.id, { active })!; @@ -146,8 +146,8 @@ export function initChromeNavApi(chrome: any, internals: NavInternals) { // link.active and link.lastUrl properties coreNavLinks .getAll() - .filter(link => link.subUrlBase && !link.disableSubUrlTracking) - .forEach(link => { + .filter((link) => link.subUrlBase && !link.disableSubUrlTracking) + .forEach((link) => { coreNavLinks.update(link.id, { subUrlBase: relativeToAbsolute(chrome.addBasePath(link.subUrlBase)), }); diff --git a/src/legacy/ui/public/chrome/api/saved_object_client.ts b/src/legacy/ui/public/chrome/api/saved_object_client.ts index b42e74e5a5865..76d4e301e3c77 100644 --- a/src/legacy/ui/public/chrome/api/saved_object_client.ts +++ b/src/legacy/ui/public/chrome/api/saved_object_client.ts @@ -23,7 +23,7 @@ import { Chrome } from '..'; const savedObjectsClient = npStart.core.savedObjects.client; export function initSavedObjectClient(chrome: Chrome) { - chrome.getSavedObjectsClient = function() { + chrome.getSavedObjectsClient = function () { return savedObjectsClient; }; } diff --git a/src/legacy/ui/public/chrome/api/template.js b/src/legacy/ui/public/chrome/api/template.js index 8c3100ab87a27..d29a7eba7316f 100644 --- a/src/legacy/ui/public/chrome/api/template.js +++ b/src/legacy/ui/public/chrome/api/template.js @@ -18,7 +18,7 @@ */ // eslint-disable-next-line import/no-default-export -export default function(chrome, internals) { +export default function (chrome, internals) { /** * ui/chrome Template API * @@ -48,7 +48,7 @@ export default function(chrome, internals) { * @param {string} template * @return {chrome} */ - chrome.setRootTemplate = function(template) { + chrome.setRootTemplate = function (template) { internals.rootTemplate = template; return chrome; }; @@ -58,7 +58,7 @@ export default function(chrome, internals) { * @param {Function} controller - the controller initializer function * @return {chrome} */ - chrome.setRootController = function(as, controllerName) { + chrome.setRootController = function (as, controllerName) { if (controllerName === undefined) { controllerName = as; as = null; diff --git a/src/legacy/ui/public/chrome/api/ui_settings.js b/src/legacy/ui/public/chrome/api/ui_settings.js index bd5c7a39b17ed..dbdd6a9c12653 100644 --- a/src/legacy/ui/public/chrome/api/ui_settings.js +++ b/src/legacy/ui/public/chrome/api/ui_settings.js @@ -22,7 +22,7 @@ import { npSetup } from 'ui/new_platform'; const newPlatformUiSettingsClient = npSetup.core.uiSettings; export function initUiSettingsApi(chrome) { - chrome.getUiSettingsClient = function() { + chrome.getUiSettingsClient = function () { return newPlatformUiSettingsClient; }; } diff --git a/src/legacy/ui/public/chrome/api/xsrf.js b/src/legacy/ui/public/chrome/api/xsrf.js index 56a69696a3464..5086903604667 100644 --- a/src/legacy/ui/public/chrome/api/xsrf.js +++ b/src/legacy/ui/public/chrome/api/xsrf.js @@ -18,7 +18,7 @@ */ export function initChromeXsrfApi(chrome, internals) { - chrome.getXsrfToken = function() { + chrome.getXsrfToken = function () { return internals.version; }; } diff --git a/src/legacy/ui/public/chrome/chrome.js b/src/legacy/ui/public/chrome/chrome.js index 7a75ad906a870..0640017f7806a 100644 --- a/src/legacy/ui/public/chrome/chrome.js +++ b/src/legacy/ui/public/chrome/chrome.js @@ -79,8 +79,8 @@ initChromeThemeApi(chrome); npStart.core.chrome.setAppTitle(chrome.getAppTitle()); -const waitForBootstrap = new Promise(resolve => { - chrome.bootstrap = function(targetDomElement) { +const waitForBootstrap = new Promise((resolve) => { + chrome.bootstrap = function (targetDomElement) { // import chrome nav controls and hacks now so that they are executed after // everything else, can safely import the chrome, and interact with services // and such setup by all other modules @@ -114,7 +114,7 @@ const waitForBootstrap = new Promise(resolve => { * tests. Look into 'src/test_utils/public/stub_get_active_injector' for more information. */ chrome.dangerouslyGetActiveInjector = () => { - return waitForBootstrap.then(targetDomElement => { + return waitForBootstrap.then((targetDomElement) => { const $injector = angular.element(targetDomElement).injector(); if (!$injector) { return Promise.reject('targetDomElement had no angular context after bootstrapping'); diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.js b/src/legacy/ui/public/chrome/directives/kbn_chrome.js index 45da4ab6b7472..5ba34e553201e 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js @@ -60,13 +60,13 @@ export function kbnChromeProvider(chrome, internals) { // Continue to support legacy nav controls not registered with the NP. const navControls = Private(chromeHeaderNavControlsRegistry); - (navControls.bySide[NavControlSide.Left] || []).forEach(navControl => + (navControls.bySide[NavControlSide.Left] || []).forEach((navControl) => npStart.core.chrome.navControls.registerLeft({ order: navControl.order, mount: navControl.render, }) ); - (navControls.bySide[NavControlSide.Right] || []).forEach(navControl => + (navControls.bySide[NavControlSide.Right] || []).forEach((navControl) => npStart.core.chrome.navControls.registerRight({ order: navControl.order, mount: navControl.render, diff --git a/src/legacy/ui/public/config/__tests__/config.js b/src/legacy/ui/public/config/__tests__/config.js index 843fa864453fd..90dbdaf264a29 100644 --- a/src/legacy/ui/public/config/__tests__/config.js +++ b/src/legacy/ui/public/config/__tests__/config.js @@ -31,7 +31,7 @@ describe('Config service', () => { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject($injector => { + ngMock.inject(($injector) => { config = $injector.get('config'); uiSettings = chrome.getUiSettingsClient(); $q = $injector.get('$q'); @@ -191,8 +191,8 @@ describe('Config service', () => { it('synchronously emits events when changes are outside a digest cycle', async () => { const stub = sinon.stub(); - await new Promise(resolve => { - setTimeout(function() { + await new Promise((resolve) => { + setTimeout(function () { const off = $rootScope.$on('change:config.foobar', stub); config.set('foobar', 'baz'); // we unlisten to make sure that stub is not called before our assertions below diff --git a/src/legacy/ui/public/config/config.js b/src/legacy/ui/public/config/config.js index 80a9d39221b2c..a8f24c126caff 100644 --- a/src/legacy/ui/public/config/config.js +++ b/src/legacy/ui/public/config/config.js @@ -31,7 +31,7 @@ const module = uiModules.get('kibana/config'); * to expose the exact same API as the config service that has existed since forever. * @name config */ -module.service(`config`, function($rootScope, Promise) { +module.service(`config`, function ($rootScope, Promise) { const uiSettings = chrome.getUiSettingsClient(); // direct bind sync methods @@ -43,7 +43,7 @@ module.service(`config`, function($rootScope, Promise) { this.isOverridden = (...args) => uiSettings.isOverridden(...args); // modify remove() to use angular Promises - this.remove = key => Promise.resolve(uiSettings.remove(key)); + this.remove = (key) => Promise.resolve(uiSettings.remove(key)); // modify set() to use angular Promises and angular.toJson() this.set = (key, value) => @@ -66,7 +66,7 @@ module.service(`config`, function($rootScope, Promise) { ); $rootScope.$on('$destroy', () => subscription.unsubscribe()); - this.watchAll = function(handler, scope = $rootScope) { + this.watchAll = function (handler, scope = $rootScope) { // call handler immediately to initialize handler(null, null, null, this); @@ -75,7 +75,7 @@ module.service(`config`, function($rootScope, Promise) { }); }; - this.watch = function(key, handler, scope = $rootScope) { + this.watch = function (key, handler, scope = $rootScope) { if (!this.isDeclared(key)) { throw new Error(`Unexpected \`config.watch("${key}", fn)\` call on unrecognized configuration setting "${key}". Setting an initial value via \`config.set("${key}", value)\` before binding @@ -100,8 +100,8 @@ any custom setting configuration watchers for "${key}" may fix this issue.`); * be stored. Defaults to the config key * @return {function} - an unbind function */ - this.bindToScope = function(scope, key, property = key) { - const onUpdate = newVal => { + this.bindToScope = function (scope, key, property = key) { + const onUpdate = (newVal) => { scope[property] = newVal; }; diff --git a/src/legacy/ui/public/directives/__tests__/input_focus.js b/src/legacy/ui/public/directives/__tests__/input_focus.js index 840803c4d28a0..45b1821cbfd21 100644 --- a/src/legacy/ui/public/directives/__tests__/input_focus.js +++ b/src/legacy/ui/public/directives/__tests__/input_focus.js @@ -22,7 +22,7 @@ import ngMock from 'ng_mock'; import $ from 'jquery'; import '../input_focus'; -describe('Input focus directive', function() { +describe('Input focus directive', function () { let $compile; let $rootScope; let $timeout; @@ -34,7 +34,7 @@ describe('Input focus directive', function() { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function(_$compile_, _$rootScope_, _$timeout_) { + ngMock.inject(function (_$compile_, _$rootScope_, _$timeout_) { $compile = _$compile_; $rootScope = _$rootScope_; $timeout = _$timeout_; @@ -44,7 +44,7 @@ describe('Input focus directive', function() { }) ); - afterEach(function() { + afterEach(function () { $el.remove(); $el = null; }); @@ -61,25 +61,25 @@ describe('Input focus directive', function() { } } - it('should focus the input', function() { + it('should focus the input', function () { renderEl(''); expect(selectedEl).to.equal(element[0]); expect(selectedText.length).to.equal(0); }); - it('should select the text in the input', function() { + it('should select the text in the input', function () { renderEl(''); expect(selectedEl).to.equal(element[0]); expect(selectedText.length).to.equal(inputValue.length); expect(selectedText).to.equal(inputValue); }); - it('should not focus the input if disable-input-focus is set to true on the same element', function() { + it('should not focus the input if disable-input-focus is set to true on the same element', function () { renderEl(''); expect(selectedEl).not.to.be(element[0]); }); - it('should still focus the input if disable-input-focus is falsy', function() { + it('should still focus the input if disable-input-focus is falsy', function () { renderEl(''); expect(selectedEl).to.be(element[0]); }); diff --git a/src/legacy/ui/public/directives/bind/__tests__/bind.js b/src/legacy/ui/public/directives/bind/__tests__/bind.js index 1d2b46b2e0523..658a726e8c4cf 100644 --- a/src/legacy/ui/public/directives/bind/__tests__/bind.js +++ b/src/legacy/ui/public/directives/bind/__tests__/bind.js @@ -19,19 +19,19 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -describe('$scope.$bind', function() { +describe('$scope.$bind', function () { let $rootScope; let $scope; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $rootScope = $injector.get('$rootScope'); $scope = $rootScope.$new(); }) ); - it('exposes $bind on all scopes', function() { + it('exposes $bind on all scopes', function () { expect($rootScope.$bind).to.be.a('function'); expect($scope).to.have.property('$bind', $rootScope.$bind); @@ -39,7 +39,7 @@ describe('$scope.$bind', function() { expect($isoScope).to.have.property('$bind', $rootScope.$bind); }); - it("sets up binding from a parent scope to it's child", function() { + it("sets up binding from a parent scope to it's child", function () { $rootScope.val = 'foo'; $scope.$bind('localVal', 'val'); expect($scope.localVal).to.be('foo'); @@ -51,7 +51,7 @@ describe('$scope.$bind', function() { expect($scope.localVal).to.be('bar'); }); - it('sets up a binding from the child to the parent scope', function() { + it('sets up a binding from the child to the parent scope', function () { $rootScope.val = 'foo'; $scope.$bind('localVal', 'val'); expect($scope.localVal).to.be('foo'); @@ -63,7 +63,7 @@ describe('$scope.$bind', function() { expect($rootScope.val).to.be('bar'); }); - it('pulls from the scopes $parent by default', function() { + it('pulls from the scopes $parent by default', function () { const $parent = $rootScope.$new(); const $self = $parent.$new(); @@ -74,7 +74,7 @@ describe('$scope.$bind', function() { expect($self.localVal).to.be('foo'); }); - it('accepts an alternate scope to read from', function() { + it('accepts an alternate scope to read from', function () { const $parent = $rootScope.$new(); const $self = $parent.$new(); diff --git a/src/legacy/ui/public/directives/bind/bind.js b/src/legacy/ui/public/directives/bind/bind.js index 490816501564d..a9210cace5cea 100644 --- a/src/legacy/ui/public/directives/bind/bind.js +++ b/src/legacy/ui/public/directives/bind/bind.js @@ -20,7 +20,7 @@ import angular from 'angular'; import { uiModules } from '../../modules'; -uiModules.get('kibana').config(function($provide) { +uiModules.get('kibana').config(function ($provide) { function strictEquality(a, b) { // are the values equal? or, are they both NaN? return a === b || (a !== a && b !== b); @@ -37,7 +37,7 @@ uiModules.get('kibana').config(function($provide) { ); } - $provide.decorator('$rootScope', function($delegate, $parse) { + $provide.decorator('$rootScope', function ($delegate, $parse) { /** * Two-way bind a value from scope to another property on scope. This * allow values on scope that work like they do in an isolate scope, but @@ -48,7 +48,7 @@ uiModules.get('kibana').config(function($provide) { * @param {Scope} $sourceScope - the scope to read "from" expression from * @return {undefined} */ - $delegate.constructor.prototype.$bind = function(to, from, $sourceScope) { + $delegate.constructor.prototype.$bind = function (to, from, $sourceScope) { const $source = $sourceScope || this.$parent; const $target = this; @@ -58,16 +58,16 @@ uiModules.get('kibana').config(function($provide) { const $from = $parse(from); // bind scopes to expressions - const getTarget = function() { + const getTarget = function () { return $to($target); }; - const setTarget = function(v) { + const setTarget = function (v) { return $to.assign($target, v); }; - const getSource = function() { + const getSource = function () { return $from($source); }; - const setSource = function(v) { + const setSource = function (v) { return $from.assignOrFail($source, v); }; @@ -80,7 +80,7 @@ uiModules.get('kibana').config(function($provide) { $from.assignOrFail = $from.assign || - function() { + function () { // revert the change and throw an error, child writes aren't supported $to($target, (lastSourceVal = $from($source))); errorNotAssignable(from, to); @@ -94,7 +94,7 @@ uiModules.get('kibana').config(function($provide) { setTarget(lastSourceVal); $target.$watch( - function() { + function () { const sourceVal = getSource(); const targetVal = getTarget(); diff --git a/src/legacy/ui/public/directives/input_focus.js b/src/legacy/ui/public/directives/input_focus.js index d9f744cb77de1..f047ff2547ba2 100644 --- a/src/legacy/ui/public/directives/input_focus.js +++ b/src/legacy/ui/public/directives/input_focus.js @@ -20,13 +20,13 @@ import { uiModules } from '../modules'; const module = uiModules.get('kibana'); -module.directive('inputFocus', function($parse, $timeout) { +module.directive('inputFocus', function ($parse, $timeout) { return { restrict: 'A', - link: function($scope, $elem, attrs) { + link: function ($scope, $elem, attrs) { const isDisabled = attrs.disableInputFocus && $parse(attrs.disableInputFocus)($scope); if (!isDisabled) { - $timeout(function() { + $timeout(function () { $elem.focus(); if (attrs.inputFocus === 'select') $elem.select(); }); diff --git a/src/legacy/ui/public/directives/kbn_href.js b/src/legacy/ui/public/directives/kbn_href.js index d7a5f886fd4e0..5c71396e6c4de 100644 --- a/src/legacy/ui/public/directives/kbn_href.js +++ b/src/legacy/ui/public/directives/kbn_href.js @@ -23,11 +23,11 @@ import { words, kebabCase } from 'lodash'; export function kbnUrlDirective(name) { const attr = kebabCase(words(name).slice(1)); - uiModules.get('kibana').directive(name, function(chrome) { + uiModules.get('kibana').directive(name, function (chrome) { return { restrict: 'A', - link: function($scope, $el, $attr) { - $attr.$observe(name, function(val) { + link: function ($scope, $el, $attr) { + $attr.$observe(name, function (val) { $attr.$set(attr, chrome.addBasePath(val)); }); }, diff --git a/src/legacy/ui/public/directives/listen/__tests__/listen.js b/src/legacy/ui/public/directives/listen/__tests__/listen.js index 7aa1a2d0e430c..9a1d482956154 100644 --- a/src/legacy/ui/public/directives/listen/__tests__/listen.js +++ b/src/legacy/ui/public/directives/listen/__tests__/listen.js @@ -23,24 +23,24 @@ import ngMock from 'ng_mock'; import '..'; import { EventsProvider } from '../../../events'; -describe('listen component', function() { +describe('listen component', function () { let $rootScope; let Events; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector, Private) { + ngMock.inject(function ($injector, Private) { $rootScope = $injector.get('$rootScope'); Events = Private(EventsProvider); }) ); - it('exposes the $listen method on all scopes', function() { + it('exposes the $listen method on all scopes', function () { expect($rootScope.$listen).to.be.a('function'); expect($rootScope.$new().$listen).to.be.a('function'); }); - it('binds to an event emitter', function() { + it('binds to an event emitter', function () { const emitter = new Events(); const $scope = $rootScope.$new(); @@ -51,7 +51,7 @@ describe('listen component', function() { expect(emitter._listeners.hello[0].handler).to.be(handler); }); - it('binds to $scope, waiting for the destroy event', function() { + it('binds to $scope, waiting for the destroy event', function () { const emitter = new Events(); const $scope = $rootScope.$new(); @@ -69,7 +69,7 @@ describe('listen component', function() { expect(call.args[1]).to.be.a('function'); }); - it('unbinds the event handler when $destroy is triggered', function() { + it('unbinds the event handler when $destroy is triggered', function () { const emitter = new Events(); const $scope = $rootScope.$new(); diff --git a/src/legacy/ui/public/directives/render_directive/__tests__/render_directive.js b/src/legacy/ui/public/directives/render_directive/__tests__/render_directive.js index 313f9e12b017a..a604eca52affe 100644 --- a/src/legacy/ui/public/directives/render_directive/__tests__/render_directive.js +++ b/src/legacy/ui/public/directives/render_directive/__tests__/render_directive.js @@ -27,10 +27,10 @@ let init; let $rootScope; let $compile; -describe('render_directive', function() { +describe('render_directive', function () { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $rootScope = $injector.get('$rootScope'); $compile = $injector.get('$compile'); init = function init(markup = '', definition = {}) { @@ -55,14 +55,14 @@ describe('render_directive', function() { }) ); - describe('directive requirements', function() { - it('should throw if not given a definition', function() { + describe('directive requirements', function () { + it('should throw if not given a definition', function () { expect(() => init('', null)).to.throwException(/must have a definition/); }); }); - describe('rendering with definition', function() { - it('should call link method', function() { + describe('rendering with definition', function () { + it('should call link method', function () { const markup = '

hello world

'; const definition = { link: sinon.stub(), @@ -73,7 +73,7 @@ describe('render_directive', function() { sinon.assert.callCount(definition.link, 1); }); - it('should call controller method', function() { + it('should call controller method', function () { const markup = '

hello world

'; const definition = { controller: sinon.stub(), @@ -85,8 +85,8 @@ describe('render_directive', function() { }); }); - describe('definition scope binding', function() { - it('should accept two-way, attribute, and expression binding directives', function() { + describe('definition scope binding', function () { + it('should accept two-way, attribute, and expression binding directives', function () { const $el = angular.element(` { if (!bindingRE.test(binding)) { throw new Error(`Invalid scope binding "${binding}". Expected it to match ${bindingRE}`); @@ -44,7 +44,7 @@ export function ApplyScopeBindingsProvider($parse) { case '&': if (attr) { const getter = $parse(attr); - $scope[local] = function() { + $scope[local] = function () { return getter($scope.$parent); }; } else { @@ -53,7 +53,7 @@ export function ApplyScopeBindingsProvider($parse) { break; case '@': $scope[local] = attr; - $attrs.$observe(attribute, v => ($scope[local] = v)); + $attrs.$observe(attribute, (v) => ($scope[local] = v)); break; } }); diff --git a/src/legacy/ui/public/directives/render_directive/render_directive.js b/src/legacy/ui/public/directives/render_directive/render_directive.js index 1d28377ef9126..a5232f39b82c3 100644 --- a/src/legacy/ui/public/directives/render_directive/render_directive.js +++ b/src/legacy/ui/public/directives/render_directive/render_directive.js @@ -43,7 +43,7 @@ import { ApplyScopeBindingsProvider } from './apply_scope_bindings'; * @param [Object|Function] definition.link - either a post link function or an object with pre and/or * post link functions. */ -uiModules.get('kibana').directive('renderDirective', function(Private) { +uiModules.get('kibana').directive('renderDirective', function (Private) { const applyScopeBindings = Private(ApplyScopeBindingsProvider); return { @@ -51,10 +51,10 @@ uiModules.get('kibana').directive('renderDirective', function(Private) { scope: { definition: '=', }, - template: function($el) { + template: function ($el) { return $el.html(); }, - controller: function($scope, $element, $attrs, $transclude, $injector) { + controller: function ($scope, $element, $attrs, $transclude, $injector) { if (!$scope.definition) throw new Error('render-directive must have a definition attribute'); const { controller, controllerAs, scope } = $scope.definition; diff --git a/src/legacy/ui/public/directives/storage/index.js b/src/legacy/ui/public/directives/storage/index.js index 80e4c3b645108..7c195ecc85d2f 100644 --- a/src/legacy/ui/public/directives/storage/index.js +++ b/src/legacy/ui/public/directives/storage/index.js @@ -20,8 +20,8 @@ import { uiModules } from '../../modules'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -const createService = function(type) { - return function($window) { +const createService = function (type) { + return function ($window) { return new Storage($window[type]); }; }; diff --git a/src/legacy/ui/public/directives/watch_multi/__tests__/watch_multi.js b/src/legacy/ui/public/directives/watch_multi/__tests__/watch_multi.js index 7aab953c55ac7..0de41a5ae57cb 100644 --- a/src/legacy/ui/public/directives/watch_multi/__tests__/watch_multi.js +++ b/src/legacy/ui/public/directives/watch_multi/__tests__/watch_multi.js @@ -22,20 +22,20 @@ import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import sinon from 'sinon'; -describe('$scope.$watchMulti', function() { +describe('$scope.$watchMulti', function () { let $rootScope; let $scope; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $rootScope = $injector.get('$rootScope'); $scope = $rootScope.$new(); }) ); - describe('basic functionality', function() { - it('exposes $watchMulti on all scopes', function() { + describe('basic functionality', function () { + it('exposes $watchMulti on all scopes', function () { expect($rootScope.$watchMulti).to.be.a('function'); expect($scope).to.have.property('$watchMulti', $rootScope.$watchMulti); @@ -43,11 +43,11 @@ describe('$scope.$watchMulti', function() { expect($isoScope).to.have.property('$watchMulti', $rootScope.$watchMulti); }); - it('returns a working unwatch function', function() { + it('returns a working unwatch function', function () { $scope.a = 0; $scope.b = 0; let triggers = 0; - const unwatch = $scope.$watchMulti(['a', 'b'], function() { + const unwatch = $scope.$watchMulti(['a', 'b'], function () { triggers++; }); @@ -72,8 +72,8 @@ describe('$scope.$watchMulti', function() { }); }); - describe('simple scope watchers', function() { - it('only triggers a single watch on initialization', function() { + describe('simple scope watchers', function () { + it('only triggers a single watch on initialization', function () { const stub = sinon.stub(); $scope.$watchMulti(['one', 'two', 'three'], stub); @@ -82,8 +82,8 @@ describe('$scope.$watchMulti', function() { expect(stub.callCount).to.be(1); }); - it('only triggers a single watch when multiple values change', function() { - const stub = sinon.spy(function() {}); + it('only triggers a single watch when multiple values change', function () { + const stub = sinon.spy(function () {}); $scope.$watchMulti(['one', 'two', 'three'], stub); @@ -98,8 +98,8 @@ describe('$scope.$watchMulti', function() { expect(stub.callCount).to.be(2); }); - it('passes an array of the current and previous values, in order', function() { - const stub = sinon.spy(function() {}); + it('passes an array of the current and previous values, in order', function () { + const stub = sinon.spy(function () {}); $scope.one = 'a'; $scope.two = 'b'; @@ -123,17 +123,17 @@ describe('$scope.$watchMulti', function() { ]); }); - it('always has an up to date value', function() { + it('always has an up to date value', function () { let count = 0; $scope.vals = [1, 0]; - $scope.$watchMulti(['vals[0]', 'vals[1]'], function(cur) { + $scope.$watchMulti(['vals[0]', 'vals[1]'], function (cur) { expect(cur).to.eql($scope.vals); count++; }); const $child = $scope.$new(); - $child.$watch('vals[0]', function(cur) { + $child.$watch('vals[0]', function (cur) { $child.vals[1] = cur; }); @@ -142,17 +142,17 @@ describe('$scope.$watchMulti', function() { }); }); - describe('complex watch expressions', function() { + describe('complex watch expressions', function () { let stateWatchers; let firstValue; let secondValue; - beforeEach(function() { - const firstGetter = function() { + beforeEach(function () { + const firstGetter = function () { return firstValue; }; - const secondGetter = function() { + const secondGetter = function () { return secondValue; }; @@ -168,7 +168,7 @@ describe('$scope.$watchMulti', function() { ]; }); - it('should trigger the watcher on initialization', function() { + it('should trigger the watcher on initialization', function () { const stub = sinon.stub(); firstValue = 'first'; secondValue = 'second'; @@ -183,8 +183,8 @@ describe('$scope.$watchMulti', function() { }); }); - describe('nested watchers', function() { - it('should trigger the handler at least once', function() { + describe('nested watchers', function () { + it('should trigger the handler at least once', function () { const $scope = $rootScope.$new(); $scope.$$watchers = [ { @@ -205,7 +205,7 @@ describe('$scope.$watchMulti', function() { const second = sinon.stub(); function registerWatchers() { - $scope.$watchMulti([first, second], function() { + $scope.$watchMulti([first, second], function () { expect(first.callCount).to.be.greaterThan(0); expect(second.callCount).to.be.greaterThan(0); }); diff --git a/src/legacy/ui/public/doc_title/__tests__/doc_title.js b/src/legacy/ui/public/doc_title/__tests__/doc_title.js index 8e4af5aa11dc3..fa8b83f755957 100644 --- a/src/legacy/ui/public/doc_title/__tests__/doc_title.js +++ b/src/legacy/ui/public/doc_title/__tests__/doc_title.js @@ -23,38 +23,38 @@ import ngMock from 'ng_mock'; import { docTitle } from '../doc_title'; import { npStart } from '../../new_platform'; -describe('docTitle Service', function() { +describe('docTitle Service', function () { let initialDocTitle; const MAIN_TITLE = 'Kibana 4'; let $rootScope; - beforeEach(function() { + beforeEach(function () { initialDocTitle = document.title; document.title = MAIN_TITLE; npStart.core.chrome.docTitle.__legacy.setBaseTitle(MAIN_TITLE); }); - afterEach(function() { + afterEach(function () { document.title = initialDocTitle; npStart.core.chrome.docTitle.__legacy.setBaseTitle(initialDocTitle); }); beforeEach( - ngMock.module('kibana', function($provide) { + ngMock.module('kibana', function ($provide) { $provide.decorator('$rootScope', decorateWithSpy('$on')); }) ); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $rootScope = $injector.get('$rootScope'); }) ); - describe('setup', function() { - it('resets the title when a route change begins', function() { + describe('setup', function () { + it('resets the title when a route change begins', function () { const spy = $rootScope.$on; - const found = spy.args.some(function(args) { + const found = spy.args.some(function (args) { return args[0] === '$routeChangeStart' && args[1] === docTitle.reset; }); @@ -64,8 +64,8 @@ describe('docTitle Service', function() { }); }); - describe('#reset', function() { - it('clears the internal state', function() { + describe('#reset', function () { + it('clears the internal state', function () { docTitle.change('some title'); expect(document.title).to.be('some title - ' + MAIN_TITLE); @@ -74,8 +74,8 @@ describe('docTitle Service', function() { }); }); - describe('#change', function() { - it('writes the first param to as the first part of the doc name', function() { + describe('#change', function () { + it('writes the first param to as the first part of the doc name', function () { expect(document.title).to.be(MAIN_TITLE); docTitle.change('some secondary title'); expect(document.title).to.be('some secondary title - ' + MAIN_TITLE); @@ -83,7 +83,7 @@ describe('docTitle Service', function() { }); function decorateWithSpy(prop) { - return function($delegate) { + return function ($delegate) { sinon.spy($delegate, prop); return $delegate; }; diff --git a/src/legacy/ui/public/doc_title/doc_title.js b/src/legacy/ui/public/doc_title/doc_title.js index 5d0d63380f152..096e49e7a6de8 100644 --- a/src/legacy/ui/public/doc_title/doc_title.js +++ b/src/legacy/ui/public/doc_title/doc_title.js @@ -36,7 +36,7 @@ export const docTitle = { reset, }; -uiModules.get('kibana').run(function($rootScope) { +uiModules.get('kibana').run(function ($rootScope) { // always bind to the route events $rootScope.$on('$routeChangeStart', docTitle.reset); }); diff --git a/src/legacy/ui/public/documentation_links/__tests__/documentation_links.js b/src/legacy/ui/public/documentation_links/__tests__/documentation_links.js index 0e38ec998d62e..6c9cdd12422cf 100644 --- a/src/legacy/ui/public/documentation_links/__tests__/documentation_links.js +++ b/src/legacy/ui/public/documentation_links/__tests__/documentation_links.js @@ -23,8 +23,8 @@ import { metadata } from '../../metadata'; const urlVersion = metadata.branch; -describe('documentation link service', function() { - it("should inject Kibana's major.minor version into doc links", function() { +describe('documentation link service', function () { + it("should inject Kibana's major.minor version into doc links", function () { expect(documentationLinks.filebeat.configuration).to.contain(urlVersion); }); }); diff --git a/src/legacy/ui/public/dom_location.js b/src/legacy/ui/public/dom_location.js index 18411f3f66ed0..baf03ba4c4b1c 100644 --- a/src/legacy/ui/public/dom_location.js +++ b/src/legacy/ui/public/dom_location.js @@ -19,7 +19,7 @@ export function DomLocationProvider($window) { return { - reload: function(forceFetch) { + reload: function (forceFetch) { $window.location.reload(forceFetch); }, diff --git a/src/legacy/ui/public/events.js b/src/legacy/ui/public/events.js index 00c92038e7c9f..1dc8a71afb193 100644 --- a/src/legacy/ui/public/events.js +++ b/src/legacy/ui/public/events.js @@ -45,7 +45,7 @@ export function EventsProvider(Promise) { * @param {function} handler - The function to call when the event is triggered * @return {Events} - this, for chaining */ - Events.prototype.on = function(name, handler) { + Events.prototype.on = function (name, handler) { if (!Array.isArray(this._listeners[name])) { this._listeners[name] = []; } @@ -57,11 +57,11 @@ export function EventsProvider(Promise) { (function rebuildDefer() { listener.defer = createDefer(Promise); - listener.resolved = listener.defer.promise.then(function(args) { + listener.resolved = listener.defer.promise.then(function (args) { rebuildDefer(); // we ignore the completion of handlers, just watch for unhandled errors - Promise.resolve(handler.apply(handler, args)).catch(error => fatalError(error, location)); + Promise.resolve(handler.apply(handler, args)).catch((error) => fatalError(error, location)); // indicate to bluebird not to worry about this promise being a "runaway" return null; @@ -77,7 +77,7 @@ export function EventsProvider(Promise) { * @param {function} [handler] - The handler to remove * @return {Events} - this, for chaining */ - Events.prototype.off = function(name, handler) { + Events.prototype.off = function (name, handler) { if (!name && !handler) { this._listeners = {}; return this.removeAllListeners(); @@ -90,7 +90,7 @@ export function EventsProvider(Promise) { if (!handler) { delete this._listeners[name]; } else { - this._listeners[name] = _.filter(this._listeners[name], function(listener) { + this._listeners[name] = _.filter(this._listeners[name], function (listener) { return handler !== listener.handler; }); } @@ -105,7 +105,7 @@ export function EventsProvider(Promise) { * @param {any} [value] - The value that will be passed to all event handlers. * @returns {Promise} */ - Events.prototype.emit = function(name) { + Events.prototype.emit = function (name) { const self = this; const args = _.rest(arguments); @@ -113,8 +113,8 @@ export function EventsProvider(Promise) { return self._emitChain; } - return Promise.map(self._listeners[name], function(listener) { - return (self._emitChain = self._emitChain.then(function() { + return Promise.map(self._listeners[name], function (listener) { + return (self._emitChain = self._emitChain.then(function () { // Double check that off wasn't called after an emit, but before this is fired. if (!self._listeners[name] || self._listeners[name].indexOf(listener) < 0) return; @@ -130,7 +130,7 @@ export function EventsProvider(Promise) { * @param {string} name * @return {array[function]} */ - Events.prototype.listeners = function(name) { + Events.prototype.listeners = function (name) { return _.pluck(this._listeners[name], 'handler'); }; diff --git a/src/legacy/ui/public/i18n/index.tsx b/src/legacy/ui/public/i18n/index.tsx index 6f1120dce0c7c..290e82a1334b9 100644 --- a/src/legacy/ui/public/i18n/index.tsx +++ b/src/legacy/ui/public/i18n/index.tsx @@ -29,7 +29,7 @@ import { npStart } from 'ui/new_platform'; export const I18nContext = npStart.core.i18n.Context; export function wrapInI18nContext

(ComponentToWrap: React.ComponentType

) { - const ContextWrapper: React.FC

= props => { + const ContextWrapper: React.FC

= (props) => { return ( diff --git a/src/legacy/ui/public/indexed_array/__tests__/indexed_array.js b/src/legacy/ui/public/indexed_array/__tests__/indexed_array.js index bbc72d599651a..a8abbba9df433 100644 --- a/src/legacy/ui/public/indexed_array/__tests__/indexed_array.js +++ b/src/legacy/ui/public/indexed_array/__tests__/indexed_array.js @@ -37,42 +37,42 @@ users.inIdOrder = _.sortBy(users, 'id'); // then things started becoming unruly... so IndexedArray! -describe('IndexedArray', function() { - describe('Basics', function() { +describe('IndexedArray', function () { + describe('Basics', function () { let reg; - beforeEach(function() { + beforeEach(function () { reg = new IndexedArray(); }); - it('Extends Array', function() { + it('Extends Array', function () { expect(reg).to.be.a(Array); }); - it('fails basic lodash check', function() { + it('fails basic lodash check', function () { expect(Array.isArray(reg)).to.be(false); }); - it('clones to an object', function() { + it('clones to an object', function () { expect(_.isPlainObject(_.clone(reg))).to.be(true); expect(Array.isArray(_.clone(reg))).to.be(false); }); }); - describe('Indexing', function() { - it('provides the initial set', function() { + describe('Indexing', function () { + it('provides the initial set', function () { const reg = new IndexedArray({ initialSet: [1, 2, 3], }); expect(reg).to.have.length(3); - reg.forEach(function(v, i) { + reg.forEach(function (v, i) { expect(v).to.eql(i + 1); }); }); - it('indexes the initial set', function() { + it('indexes the initial set', function () { const reg = new IndexedArray({ index: ['username'], initialSet: users, @@ -82,7 +82,7 @@ describe('IndexedArray', function() { expect(reg.byUsername).to.eql(users.byUsername); }); - it('updates indices after values are added', function() { + it('updates indices after values are added', function () { // split up the user list, and add it in chunks const firstUser = users.slice(0, 1).pop(); const otherUsers = users.slice(1); @@ -106,7 +106,7 @@ describe('IndexedArray', function() { expect(reg.inIdOrder).to.eql(users.inIdOrder); }); - it('updates indices after values are removed', function() { + it('updates indices after values are removed', function () { // start off with all const reg = new IndexedArray({ group: ['group'], @@ -123,7 +123,7 @@ describe('IndexedArray', function() { const sumOfGroups = _.reduce( reg.byGroup, - function(note, group) { + function (note, group) { return note + group.length; }, 0 @@ -131,7 +131,7 @@ describe('IndexedArray', function() { expect(sumOfGroups).to.eql(expectedCount); }); - it('removes items based on a predicate', function() { + it('removes items based on a predicate', function () { const reg = new IndexedArray({ group: ['group'], order: ['id'], @@ -145,12 +145,12 @@ describe('IndexedArray', function() { expect(reg[0].name).to.be('Anon'); }); - it('updates indices after values are re-ordered', function() { + it('updates indices after values are re-ordered', function () { const rawUsers = users.slice(0); // collect and shuffle the ids available let ids = []; - _.times(rawUsers.length, function(i) { + _.times(rawUsers.length, function (i) { ids.push(i); }); ids = _.shuffle(ids); @@ -160,7 +160,7 @@ describe('IndexedArray', function() { // from here const fromI = ids.shift(); // do the move - const move = function(arr) { + const move = function (arr) { arr.splice(toI, 0, arr.splice(fromI, 1)[0]); }; @@ -178,8 +178,8 @@ describe('IndexedArray', function() { }); }); - describe('Ordering', function() { - it('ordering is case insensitive', function() { + describe('Ordering', function () { + it('ordering is case insensitive', function () { const reg = new IndexedArray({ index: ['title'], order: ['title'], @@ -191,7 +191,7 @@ describe('IndexedArray', function() { expect(ordered[1].title).to.be('APM'); }); - it('ordering handles numbers', function() { + it('ordering handles numbers', function () { const reg = new IndexedArray({ index: ['id'], order: ['id'], diff --git a/src/legacy/ui/public/indexed_array/__tests__/inflector.js b/src/legacy/ui/public/indexed_array/__tests__/inflector.js index 51e4b94b416eb..49ac79094e501 100644 --- a/src/legacy/ui/public/indexed_array/__tests__/inflector.js +++ b/src/legacy/ui/public/indexed_array/__tests__/inflector.js @@ -20,14 +20,14 @@ import { inflector } from '../inflector'; import expect from '@kbn/expect'; -describe('IndexedArray Inflector', function() { - it('returns a function', function() { +describe('IndexedArray Inflector', function () { + it('returns a function', function () { const getter = inflector(); expect(getter).to.be.a('function'); }); - describe('fn', function() { - it('prepends a prefix', function() { + describe('fn', function () { + it('prepends a prefix', function () { const inflect = inflector('my'); expect(inflect('Family')).to.be('myFamily'); @@ -35,7 +35,7 @@ describe('IndexedArray Inflector', function() { expect(inflect('fAmIlY')).to.be('myFAmIlY'); }); - it('adds both a prefix and suffix', function() { + it('adds both a prefix and suffix', function () { const inflect = inflector('foo', 'Bar'); expect(inflect('box')).to.be('fooBoxBar'); @@ -43,19 +43,19 @@ describe('IndexedArray Inflector', function() { expect(inflect('BaZzY')).to.be('fooBaZzYBar'); }); - it('ignores prefix if it is already at the end of the inflected string', function() { + it('ignores prefix if it is already at the end of the inflected string', function () { const inflect = inflector('foo', 'Bar'); expect(inflect('fooBox')).to.be('fooBoxBar'); expect(inflect('FooBox')).to.be('FooBoxBar'); }); - it('ignores postfix if it is already at the end of the inflected string', function() { + it('ignores postfix if it is already at the end of the inflected string', function () { const inflect = inflector('foo', 'Bar'); expect(inflect('bar')).to.be('fooBar'); expect(inflect('showBoxBar')).to.be('fooShowBoxBar'); }); - it('works with "name"', function() { + it('works with "name"', function () { const inflect = inflector('in', 'Order'); expect(inflect('name')).to.be('inNameOrder'); }); diff --git a/src/legacy/ui/public/indexed_array/indexed_array.js b/src/legacy/ui/public/indexed_array/indexed_array.js index 39c79b2f021a3..79ef5e8c183da 100644 --- a/src/legacy/ui/public/indexed_array/indexed_array.js +++ b/src/legacy/ui/public/indexed_array/indexed_array.js @@ -21,9 +21,7 @@ import _ from 'lodash'; import { inflector } from './inflector'; import { organizeBy } from './helpers/organize_by'; -const pathGetter = _(_.get) - .rearg(1, 0) - .ary(2); +const pathGetter = _(_.get).rearg(1, 0).ary(2); const inflectIndex = inflector('by'); const inflectOrder = inflector('in', 'Order'); @@ -62,9 +60,7 @@ export class IndexedArray { if (typeof a === 'number' && typeof b === 'number') { return a - b; } - return String(a) - .toLowerCase() - .localeCompare(String(b).toLowerCase()); + return String(a).toLowerCase().localeCompare(String(b).toLowerCase()); }); }) ); @@ -171,7 +167,7 @@ export class IndexedArray { // shortcut for empty props if (!props || props.length === 0) return; - return props.map(prop => { + return props.map((prop) => { const indexName = inflect(prop); const getIndexValueFromItem = pathGetter.partial(prop).value(); let cache; @@ -180,7 +176,7 @@ export class IndexedArray { enumerable: false, configurable: false, - set: val => { + set: (val) => { // can't set any value other than the CLEAR_CACHE constant if (val === CLEAR_CACHE) { cache = false; @@ -208,7 +204,7 @@ export class IndexedArray { * @return {undefined} */ _clearIndices() { - this._indexNames.forEach(name => { + this._indexNames.forEach((name) => { this[name] = CLEAR_CACHE; }); } diff --git a/src/legacy/ui/public/indexed_array/inflector.js b/src/legacy/ui/public/indexed_array/inflector.js index 00aa4cdefc4f3..e034146f5f62f 100644 --- a/src/legacy/ui/public/indexed_array/inflector.js +++ b/src/legacy/ui/public/indexed_array/inflector.js @@ -39,7 +39,7 @@ export function inflector(prefix, postfix) { if (key.indexOf('.') !== -1) { inflected = key .split('.') - .map(function(step, i) { + .map(function (step, i) { return i === 0 ? step : upFirst(step, true); }) .join(''); diff --git a/src/legacy/ui/public/kfetch/kfetch.test.ts b/src/legacy/ui/public/kfetch/kfetch.test.ts index 259acc2753a43..c45a142d54e9b 100644 --- a/src/legacy/ui/public/kfetch/kfetch.test.ts +++ b/src/legacy/ui/public/kfetch/kfetch.test.ts @@ -353,11 +353,11 @@ describe('kfetch', () => { beforeEach(async () => { fetchMock.get('*', { foo: 'bar' }); addInterceptor({ - request: config => ({ + request: (config) => ({ ...config, pathname: '/my/intercepted-route', }), - response: res => ({ + response: (res) => ({ ...res, addedByResponseInterceptor: true, }), @@ -386,12 +386,12 @@ describe('kfetch', () => { beforeEach(async () => { fetchMock.get('*', { foo: 'bar' }); addInterceptor({ - request: config => + request: (config) => Promise.resolve({ ...config, pathname: '/my/intercepted-route', }), - response: res => + response: (res) => Promise.resolve({ ...res, addedByResponseInterceptor: true, @@ -421,7 +421,7 @@ function mockInterceptorCalls(interceptors: Interceptor[]) { const interceptorCalls: string[] = []; interceptors.forEach((interceptor, i) => { addInterceptor({ - request: config => { + request: (config) => { interceptorCalls.push(`Request #${i + 1}`); if (interceptor.request) { @@ -430,7 +430,7 @@ function mockInterceptorCalls(interceptors: Interceptor[]) { return config; }, - requestError: e => { + requestError: (e) => { interceptorCalls.push(`RequestError #${i + 1}`); if (interceptor.requestError) { return interceptor.requestError(e); @@ -438,7 +438,7 @@ function mockInterceptorCalls(interceptors: Interceptor[]) { throw e; }, - response: res => { + response: (res) => { interceptorCalls.push(`Response #${i + 1}`); if (interceptor.response) { @@ -447,7 +447,7 @@ function mockInterceptorCalls(interceptors: Interceptor[]) { return res; }, - responseError: e => { + responseError: (e) => { interceptorCalls.push(`ResponseError #${i + 1}`); if (interceptor.responseError) { diff --git a/src/legacy/ui/public/kfetch/kfetch.ts b/src/legacy/ui/public/kfetch/kfetch.ts index 02be7a32db296..4eb7149931575 100644 --- a/src/legacy/ui/public/kfetch/kfetch.ts +++ b/src/legacy/ui/public/kfetch/kfetch.ts @@ -60,7 +60,7 @@ export function createKfetch(http: HttpSetup) { .then(({ pathname, ...restOptions }) => http.fetch(pathname, { ...restOptions, prependBasePath }) ) - .catch(err => { + .catch((err) => { throw new KFetchError(err.response || { statusText: err.message }, err.body); }) ); diff --git a/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js b/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js index 3ca836e23881a..efcfb77997265 100644 --- a/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js +++ b/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js @@ -28,30 +28,30 @@ import { version } from '../../../../../core/server/utils/package_json'; const xsrfHeader = 'kbn-version'; -describe('chrome xsrf apis', function() { +describe('chrome xsrf apis', function () { const sandbox = sinon.createSandbox(); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); - describe('jQuery support', function() { - it('adds a global jQuery prefilter', function() { + describe('jQuery support', function () { + it('adds a global jQuery prefilter', function () { sandbox.stub($, 'ajaxPrefilter'); $setupXsrfRequestInterceptor(version); expect($.ajaxPrefilter.callCount).to.be(1); }); - describe('jQuery prefilter', function() { + describe('jQuery prefilter', function () { let prefilter; - beforeEach(function() { + beforeEach(function () { sandbox.stub($, 'ajaxPrefilter'); $setupXsrfRequestInterceptor(version); prefilter = $.ajaxPrefilter.args[0][0]; }); - it(`sets the ${xsrfHeader} header`, function() { + it(`sets the ${xsrfHeader} header`, function () { const setHeader = sinon.stub(); prefilter({}, {}, { setRequestHeader: setHeader }); @@ -59,24 +59,24 @@ describe('chrome xsrf apis', function() { expect(setHeader.args[0]).to.eql([xsrfHeader, version]); }); - it('can be canceled by setting the kbnXsrfToken option', function() { + it('can be canceled by setting the kbnXsrfToken option', function () { const setHeader = sinon.stub(); prefilter({ kbnXsrfToken: false }, {}, { setRequestHeader: setHeader }); expect(setHeader.callCount).to.be(0); }); }); - describe('Angular support', function() { + describe('Angular support', function () { let $http; let $httpBackend; - beforeEach(function() { + beforeEach(function () { sandbox.stub($, 'ajaxPrefilter'); ngMock.module($setupXsrfRequestInterceptor(version)); }); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $http = $injector.get('$http'); $httpBackend = $injector.get('$httpBackend'); @@ -84,14 +84,14 @@ describe('chrome xsrf apis', function() { }) ); - afterEach(function() { + afterEach(function () { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); - it(`injects a ${xsrfHeader} header on every request`, function() { + it(`injects a ${xsrfHeader} header on every request`, function () { $httpBackend - .expectPOST('/api/test', undefined, function(headers) { + .expectPOST('/api/test', undefined, function (headers) { return headers[xsrfHeader] === version; }) .respond(200, ''); @@ -100,9 +100,9 @@ describe('chrome xsrf apis', function() { $httpBackend.flush(); }); - it('skips requests with the kbnXsrfToken set falsy', function() { + it('skips requests with the kbnXsrfToken set falsy', function () { $httpBackend - .expectPOST('/api/test', undefined, function(headers) { + .expectPOST('/api/test', undefined, function (headers) { return !(xsrfHeader in headers); }) .respond(200, ''); @@ -128,10 +128,10 @@ describe('chrome xsrf apis', function() { $httpBackend.flush(); }); - it('treats the kbnXsrfToken option as boolean-y', function() { + it('treats the kbnXsrfToken option as boolean-y', function () { const customToken = `custom:${version}`; $httpBackend - .expectPOST('/api/test', undefined, function(headers) { + .expectPOST('/api/test', undefined, function (headers) { return headers[xsrfHeader] === version; }) .respond(200, ''); diff --git a/src/legacy/ui/public/management/index.d.ts b/src/legacy/ui/public/management/index.d.ts deleted file mode 100644 index 529efd36623a3..0000000000000 --- a/src/legacy/ui/public/management/index.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -declare module 'ui/management' { - export const SidebarNav: React.FC; - export const management: any; // TODO - properly provide types - export const MANAGEMENT_BREADCRUMB: { - text: string; - href: string; - }; -} diff --git a/src/legacy/ui/public/management/index.js b/src/legacy/ui/public/management/index.js deleted file mode 100644 index 25d3678c5dbba..0000000000000 --- a/src/legacy/ui/public/management/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { MANAGEMENT_BREADCRUMB } from './breadcrumbs'; -import { npStart } from 'ui/new_platform'; -export const management = npStart.plugins.management.legacy; diff --git a/src/legacy/ui/public/modules.js b/src/legacy/ui/public/modules.js index cbc222061e757..bb1c8aead1c34 100644 --- a/src/legacy/ui/public/modules.js +++ b/src/legacy/ui/public/modules.js @@ -107,7 +107,7 @@ export function get(moduleName, requires) { module.close = _.partial(close, moduleName); // ensure that it is required by linked modules - _.each(links, function(app) { + _.each(links, function (app) { if (!~app.requires.indexOf(moduleName)) app.requires.push(moduleName); }); } @@ -131,7 +131,7 @@ export function close(moduleName) { if (i > -1) links.splice(i, 1); // remove from linked modules list of required modules - _.each(links, function(app) { + _.each(links, function (app) { _.pull(app.requires, moduleName); }); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 67422fa659439..d98770842a0f0 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -28,6 +28,11 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../src/plugins/data/public/search/aggs'; import { ComponentRegistry } from '../../../../../src/plugins/advanced_settings/public/'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/public/'; +import { + CSV_SEPARATOR_SETTING, + CSV_QUOTE_VALUES_SETTING, +} from '../../../../../src/plugins/share/public'; const mockObservable = () => { return { @@ -49,18 +54,31 @@ let isTimeRangeSelectorEnabled = true; let isAutoRefreshSelectorEnabled = true; export const mockUiSettings = { - get: item => { - return mockUiSettings[item]; + get: (item, defaultValue) => { + const defaultValues = { + dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', + 'dateFormat:tz': 'UTC', + [UI_SETTINGS.SHORT_DOTS_ENABLE]: true, + [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: true, + [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: true, + [UI_SETTINGS.QUERY_STRING_OPTIONS]: {}, + [UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN]: '($0,0.[00])', + [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: '0,0.[000]', + [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: '0,0.[000]%', + [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: 'en', + [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: {}, + [CSV_SEPARATOR_SETTING]: ',', + [CSV_QUOTE_VALUES_SETTING]: true, + [UI_SETTINGS.SEARCH_QUERY_LANGUAGE]: 'kuery', + 'state:storeInSessionStorage': false, + }; + + return defaultValues[item] || defaultValue; }, getUpdate$: () => ({ subscribe: sinon.fake(), }), isDefault: sinon.fake(), - 'query:allowLeadingWildcards': true, - 'query:queryString:options': {}, - 'courier:ignoreFilterIfFieldNotInIndex': true, - 'dateFormat:tz': 'Browser', - 'format:defaultTypeMap': {}, }; const mockCoreSetup = { @@ -134,7 +152,7 @@ const querySetup = { getRefreshInterval: () => { return refreshInterval; }, - setRefreshInterval: interval => { + setRefreshInterval: (interval) => { refreshInterval = interval; }, enableTimeRangeSelector: () => { @@ -173,8 +191,8 @@ const mockAggTypesRegistry = () => { notifications: mockCoreStart.notifications, }), }); - aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); - aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); + aggTypes.buckets.forEach((type) => registrySetup.registerBucket(type)); + aggTypes.metrics.forEach((type) => registrySetup.registerMetric(type)); return registry; }; @@ -236,6 +254,9 @@ export const npSetup = { }, share: { register: () => {}, + urlGenerators: { + registerUrlGenerator: () => {}, + }, }, devTools: { register: () => {}, @@ -382,7 +403,7 @@ export const npStart = { }, getSuggestions: sinon.fake(), indexPatterns: { - get: sinon.spy(indexPatternId => + get: sinon.spy((indexPatternId) => Promise.resolve({ id: indexPatternId, isTimeNanosBased: () => false, @@ -428,7 +449,7 @@ export const npStart = { getRefreshInterval: () => { return refreshInterval; }, - setRefreshInterval: interval => { + setRefreshInterval: (interval) => { refreshInterval = interval; }, enableTimeRangeSelector: () => { @@ -524,6 +545,8 @@ export function __setup__(coreSetup) { // bootstrap an LP plugin outside of tests) npSetup.core.application.register = () => {}; + npSetup.core.uiSettings.get = mockUiSettings.get; + // Services that need to be set in the legacy platform since the legacy data // & vis plugins which previously provided them have been removed. setSetupServices(npSetup); @@ -532,6 +555,8 @@ export function __setup__(coreSetup) { export function __start__(coreStart) { npStart.core = coreStart; + npStart.core.uiSettings.get = mockUiSettings.get; + // Services that need to be set in the legacy platform since the legacy data // & vis plugins which previously provided them have been removed. setStartServices(npStart); diff --git a/src/legacy/ui/public/new_platform/new_platform.test.ts b/src/legacy/ui/public/new_platform/new_platform.test.ts index 21e7b559f71f5..d515c348ca440 100644 --- a/src/legacy/ui/public/new_platform/new_platform.test.ts +++ b/src/legacy/ui/public/new_platform/new_platform.test.ts @@ -113,7 +113,7 @@ describe('ui/new_platform', () => { controller(scopeMock, elementMock); // Flush promise queue. Must be done this way because the controller cannot return a Promise without breaking // angular. - await new Promise(resolve => setTimeout(resolve, 1)); + await new Promise((resolve) => setTimeout(resolve, 1)); const [event, eventHandler] = scopeMock.$on.mock.calls[0]; expect(event).toEqual('$destroy'); diff --git a/src/legacy/ui/public/new_platform/set_services.test.ts b/src/legacy/ui/public/new_platform/set_services.test.ts index 25a4524925169..b7878954846fa 100644 --- a/src/legacy/ui/public/new_platform/set_services.test.ts +++ b/src/legacy/ui/public/new_platform/set_services.test.ts @@ -29,8 +29,8 @@ import { npSetup, npStart } from './__mocks__'; describe('ui/new_platform', () => { describe('set service getters', () => { const testServiceGetters = (name: string, services: Record) => { - const getters = Object.keys(services).filter(k => k.substring(0, 3) === 'get'); - getters.forEach(g => { + const getters = Object.keys(services).filter((k) => k.substring(0, 3) === 'get'); + getters.forEach((g) => { it(`ui/new_platform sets a value for ${name} getter ${g}`, () => { __reset__(); __setup__( diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts index 9d02ad67b3937..ee92eda064aa8 100644 --- a/src/legacy/ui/public/new_platform/set_services.ts +++ b/src/legacy/ui/public/new_platform/set_services.ts @@ -65,6 +65,7 @@ export function setStartServices(npStart: NpStart) { visualizationsServices.setCapabilities(npStart.core.application.capabilities); visualizationsServices.setHttp(npStart.core.http); visualizationsServices.setApplication(npStart.core.application); + visualizationsServices.setEmbeddable(npStart.plugins.embeddable); visualizationsServices.setSavedObjects(npStart.core.savedObjects); visualizationsServices.setIndexPatterns(npStart.plugins.data.indexPatterns); visualizationsServices.setFilterManager(npStart.plugins.data.query.filterManager); diff --git a/src/legacy/ui/public/private/__tests__/private.js b/src/legacy/ui/public/private/__tests__/private.js index c8c9c7467ad2e..1f9d696bb440f 100644 --- a/src/legacy/ui/public/private/__tests__/private.js +++ b/src/legacy/ui/public/private/__tests__/private.js @@ -20,17 +20,17 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -describe('Private module loader', function() { +describe('Private module loader', function () { let Private; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { Private = $injector.get('Private'); }) ); - it('accepts a provider that will be called to init a module', function() { + it('accepts a provider that will be called to init a module', function () { const football = {}; function Provider() { return football; @@ -40,7 +40,7 @@ describe('Private module loader', function() { expect(instance).to.be(football); }); - it('injects angular dependencies into the Provider', function() { + it('injects angular dependencies into the Provider', function () { function Provider(Private) { return Private; } @@ -49,8 +49,8 @@ describe('Private module loader', function() { expect(instance).to.be(Private); }); - it('detects circular dependencies', function() { - expect(function() { + it('detects circular dependencies', function () { + expect(function () { function Provider1() { Private(Provider2); } @@ -63,7 +63,7 @@ describe('Private module loader', function() { }).to.throwException(/circular/i); }); - it('always provides the same instance form the Provider', function() { + it('always provides the same instance form the Provider', function () { function Provider() { return {}; } @@ -71,8 +71,8 @@ describe('Private module loader', function() { expect(Private(Provider)).to.be(Private(Provider)); }); - describe('#stub', function() { - it('accepts a replacement instance for a Provider', function() { + describe('#stub', function () { + it('accepts a replacement instance for a Provider', function () { const replaced = {}; const replacement = {}; @@ -95,8 +95,8 @@ describe('Private module loader', function() { }); }); - describe('#swap', function() { - it('accepts a new Provider that should replace an existing Provider', function() { + describe('#swap', function () { + it('accepts a new Provider that should replace an existing Provider', function () { function Provider1() { return {}; } @@ -120,7 +120,7 @@ describe('Private module loader', function() { expect(instance3).to.be(instance1); }); - it('gives the new Provider access to the Provider it replaced via an injectable dependency called $decorate', function() { + it('gives the new Provider access to the Provider it replaced via an injectable dependency called $decorate', function () { function Provider1() { return {}; } diff --git a/src/legacy/ui/public/promises/__tests__/promises.js b/src/legacy/ui/public/promises/__tests__/promises.js index cbf995d76e994..7041aa2993376 100644 --- a/src/legacy/ui/public/promises/__tests__/promises.js +++ b/src/legacy/ui/public/promises/__tests__/promises.js @@ -35,7 +35,7 @@ describe('Promise service', () => { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject($injector => { + ngMock.inject(($injector) => { sandbox.useFakeTimers(); Promise = $injector.get('Promise'); @@ -126,8 +126,8 @@ describe('Promise service', () => { describe('Promise.race()', () => { it(`resolves with the first resolved promise's value`, () => { - const p1 = new Promise(resolve => setTimeout(resolve, 100, 1)); - const p2 = new Promise(resolve => setTimeout(resolve, 200, 2)); + const p1 = new Promise((resolve) => setTimeout(resolve, 100, 1)); + const p2 = new Promise((resolve) => setTimeout(resolve, 200, 2)); const onResolve = sinon.stub(); Promise.race([p1, p2]).then(onResolve); @@ -155,10 +155,10 @@ describe('Promise service', () => { it('does not wait for subsequent promises to resolve/reject', () => { const onP1Resolve = sinon.stub(); - const p1 = new Promise(resolve => setTimeout(resolve, 100)).then(onP1Resolve); + const p1 = new Promise((resolve) => setTimeout(resolve, 100)).then(onP1Resolve); const onP2Resolve = sinon.stub(); - const p2 = new Promise(resolve => setTimeout(resolve, 101)).then(onP2Resolve); + const p2 = new Promise((resolve) => setTimeout(resolve, 101)).then(onP2Resolve); const onResolve = sinon.stub(); Promise.race([p1, p2]).then(onResolve); @@ -226,8 +226,8 @@ describe('Promise service', () => { describe('argument is a generator', () => { it('resolves with the first resolved value', () => { function* gen() { - yield new Promise(resolve => setTimeout(resolve, 100, 1)); - yield new Promise(resolve => setTimeout(resolve, 200, 2)); + yield new Promise((resolve) => setTimeout(resolve, 100, 1)); + yield new Promise((resolve) => setTimeout(resolve, 200, 2)); } const onResolve = sinon.stub(); @@ -242,7 +242,7 @@ describe('Promise service', () => { it('resolves with the first non-promise value', () => { function* gen() { yield 1; - yield new Promise(resolve => setTimeout(resolve, 200, 2)); + yield new Promise((resolve) => setTimeout(resolve, 200, 2)); } const onResolve = sinon.stub(); @@ -260,7 +260,7 @@ describe('Promise service', () => { yieldCount += 1; yield 1; yieldCount += 1; - yield new Promise(resolve => setTimeout(resolve, 200, 2)); + yield new Promise((resolve) => setTimeout(resolve, 200, 2)); } const onResolve = sinon.stub(); diff --git a/src/legacy/ui/public/react_components.js b/src/legacy/ui/public/react_components.js index b771e37c9d538..21fb53d6407fa 100644 --- a/src/legacy/ui/public/react_components.js +++ b/src/legacy/ui/public/react_components.js @@ -25,8 +25,8 @@ import { uiModules } from './modules'; const app = uiModules.get('app/kibana', ['react']); -app.directive('icon', reactDirective => reactDirective(EuiIcon)); +app.directive('icon', (reactDirective) => reactDirective(EuiIcon)); -app.directive('iconTip', reactDirective => +app.directive('iconTip', (reactDirective) => reactDirective(EuiIconTip, ['content', 'type', 'position', 'title', 'color']) ); diff --git a/src/legacy/ui/public/registry/__tests__/registry.js b/src/legacy/ui/public/registry/__tests__/registry.js index 14e4ce4a14e28..10b2423abc0e3 100644 --- a/src/legacy/ui/public/registry/__tests__/registry.js +++ b/src/legacy/ui/public/registry/__tests__/registry.js @@ -21,23 +21,23 @@ import { uiRegistry } from '../_registry'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -describe('Registry', function() { +describe('Registry', function () { let Private; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { Private = $injector.get('Private'); }) ); - it('is technically a function', function() { + it('is technically a function', function () { const reg = uiRegistry(); expect(reg).to.be.a('function'); }); - describe('#register', function() { - it('accepts a Private module', function() { + describe('#register', function () { + it('accepts a Private module', function () { const reg = uiRegistry(); const mod = function SomePrivateModule() {}; @@ -45,9 +45,9 @@ describe('Registry', function() { // modules are not exposed, so this is the most that we can test }); - it('applies the filter function if one is specified', function() { + it('applies the filter function if one is specified', function () { const reg = uiRegistry({ - filter: item => item.value % 2 === 0, // register only even numbers + filter: (item) => item.value % 2 === 0, // register only even numbers }); reg.register(() => ({ value: 17 })); @@ -60,8 +60,8 @@ describe('Registry', function() { }); }); - describe('as a module', function() { - it('exposes the list of registered modules', function() { + describe('as a module', function () { + it('exposes the list of registered modules', function () { const reg = uiRegistry(); const mod = function SomePrivateModule(Private) { this.PrivateModuleLoader = Private; @@ -74,12 +74,12 @@ describe('Registry', function() { }); }); - describe('spec', function() { - it('executes with the module list as "this", and can override it', function() { + describe('spec', function () { + it('executes with the module list as "this", and can override it', function () { let self; const reg = uiRegistry({ - constructor: function() { + constructor: function () { return { mods: (self = this) }; }, }); @@ -90,8 +90,8 @@ describe('Registry', function() { }); }); - describe('spec.name', function() { - it('sets the displayName of the registry and the name param on the final instance', function() { + describe('spec.name', function () { + it('sets the displayName of the registry and the name param on the final instance', function () { const reg = uiRegistry({ name: 'visTypes', }); @@ -101,12 +101,12 @@ describe('Registry', function() { }); }); - describe('spec.constructor', function() { - it('executes before the modules are returned', function() { + describe('spec.constructor', function () { + it('executes before the modules are returned', function () { let i = 0; const reg = uiRegistry({ - constructor: function() { + constructor: function () { i = i + 1; }, }); @@ -115,11 +115,11 @@ describe('Registry', function() { expect(i).to.be(1); }); - it('executes with the module list as "this", and can override it', function() { + it('executes with the module list as "this", and can override it', function () { let self; const reg = uiRegistry({ - constructor: function() { + constructor: function () { return { mods: (self = this) }; }, }); @@ -134,7 +134,7 @@ describe('Registry', function() { it('is called with the registered providers and defines the initial set of values in the registry', () => { const reg = uiRegistry({ invokeProviders(providers) { - return providers.map(i => i * 1000); + return providers.map((i) => i * 1000); }, }); @@ -152,10 +152,10 @@ describe('Registry', function() { }); }); - describe('spec[any]', function() { - it('mixes the extra properties into the module list', function() { + describe('spec[any]', function () { + it('mixes the extra properties into the module list', function () { const reg = uiRegistry({ - someMethod: function() { + someMethod: function () { return this; }, }); diff --git a/src/legacy/ui/public/registry/_registry.js b/src/legacy/ui/public/registry/_registry.js index 39e0d1526fd8a..85aa1d9f2eca8 100644 --- a/src/legacy/ui/public/registry/_registry.js +++ b/src/legacy/ui/public/registry/_registry.js @@ -91,14 +91,14 @@ export function uiRegistry(spec) { * that were registered, the registry spec * defines how things will be indexed. */ - const registry = function(Private, $injector) { - getInvokedProviders = function(newProviders) { + const registry = function (Private, $injector) { + getInvokedProviders = function (newProviders) { let set = invokeProviders ? $injector.invoke(invokeProviders, undefined, { providers: newProviders }) : newProviders.map(Private); if (filter && _.isFunction(filter)) { - set = set.filter(item => filter(item)); + set = set.filter((item) => filter(item)); } return set; @@ -123,7 +123,7 @@ export function uiRegistry(spec) { registry.displayName = '[registry ' + props.name + ']'; - registry.register = function(privateModule) { + registry.register = function (privateModule) { providers.push(privateModule); if (isInstantiated) { diff --git a/src/legacy/ui/public/routes/__tests__/_route_manager.js b/src/legacy/ui/public/routes/__tests__/_route_manager.js index 011922c7ed789..51bde8b8605ac 100644 --- a/src/legacy/ui/public/routes/__tests__/_route_manager.js +++ b/src/legacy/ui/public/routes/__tests__/_route_manager.js @@ -31,9 +31,9 @@ const chainableMethods = [ ]; let $rp; -describe('routes/route_manager', function() { +describe('routes/route_manager', function () { beforeEach( - ngMock.module('kibana', function($routeProvider) { + ngMock.module('kibana', function ($routeProvider) { $rp = $routeProvider; sinon.stub($rp, 'otherwise'); sinon.stub($rp, 'when'); @@ -41,19 +41,19 @@ describe('routes/route_manager', function() { ); beforeEach( - ngMock.inject(function() { + ngMock.inject(function () { routes = new RouteManager(); }) ); - it('should have chainable methods: ' + _.pluck(chainableMethods, 'name').join(', '), function() { - chainableMethods.forEach(function(meth) { + it('should have chainable methods: ' + _.pluck(chainableMethods, 'name').join(', '), function () { + chainableMethods.forEach(function (meth) { expect(routes[meth.name].apply(routes, _.clone(meth.args))).to.be(routes); }); }); - describe('#otherwise', function() { - it('should forward the last otherwise route', function() { + describe('#otherwise', function () { + it('should forward the last otherwise route', function () { const otherRoute = {}; routes.otherwise({}); routes.otherwise(otherRoute); @@ -65,15 +65,15 @@ describe('routes/route_manager', function() { }); }); - describe('#when', function() { - it('should merge the additions into the when() defined routes', function() { + describe('#when', function () { + it('should merge the additions into the when() defined routes', function () { routes.when('/some/route'); routes.when('/some/other/route'); // add the addition resolve to every route routes.defaults(/.*/, { resolve: { - addition: function() {}, + addition: function () {}, }, }); @@ -89,21 +89,21 @@ describe('routes/route_manager', function() { }); }); - describe('#config', function() { - it('should add defined routes to the global $routeProvider service in order', function() { + describe('#config', function () { + it('should add defined routes to the global $routeProvider service in order', function () { const args = [ ['/one', {}], ['/two', {}], ]; - args.forEach(function(a) { + args.forEach(function (a) { routes.when(a[0], a[1]); }); routes.config($rp); expect($rp.when.callCount).to.be(args.length); - _.times(args.length, function(i) { + _.times(args.length, function (i) { const call = $rp.when.getCall(i); const a = args.shift(); @@ -112,7 +112,7 @@ describe('routes/route_manager', function() { }); }); - it('sets route.reloadOnSearch to false by default', function() { + it('sets route.reloadOnSearch to false by default', function () { routes.when('/nothing-set'); routes.when('/no-reload', { reloadOnSearch: false }); routes.when('/always-reload', { reloadOnSearch: true }); diff --git a/src/legacy/ui/public/routes/__tests__/_work_queue.js b/src/legacy/ui/public/routes/__tests__/_work_queue.js index cc6b4ce8d29bc..72891f7321fbd 100644 --- a/src/legacy/ui/public/routes/__tests__/_work_queue.js +++ b/src/legacy/ui/public/routes/__tests__/_work_queue.js @@ -24,32 +24,32 @@ import { WorkQueue } from '../work_queue'; import sinon from 'sinon'; import { createDefer } from 'ui/promises'; -describe('work queue', function() { +describe('work queue', function () { let queue; let Promise; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function(_Promise_) { + ngMock.inject(function (_Promise_) { Promise = _Promise_; }) ); - beforeEach(function() { + beforeEach(function () { queue = new WorkQueue(); }); - afterEach(function() { + afterEach(function () { queue.empty(); }); - describe('#push', function() { - it('adds to the interval queue', function() { + describe('#push', function () { + it('adds to the interval queue', function () { queue.push(createDefer(Promise)); expect(queue).to.have.length(1); }); }); - describe('#resolveWhenFull', function() { - it('resolves requests waiting for the queue to fill when appropriate', function() { + describe('#resolveWhenFull', function () { + it('resolves requests waiting for the queue to fill when appropriate', function () { const size = _.random(5, 50); queue.limit = size; @@ -58,7 +58,7 @@ describe('work queue', function() { queue.resolveWhenFull(whenFull); // push all but one into the queue - _.times(size - 1, function() { + _.times(size - 1, function () { queue.push(createDefer(Promise)); }); @@ -81,7 +81,7 @@ describe('work queue', function() { const size = _.random(5, 50); const stub = sinon.stub(); - _.times(size, function() { + _.times(size, function () { const d = createDefer(Promise); // overwrite the defer methods with the stub d.resolve = stub; @@ -92,9 +92,9 @@ describe('work queue', function() { then(size, stub); } - describe('#doWork', function() { - it('flushes the queue and resolves all promises', function() { - fillWithStubs(function(size, stub) { + describe('#doWork', function () { + it('flushes the queue and resolves all promises', function () { + fillWithStubs(function (size, stub) { expect(queue).to.have.length(size); queue.doWork(); expect(queue).to.have.length(0); @@ -103,9 +103,9 @@ describe('work queue', function() { }); }); - describe('#empty()', function() { - it('empties the internal queue WITHOUT resolving any promises', function() { - fillWithStubs(function(size, stub) { + describe('#empty()', function () { + it('empties the internal queue WITHOUT resolving any promises', function () { + fillWithStubs(function (size, stub) { expect(queue).to.have.length(size); queue.empty(); expect(queue).to.have.length(0); diff --git a/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js b/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js index c5175e86040c1..8ae85fce591a1 100644 --- a/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js +++ b/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js @@ -26,20 +26,20 @@ import '../../private'; let routes; -describe('wrapRouteWithPrep fn', function() { +describe('wrapRouteWithPrep fn', function () { require('test_utils/no_digest_promises').activateForSuite(); - beforeEach(function() { + beforeEach(function () { routes = new RouteManager(); }); - const SchedulingTest = function(opts) { + const SchedulingTest = function (opts) { opts = opts || {}; const delaySetup = opts.delayUserWork ? 0 : 50; const delayUserWork = opts.delayUserWork ? 50 : 0; - return function() { + return function () { ngMock.module('kibana'); let setupComplete = false; let userWorkComplete = false; @@ -47,14 +47,14 @@ describe('wrapRouteWithPrep fn', function() { let Promise; let $injector; - ngMock.inject(function(_Promise_, _$injector_) { + ngMock.inject(function (_Promise_, _$injector_) { Promise = _Promise_; $injector = _$injector_; }); - routes.addSetupWork(function() { - return new Promise(function(resolve) { - setTimeout(function() { + routes.addSetupWork(function () { + return new Promise(function (resolve) { + setTimeout(function () { setupComplete = true; resolve(); }, delaySetup); @@ -64,26 +64,26 @@ describe('wrapRouteWithPrep fn', function() { routes .when('/', { resolve: { - test: function() { + test: function () { expect(setupComplete).to.be(true); userWorkComplete = true; }, }, }) .config({ - when: function(p, _r) { + when: function (p, _r) { route = _r; }, }); - return new Promise(function(resolve, reject) { - setTimeout(function() { + return new Promise(function (resolve, reject) { + setTimeout(function () { Promise.all( - _.map(route.resolve, function(fn) { + _.map(route.resolve, function (fn) { return $injector.invoke(fn); }) ) - .then(function() { + .then(function () { expect(setupComplete).to.be(true); expect(userWorkComplete).to.be(true); }) diff --git a/src/legacy/ui/public/routes/__tests__/index.js b/src/legacy/ui/public/routes/__tests__/index.js index 62bbec9ef3e67..30cedaaca3d19 100644 --- a/src/legacy/ui/public/routes/__tests__/index.js +++ b/src/legacy/ui/public/routes/__tests__/index.js @@ -20,4 +20,4 @@ import './_route_manager'; import './_work_queue'; import './_wrap_route_with_prep'; -describe('Custom Route Management', function() {}); +describe('Custom Route Management', function () {}); diff --git a/src/legacy/ui/public/routes/route_manager.js b/src/legacy/ui/public/routes/route_manager.js index 1cf2a5fc5a64a..de8a541d1c50a 100644 --- a/src/legacy/ui/public/routes/route_manager.js +++ b/src/legacy/ui/public/routes/route_manager.js @@ -31,12 +31,12 @@ export default function RouteManager() { const defaults = []; let otherwise; - self.config = function($routeProvider) { - when.forEach(function(args) { + self.config = function ($routeProvider) { + when.forEach(function (args) { const path = args[0]; const route = args[1] || {}; - defaults.forEach(def => { + defaults.forEach((def) => { if (def.regex.test(path)) { defaultsDeep(route, cloneDeep(def.value)); } @@ -56,7 +56,7 @@ export default function RouteManager() { } }; - self.run = function($location, $route, $injector, $rootScope) { + self.run = function ($location, $route, $injector, $rootScope) { if (window.elasticApm && typeof window.elasticApm.startTransaction === 'function') { /** * capture route-change events as transactions which happens after @@ -89,24 +89,24 @@ export default function RouteManager() { this.afterSetupWork = wrap(setup.afterSetupWork, wrapSetupAndChain); this.afterWork = wrap(setup.afterWork, wrapSetupAndChain); - self.when = function(path, route) { + self.when = function (path, route) { when.push([path, route]); return self; }; // before attaching the routes to the routeProvider, test the RE // against the .when() path and add/override the resolves if there is a match - self.defaults = function(regex, value) { + self.defaults = function (regex, value) { defaults.push({ regex, value }); return self; }; - self.otherwise = function(route) { + self.otherwise = function (route) { otherwise = route; return self; }; - self.getBreadcrumbs = function() { + self.getBreadcrumbs = function () { // overwritten in self.run(); return []; }; diff --git a/src/legacy/ui/public/routes/route_setup_manager.js b/src/legacy/ui/public/routes/route_setup_manager.js index ded295e97d4c4..a7a2f078f40fb 100644 --- a/src/legacy/ui/public/routes/route_setup_manager.js +++ b/src/legacy/ui/public/routes/route_setup_manager.js @@ -55,7 +55,7 @@ export class RouteSetupManager { */ doWork(Promise, $injector, userWork) { const invokeEach = (arr, locals) => { - return Promise.map(arr, fn => { + return Promise.map(arr, (fn) => { if (!fn) return; return $injector.invoke(fn, null, locals); }); @@ -69,13 +69,13 @@ export class RouteSetupManager { // clone so we don't discard handlers or loose them handlers = handlers.slice(0); - const next = err => { + const next = (err) => { if (!handlers.length) throw err; const handler = handlers.shift(); if (!handler) return next(err); - return Promise.try(function() { + return Promise.try(function () { return $injector.invoke(handler, null, { err }); }).catch(next); }; @@ -86,7 +86,7 @@ export class RouteSetupManager { return invokeEach(this.setupWork) .then( () => invokeEach(this.onSetupComplete), - err => callErrorHandlers(this.onSetupError, err) + (err) => callErrorHandlers(this.onSetupError, err) ) .then(() => { // wait for the queue to fill up, then do all the work @@ -95,7 +95,7 @@ export class RouteSetupManager { return defer.promise.then(() => Promise.all(userWork.doWork())); }) - .catch(error => { + .catch((error) => { if (error === WAIT_FOR_URL_CHANGE_TOKEN) { // prevent moving forward, return a promise that never resolves // so that the $router can observe the $location update @@ -106,7 +106,7 @@ export class RouteSetupManager { }) .then( () => invokeEach(this.onWorkComplete), - err => callErrorHandlers(this.onWorkError, err) + (err) => callErrorHandlers(this.onWorkError, err) ); } } diff --git a/src/legacy/ui/public/routes/work_queue.js b/src/legacy/ui/public/routes/work_queue.js index f733375697438..5c4c83663c590 100644 --- a/src/legacy/ui/public/routes/work_queue.js +++ b/src/legacy/ui/public/routes/work_queue.js @@ -25,40 +25,40 @@ export function WorkQueue() { q.limit = 0; Object.defineProperty(q, 'length', { - get: function() { + get: function () { return work.length; }, }); - const resolve = function(defers) { - return defers.splice(0).map(function(defer) { + const resolve = function (defers) { + return defers.splice(0).map(function (defer) { return defer.resolve(); }); }; - const checkIfFull = function() { + const checkIfFull = function () { if (work.length >= q.limit && fullDefers.length) { resolve(fullDefers); } }; - q.resolveWhenFull = function(defer) { + q.resolveWhenFull = function (defer) { fullDefers.push(defer); checkIfFull(); }; - q.doWork = function() { + q.doWork = function () { const resps = resolve(work); checkIfFull(); return resps; }; - q.empty = function() { + q.empty = function () { work.splice(0); checkIfFull(); }; - q.push = function(defer) { + q.push = function (defer) { work.push(defer); checkIfFull(); }; diff --git a/src/legacy/ui/public/routes/wrap_route_with_prep.js b/src/legacy/ui/public/routes/wrap_route_with_prep.js index e74de9c85efa8..e9ed33148d9ac 100644 --- a/src/legacy/ui/public/routes/wrap_route_with_prep.js +++ b/src/legacy/ui/public/routes/wrap_route_with_prep.js @@ -30,18 +30,18 @@ export function wrapRouteWithPrep(route, setup) { userWork.limit = _.keys(route.resolve).length; const resolve = { - __prep__: function($injector) { + __prep__: function ($injector) { return $injector.invoke(setup.doWork, setup, { userWork }); }, }; // send each user resolve to the userWork queue, which will prevent it from running before the // prep is complete - _.forOwn(route.resolve || {}, function(expr, name) { - resolve[name] = function($injector, Promise) { + _.forOwn(route.resolve || {}, function (expr, name) { + resolve[name] = function ($injector, Promise) { const defer = createDefer(Promise); userWork.push(defer); - return defer.promise.then(function() { + return defer.promise.then(function () { return $injector[angular.isString(expr) ? 'get' : 'invoke'](expr); }); }; diff --git a/src/legacy/ui/public/state_management/__tests__/app_state.js b/src/legacy/ui/public/state_management/__tests__/app_state.js index 1c8cfe7f796a1..c47fa3bb0417f 100644 --- a/src/legacy/ui/public/state_management/__tests__/app_state.js +++ b/src/legacy/ui/public/state_management/__tests__/app_state.js @@ -22,31 +22,31 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { AppStateProvider } from '../app_state'; -describe('State Management', function() { +describe('State Management', function () { let $rootScope; let AppState; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function(_$rootScope_, _$location_, Private) { + ngMock.inject(function (_$rootScope_, _$location_, Private) { $rootScope = _$rootScope_; AppState = Private(AppStateProvider); }) ); - describe('App State', function() { + describe('App State', function () { let appState; - beforeEach(function() { + beforeEach(function () { appState = new AppState(); }); - it('should have _urlParam of _a', function() { + it('should have _urlParam of _a', function () { expect(appState).to.have.property('_urlParam'); expect(appState._urlParam).to.equal('_a'); }); - it('should use passed in params', function() { + it('should use passed in params', function () { const params = { test: true, mock: false, @@ -55,17 +55,17 @@ describe('State Management', function() { appState = new AppState(params); expect(appState).to.have.property('_defaults'); - Object.keys(params).forEach(function(key) { + Object.keys(params).forEach(function (key) { expect(appState._defaults).to.have.property(key); expect(appState._defaults[key]).to.equal(params[key]); }); }); - it('should have a destroy method', function() { + it('should have a destroy method', function () { expect(appState).to.have.property('destroy'); }); - it('should be destroyed on $routeChangeStart', function() { + it('should be destroyed on $routeChangeStart', function () { const destroySpy = sinon.spy(appState, 'destroy'); $rootScope.$emit('$routeChangeStart'); diff --git a/src/legacy/ui/public/state_management/__tests__/config_provider.js b/src/legacy/ui/public/state_management/__tests__/config_provider.js index 1f971bed6c165..9f756bc51dcb0 100644 --- a/src/legacy/ui/public/state_management/__tests__/config_provider.js +++ b/src/legacy/ui/public/state_management/__tests__/config_provider.js @@ -21,13 +21,13 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import '../config_provider'; -describe('State Management Config', function() { +describe('State Management Config', function () { let stateManagementConfig; describe('is enabled', () => { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function(_stateManagementConfig_) { + ngMock.inject(function (_stateManagementConfig_) { stateManagementConfig = _stateManagementConfig_; }) ); @@ -39,13 +39,13 @@ describe('State Management Config', function() { describe('can be disabled', () => { beforeEach( - ngMock.module('kibana', function(stateManagementConfigProvider) { + ngMock.module('kibana', function (stateManagementConfigProvider) { stateManagementConfigProvider.disable(); }) ); beforeEach( - ngMock.inject(function(_stateManagementConfig_) { + ngMock.inject(function (_stateManagementConfig_) { stateManagementConfig = _stateManagementConfig_; }) ); diff --git a/src/legacy/ui/public/state_management/__tests__/global_state.js b/src/legacy/ui/public/state_management/__tests__/global_state.js index 08c90bab0d2ef..e9dae5880a8d1 100644 --- a/src/legacy/ui/public/state_management/__tests__/global_state.js +++ b/src/legacy/ui/public/state_management/__tests__/global_state.js @@ -21,20 +21,20 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import '../global_state'; -describe('State Management', function() { +describe('State Management', function () { let $location; let state; beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function(_$location_, globalState) { + ngMock.inject(function (_$location_, globalState) { $location = _$location_; state = globalState; }) ); - describe('Global State', function() { - it('should use previous state when not in URL', function() { + describe('Global State', function () { + it('should use previous state when not in URL', function () { // set satte via URL $location.search({ _g: '(foo:(bar:baz))' }); state.fetch(); diff --git a/src/legacy/ui/public/state_management/__tests__/state.js b/src/legacy/ui/public/state_management/__tests__/state.js index ab70413c2d10e..cde123e6c1d85 100644 --- a/src/legacy/ui/public/state_management/__tests__/state.js +++ b/src/legacy/ui/public/state_management/__tests__/state.js @@ -46,18 +46,15 @@ describe('State Management', () => { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject(function(_$rootScope_, _$location_, Private, config) { + ngMock.inject(function (_$rootScope_, _$location_, Private, config) { const State = Private(StateProvider); $location = _$location_; $rootScope = _$rootScope_; Events = Private(EventsProvider); - setup = opts => { + setup = (opts) => { const { param, initial, storeInHash } = opts || {}; - sinon - .stub(config, 'get') - .withArgs('state:storeInSessionStorage') - .returns(!!storeInHash); + sinon.stub(config, 'get').withArgs('state:storeInSessionStorage').returns(!!storeInHash); const store = new StubBrowserStorage(); const hashedItemStore = new HashedItemStore(store); const state = new State(param, initial, hashedItemStore); @@ -89,9 +86,9 @@ describe('State Management', () => { expect(state).to.be.an(Events); }); - it('should emit an event if reset with changes', done => { + it('should emit an event if reset with changes', (done) => { const { state } = setup({ initial: { message: ['test'] } }); - state.on('reset_with_changes', keys => { + state.on('reset_with_changes', (keys) => { expect(keys).to.eql(['message']); done(); }); @@ -122,9 +119,9 @@ describe('State Management', () => { expect(search._s).to.equal('(test:foo)'); }); - it('should emit an event if changes are saved', done => { + it('should emit an event if changes are saved', (done) => { const { state, getUnhashedSearch } = setup(); - state.on('save_with_changes', keys => { + state.on('save_with_changes', (keys) => { expect(keys).to.eql(['test']); done(); }); @@ -136,9 +133,9 @@ describe('State Management', () => { }); describe('Fetch', () => { - it('should emit an event if changes are fetched', done => { + it('should emit an event if changes are fetched', (done) => { const { state } = setup(); - state.on('fetch_with_changes', keys => { + state.on('fetch_with_changes', (keys) => { expect(keys).to.eql(['foo']); done(); }); @@ -148,9 +145,9 @@ describe('State Management', () => { $rootScope.$apply(); }); - it('should have events that attach to scope', done => { + it('should have events that attach to scope', (done) => { const { state } = setup(); - state.on('test', message => { + state.on('test', (message) => { expect(message).to.equal('foo'); done(); }); @@ -158,9 +155,9 @@ describe('State Management', () => { $rootScope.$apply(); }); - it('should fire listeners for #onUpdate() on #fetch()', done => { + it('should fire listeners for #onUpdate() on #fetch()', (done) => { const { state } = setup(); - state.on('fetch_with_changes', keys => { + state.on('fetch_with_changes', (keys) => { expect(keys).to.eql(['foo']); done(); }); @@ -230,7 +227,7 @@ describe('State Management', () => { it('does not replace the state value on read', () => { const { state } = setup(); - sinon.stub($location, 'search').callsFake(newSearch => { + sinon.stub($location, 'search').callsFake((newSearch) => { if (newSearch) { return $location; } else { @@ -310,7 +307,7 @@ describe('State Management', () => { sinon.assert.calledOnce(fatalErrorStub); sinon.assert.calledWith( fatalErrorStub, - sinon.match(error => error instanceof Error && error.message.includes('github.com')) + sinon.match((error) => error instanceof Error && error.message.includes('github.com')) ); }); @@ -344,20 +341,17 @@ describe('State Management', () => { }; beforeEach( - ngMock.module('kibana', function(stateManagementConfigProvider) { + ngMock.module('kibana', function (stateManagementConfigProvider) { stateManagementConfigProvider.disable(); }) ); beforeEach( - ngMock.inject(function(_$rootScope_, _$location_, Private, config) { + ngMock.inject(function (_$rootScope_, _$location_, Private, config) { const State = Private(StateProvider); $location = _$location_; $rootScope = _$rootScope_; - sinon - .stub(config, 'get') - .withArgs('state:storeInSessionStorage') - .returns(false); + sinon.stub(config, 'get').withArgs('state:storeInSessionStorage').returns(false); class MockPersistedState extends State { _persistAcrossApps = true; @@ -372,7 +366,7 @@ describe('State Management', () => { describe('changing state', () => { const methods = ['save', 'replace', 'reset']; - methods.forEach(method => { + methods.forEach((method) => { it(`${method} should not change the URL`, () => { $location.search({ _s: '(foo:bar)' }); state[method](); diff --git a/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js b/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js index 601212d2da1a5..dc00d4e05e82f 100644 --- a/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js +++ b/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js @@ -23,7 +23,7 @@ import { EventEmitter } from 'events'; import { cloneDeep } from 'lodash'; import { stateMonitorFactory } from '../state_monitor_factory'; -describe('stateMonitorFactory', function() { +describe('stateMonitorFactory', function () { const noop = () => {}; const eventTypes = ['save_with_changes', 'reset_with_changes', 'fetch_with_changes']; @@ -44,27 +44,27 @@ describe('stateMonitorFactory', function() { mockState = createMockState({}); }); - it('should have a create method', function() { + it('should have a create method', function () { expect(stateMonitorFactory).to.have.property('create'); expect(stateMonitorFactory.create).to.be.a('function'); }); - describe('factory creation', function() { - it('should not call onChange with only the state', function() { + describe('factory creation', function () { + it('should not call onChange with only the state', function () { const monitor = stateMonitorFactory.create(mockState); const changeStub = sinon.stub(); monitor.onChange(changeStub); sinon.assert.notCalled(changeStub); }); - it('should not call onChange with matching defaultState', function() { + it('should not call onChange with matching defaultState', function () { const monitor = stateMonitorFactory.create(mockState, {}); const changeStub = sinon.stub(); monitor.onChange(changeStub); sinon.assert.notCalled(changeStub); }); - it('should call onChange with differing defaultState', function() { + it('should call onChange with differing defaultState', function () { const monitor = stateMonitorFactory.create(mockState, { test: true }); const changeStub = sinon.stub(); monitor.onChange(changeStub); @@ -72,21 +72,21 @@ describe('stateMonitorFactory', function() { }); }); - describe('instance', function() { + describe('instance', function () { let monitor; beforeEach(() => { monitor = stateMonitorFactory.create(mockState); }); - describe('onChange', function() { - it('should throw if not given a handler function', function() { + describe('onChange', function () { + it('should throw if not given a handler function', function () { const fn = () => monitor.onChange('not a function'); expect(fn).to.throwException(/must be a function/); }); - eventTypes.forEach(eventType => { - describe(`when ${eventType} is emitted`, function() { + eventTypes.forEach((eventType) => { + describe(`when ${eventType} is emitted`, function () { let handlerFn; beforeEach(() => { @@ -95,24 +95,24 @@ describe('stateMonitorFactory', function() { sinon.assert.notCalled(handlerFn); }); - it('should get called', function() { + it('should get called', function () { mockState.emit(eventType); sinon.assert.calledOnce(handlerFn); }); - it('should be given the state status', function() { + it('should be given the state status', function () { mockState.emit(eventType); const args = handlerFn.firstCall.args; expect(args[0]).to.be.an('object'); }); - it('should be given the event type', function() { + it('should be given the event type', function () { mockState.emit(eventType); const args = handlerFn.firstCall.args; expect(args[1]).to.equal(eventType); }); - it('should be given the changed keys', function() { + it('should be given the changed keys', function () { const keys = ['one', 'two', 'three']; mockState.emit(eventType, keys); const args = handlerFn.firstCall.args; @@ -122,8 +122,8 @@ describe('stateMonitorFactory', function() { }); }); - describe('ignoreProps', function() { - it('should not set status to dirty when ignored properties change', function() { + describe('ignoreProps', function () { + it('should not set status to dirty when ignored properties change', function () { let status; const mockState = createMockState({ messages: { world: 'hello', foo: 'bar' } }); const monitor = stateMonitorFactory.create(mockState); @@ -148,7 +148,7 @@ describe('stateMonitorFactory', function() { }); }); - describe('setInitialState', function() { + describe('setInitialState', function () { let changeStub; beforeEach(() => { @@ -157,22 +157,22 @@ describe('stateMonitorFactory', function() { sinon.assert.notCalled(changeStub); }); - it('should throw if no state is provided', function() { + it('should throw if no state is provided', function () { const fn = () => monitor.setInitialState(); expect(fn).to.throwException(/must be an object/); }); - it('should throw if given the wrong type', function() { + it('should throw if given the wrong type', function () { const fn = () => monitor.setInitialState([]); expect(fn).to.throwException(/must be an object/); }); - it('should trigger the onChange handler', function() { + it('should trigger the onChange handler', function () { monitor.setInitialState({ new: 'state' }); sinon.assert.calledOnce(changeStub); }); - it('should change the status with differing state', function() { + it('should change the status with differing state', function () { monitor.setInitialState({ new: 'state' }); sinon.assert.calledOnce(changeStub); @@ -181,13 +181,13 @@ describe('stateMonitorFactory', function() { expect(status).to.have.property('dirty', true); }); - it('should not trigger the onChange handler without state change', function() { + it('should not trigger the onChange handler without state change', function () { monitor.setInitialState(cloneDeep(mockState.toJSON())); sinon.assert.notCalled(changeStub); }); }); - describe('status object', function() { + describe('status object', function () { let handlerFn; beforeEach(() => { @@ -195,21 +195,21 @@ describe('stateMonitorFactory', function() { monitor.onChange(handlerFn); }); - it('should be clean by default', function() { + it('should be clean by default', function () { mockState.emit(eventTypes[0]); const status = handlerFn.firstCall.args[0]; expect(status).to.have.property('clean', true); expect(status).to.have.property('dirty', false); }); - it('should be dirty when state changes', function() { + it('should be dirty when state changes', function () { setState(mockState, { message: 'i am dirty now' }); const status = handlerFn.firstCall.args[0]; expect(status).to.have.property('clean', false); expect(status).to.have.property('dirty', true); }); - it('should be clean when state is reset', function() { + it('should be clean when state is reset', function () { const defaultState = { message: 'i am the original state' }; const handlerFn = sinon.stub(); @@ -237,7 +237,7 @@ describe('stateMonitorFactory', function() { }); }); - describe('destroy', function() { + describe('destroy', function () { let stateSpy; beforeEach(() => { @@ -245,16 +245,16 @@ describe('stateMonitorFactory', function() { sinon.assert.notCalled(stateSpy); }); - it('should remove the listeners', function() { + it('should remove the listeners', function () { monitor.onChange(noop); monitor.destroy(); sinon.assert.callCount(stateSpy, eventTypes.length); - eventTypes.forEach(eventType => { + eventTypes.forEach((eventType) => { sinon.assert.calledWith(stateSpy, eventType); }); }); - it('should stop the instance from being used any more', function() { + it('should stop the instance from being used any more', function () { monitor.onChange(noop); monitor.destroy(); const fn = () => monitor.onChange(noop); diff --git a/src/legacy/ui/public/state_management/app_state.js b/src/legacy/ui/public/state_management/app_state.js index 76675b05e0fe5..ec680d163b9da 100644 --- a/src/legacy/ui/public/state_management/app_state.js +++ b/src/legacy/ui/public/state_management/app_state.js @@ -58,17 +58,17 @@ export function AppStateProvider(Private, $location) { // if the url param is missing, write it back AppState.prototype._persistAcrossApps = false; - AppState.prototype.destroy = function() { + AppState.prototype.destroy = function () { AppState.Super.prototype.destroy.call(this); AppState.getAppState._set(null); - eventUnsubscribers.forEach(listener => listener()); + eventUnsubscribers.forEach((listener) => listener()); }; /** * @returns PersistedState instance. */ - AppState.prototype.makeStateful = function(prop) { + AppState.prototype.makeStateful = function (prop) { if (persistedStates[prop]) return persistedStates[prop]; const self = this; @@ -76,25 +76,25 @@ export function AppStateProvider(Private, $location) { persistedStates[prop] = new PersistedState(); // update the app state when the stateful instance changes - const updateOnChange = function() { + const updateOnChange = function () { const replaceState = false; // TODO: debouncing logic self[prop] = persistedStates[prop].getChanges(); // Save state to the URL. self.save(replaceState); }; - const handlerOnChange = method => persistedStates[prop][method]('change', updateOnChange); + const handlerOnChange = (method) => persistedStates[prop][method]('change', updateOnChange); handlerOnChange('on'); eventUnsubscribers.push(() => handlerOnChange('off')); // update the stateful object when the app state changes - const persistOnChange = function(changes) { + const persistOnChange = function (changes) { if (!changes) return; if (changes.indexOf(prop) !== -1) { persistedStates[prop].set(self[prop]); } }; - const handlePersist = method => this[method]('fetch_with_changes', persistOnChange); + const handlePersist = (method) => this[method]('fetch_with_changes', persistOnChange); handlePersist('on'); eventUnsubscribers.push(() => handlePersist('off')); @@ -104,7 +104,7 @@ export function AppStateProvider(Private, $location) { return persistedStates[prop]; }; - AppState.getAppState = (function() { + AppState.getAppState = (function () { let currentAppState; function get() { @@ -112,12 +112,12 @@ export function AppStateProvider(Private, $location) { } // Checks to see if the appState might already exist, even if it hasn't been newed up - get.previouslyStored = function() { + get.previouslyStored = function () { const search = $location.search(); return search[urlParam] ? true : false; }; - get._set = function(current) { + get._set = function (current) { currentAppState = current; }; @@ -129,9 +129,9 @@ export function AppStateProvider(Private, $location) { uiModules .get('kibana/global_state') - .factory('AppState', function(Private) { + .factory('AppState', function (Private) { return Private(AppStateProvider); }) - .service('getAppState', function(Private) { + .service('getAppState', function (Private) { return Private(AppStateProvider).getAppState; }); diff --git a/src/legacy/ui/public/state_management/global_state.js b/src/legacy/ui/public/state_management/global_state.js index d8ff38106b978..0e8dfe40d5950 100644 --- a/src/legacy/ui/public/state_management/global_state.js +++ b/src/legacy/ui/public/state_management/global_state.js @@ -37,6 +37,6 @@ export function GlobalStateProvider(Private) { return new GlobalState(); } -module.service('globalState', function(Private) { +module.service('globalState', function (Private) { return Private(GlobalStateProvider); }); diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index c2274eae59f50..93428e9f8fa4e 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -90,7 +90,7 @@ export function StateProvider( this.fetch(); } - State.prototype._readFromURL = function() { + State.prototype._readFromURL = function () { const search = $location.search(); const urlVal = search[this._urlParam]; @@ -139,7 +139,7 @@ export function StateProvider( * Fetches the state from the url * @returns {void} */ - State.prototype.fetch = function() { + State.prototype.fetch = function () { if (!stateManagementConfig.enabled) { return; } @@ -168,7 +168,7 @@ export function StateProvider( * Saves the state to the url * @returns {void} */ - State.prototype.save = function(replace) { + State.prototype.save = function (replace) { if (!stateManagementConfig.enabled) { return; } @@ -207,7 +207,7 @@ export function StateProvider( * Calls save with a forced replace * @returns {void} */ - State.prototype.replace = function() { + State.prototype.replace = function () { if (!stateManagementConfig.enabled) { return; } @@ -220,7 +220,7 @@ export function StateProvider( * * @returns {void} */ - State.prototype.reset = function() { + State.prototype.reset = function () { if (!stateManagementConfig.enabled) { return; } @@ -239,14 +239,14 @@ export function StateProvider( * Cleans up the state object * @returns {void} */ - State.prototype.destroy = function() { + State.prototype.destroy = function () { this.off(); // removes all listeners // Removes the $routeUpdate listener - this._cleanUpListeners.forEach(listener => listener(this)); + this._cleanUpListeners.forEach((listener) => listener(this)); }; - State.prototype.setDefaults = function(defaults) { + State.prototype.setDefaults = function (defaults) { this._defaults = defaults || {}; }; @@ -257,7 +257,7 @@ export function StateProvider( * @param {string} stateHash - state hash value from the query string. * @return {any} - the stored value, or null if hash does not resolve. */ - State.prototype._parseStateHash = function(stateHash) { + State.prototype._parseStateHash = function (stateHash) { const json = this._hashedItemStore.getItem(stateHash); if (json === null) { toastNotifications.addDanger( @@ -278,7 +278,7 @@ export function StateProvider( * @param {string} stateHashOrRison - either state hash value or rison string. * @return {string} rison */ - State.prototype.translateHashToRison = function(stateHashOrRison) { + State.prototype.translateHashToRison = function (stateHashOrRison) { if (isStateHash(stateHashOrRison)) { return rison.encode(this._parseStateHash(stateHashOrRison)); } @@ -286,7 +286,7 @@ export function StateProvider( return stateHashOrRison; }; - State.prototype.isHashingEnabled = function() { + State.prototype.isHashingEnabled = function () { return !!config.get('state:storeInSessionStorage'); }; @@ -295,7 +295,7 @@ export function StateProvider( * * @return {string} */ - State.prototype.toQueryParam = function(state = this.toObject()) { + State.prototype.toQueryParam = function (state = this.toObject()) { if (!this.isHashingEnabled()) { return rison.encode(state); } @@ -330,7 +330,7 @@ export function StateProvider( * Get the query string parameter name where this state writes and reads * @return {string} */ - State.prototype.getQueryParamName = function() { + State.prototype.getQueryParamName = function () { return this._urlParam; }; @@ -340,7 +340,7 @@ export function StateProvider( * * @return {object} */ - State.prototype.toObject = function() { + State.prototype.toObject = function () { return _.omit(this, (value, key) => { return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); }); @@ -351,7 +351,7 @@ export function StateProvider( * @obsolete Please use 'toObject' method instead * @return {object} */ - State.prototype.toJSON = function() { + State.prototype.toJSON = function () { return this.toObject(); }; diff --git a/src/legacy/ui/public/state_management/state_monitor_factory.ts b/src/legacy/ui/public/state_management/state_monitor_factory.ts index 27f3e59852569..454fefd4f8253 100644 --- a/src/legacy/ui/public/state_management/state_monitor_factory.ts +++ b/src/legacy/ui/public/state_management/state_monitor_factory.ts @@ -57,7 +57,7 @@ function stateMonitor( } function removeIgnoredProps(innerState: TStateDefault) { - ignoredProps.forEach(path => { + ignoredProps.forEach((path) => { set(innerState, path, true); }); return innerState; @@ -79,7 +79,7 @@ function stateMonitor( if (!changeHandlers) { throw new Error('Change handlers is undefined, this object has been destroyed'); } - changeHandlers.forEach(changeHandler => { + changeHandlers.forEach((changeHandler) => { changeHandler(status, type, keys); }); } diff --git a/src/legacy/ui/public/test_harness/test_harness.js b/src/legacy/ui/public/test_harness/test_harness.js index 22c78b1952e18..22c981fe0cf54 100644 --- a/src/legacy/ui/public/test_harness/test_harness.js +++ b/src/legacy/ui/public/test_harness/test_harness.js @@ -73,7 +73,7 @@ function createStubUiSettings() { createStubUiSettings(); sinon.stub(chrome, 'getUiSettingsClient').callsFake(() => stubUiSettings); -afterEach(function() { +afterEach(function () { createStubUiSettings(); }); diff --git a/src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js b/src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js index f830b8208aea3..53800d08ca05b 100644 --- a/src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js +++ b/src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js @@ -28,8 +28,8 @@ */ export function findTestBundleUrl() { const scriptTags = document.querySelectorAll('script[src]'); - const scriptUrls = [].map.call(scriptTags, el => el.getAttribute('src')); - const testBundleUrl = scriptUrls.find(url => url.includes('/tests.bundle.js')); + const scriptUrls = [].map.call(scriptTags, (el) => el.getAttribute('src')); + const testBundleUrl = scriptUrls.find((url) => url.includes('/tests.bundle.js')); if (!testBundleUrl) { throw new Error("test bundle url couldn't be found"); diff --git a/src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js b/src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js index 0093102f72390..fce1876162387 100644 --- a/src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js +++ b/src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js @@ -58,7 +58,7 @@ export function setupTestSharding() { }); // Filter top-level describe statements as they come - setupTopLevelDescribeFilter(describeName => { + setupTopLevelDescribeFilter((describeName) => { const describeShardNum = getShardNum(shardTotal, describeName); if (describeShardNum === shardNum) return true; // track shard numbers that we ignore diff --git a/src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js b/src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js index 9790b5367af81..726f890077b94 100644 --- a/src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js +++ b/src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js @@ -87,7 +87,7 @@ export function setupTopLevelDescribeFilter(test) { */ let describeCallDepth = 0; - const describeInterceptor = function(describeName, describeBody) { + const describeInterceptor = function (describeName, describeBody) { const context = this; const isTopLevelCall = describeCallDepth === 0; diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts index a7492e538b3af..7c25c6aa3166e 100644 --- a/src/legacy/ui/public/timefilter/setup_router.ts +++ b/src/legacy/ui/public/timefilter/setup_router.ts @@ -21,10 +21,15 @@ import _ from 'lodash'; import { IScope } from 'angular'; import moment from 'moment'; import chrome from 'ui/chrome'; -import { RefreshInterval, TimeRange, TimefilterContract } from 'src/plugins/data/public'; import { Subscription } from 'rxjs'; import { fatalError } from 'ui/notify/fatal_error'; import { subscribeWithScope } from '../../../../plugins/kibana_legacy/public'; +import { + RefreshInterval, + TimeRange, + TimefilterContract, + UI_SETTINGS, +} from '../../../../plugins/data/public'; // TODO // remove everything underneath once globalState is no longer an angular service @@ -38,7 +43,7 @@ export function getTimefilterConfig() { const settings = chrome.getUiSettingsClient(); return { timeDefaults: settings.get('timepicker:timeDefaults'), - refreshIntervalDefaults: settings.get('timepicker:refreshIntervalDefaults'), + refreshIntervalDefaults: settings.get(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS), }; } diff --git a/src/legacy/ui/public/url/__tests__/extract_app_path_and_id.js b/src/legacy/ui/public/url/__tests__/extract_app_path_and_id.js index 254ad3907e441..965e8f4bc9f38 100644 --- a/src/legacy/ui/public/url/__tests__/extract_app_path_and_id.js +++ b/src/legacy/ui/public/url/__tests__/extract_app_path_and_id.js @@ -21,100 +21,100 @@ import expect from '@kbn/expect'; import { extractAppPathAndId } from '../extract_app_path_and_id'; -describe('extractAppPathAndId', function() { - describe('from an absolute url with a base path', function() { - describe('with a base path', function() { +describe('extractAppPathAndId', function () { + describe('from an absolute url with a base path', function () { + describe('with a base path', function () { const basePath = '/gza'; const absoluteUrl = 'http://www.test.com:5601/gza/app/appId#appPathIsHere?query=here'; - it('extracts app path', function() { + it('extracts app path', function () { expect(extractAppPathAndId(absoluteUrl, basePath).appPath).to.be( 'appPathIsHere?query=here' ); }); - it('extracts app id', function() { + it('extracts app id', function () { expect(extractAppPathAndId(absoluteUrl, basePath).appId).to.be('appId'); }); - it('returns an empty object when there is no app path', function() { + it('returns an empty object when there is no app path', function () { const appPathAndId = extractAppPathAndId('http://www.test.com:5601/gza/noapppath'); expect(appPathAndId.appId).to.be(undefined); expect(appPathAndId.appPath).to.be(undefined); }); }); - describe('without a base path', function() { + describe('without a base path', function () { const absoluteUrl = 'http://www.test.com:5601/app/appId#appPathIsHere?query=here'; - it('extracts app path', function() { + it('extracts app path', function () { expect(extractAppPathAndId(absoluteUrl).appPath).to.be('appPathIsHere?query=here'); }); - it('extracts app id', function() { + it('extracts app id', function () { expect(extractAppPathAndId(absoluteUrl).appId).to.be('appId'); }); - it('returns an empty object when there is no app path', function() { + it('returns an empty object when there is no app path', function () { const appPathAndId = extractAppPathAndId('http://www.test.com:5601/noapppath'); expect(appPathAndId.appId).to.be(undefined); expect(appPathAndId.appPath).to.be(undefined); }); }); - describe('when appPath is empty', function() { + describe('when appPath is empty', function () { const absoluteUrl = 'http://www.test.com:5601/app/appId'; - it('extracts app id', function() { + it('extracts app id', function () { expect(extractAppPathAndId(absoluteUrl).appId).to.be('appId'); }); - it('extracts empty appPath', function() { + it('extracts empty appPath', function () { expect(extractAppPathAndId(absoluteUrl).appPath).to.be(''); }); }); }); - describe('from a root relative url', function() { - describe('with a base path', function() { + describe('from a root relative url', function () { + describe('with a base path', function () { const basePath = '/gza'; const rootRelativePath = '/gza/app/appId#appPathIsHere?query=here'; - it('extracts app path', function() { + it('extracts app path', function () { expect(extractAppPathAndId(rootRelativePath, basePath).appPath).to.be( 'appPathIsHere?query=here' ); }); - it('extracts app id', function() { + it('extracts app id', function () { expect(extractAppPathAndId(rootRelativePath, basePath).appId).to.be('appId'); }); - it('returns an empty object when there is no app path', function() { + it('returns an empty object when there is no app path', function () { const appPathAndId = extractAppPathAndId('/gza/notformattedright'); expect(appPathAndId.appId).to.be(undefined); expect(appPathAndId.appPath).to.be(undefined); }); }); - describe('without a base path', function() { + describe('without a base path', function () { const rootRelativePath = '/app/appId#appPathIsHere?query=here'; - it('extracts app path', function() { + it('extracts app path', function () { expect(extractAppPathAndId(rootRelativePath).appPath).to.be('appPathIsHere?query=here'); }); - it('extracts app id', function() { + it('extracts app id', function () { expect(extractAppPathAndId(rootRelativePath).appId).to.be('appId'); }); - it('returns an empty object when there is no app path', function() { + it('returns an empty object when there is no app path', function () { const appPathAndId = extractAppPathAndId('/notformattedright'); expect(appPathAndId.appId).to.be(undefined); expect(appPathAndId.appPath).to.be(undefined); }); }); - describe('when appPath is empty', function() { + describe('when appPath is empty', function () { const rootRelativePath = '/app/appId'; - it('extracts app id', function() { + it('extracts app id', function () { expect(extractAppPathAndId(rootRelativePath).appId).to.be('appId'); }); - it('extracts empty appPath', function() { + it('extracts empty appPath', function () { expect(extractAppPathAndId(rootRelativePath).appPath).to.be(''); }); }); diff --git a/src/legacy/ui/public/url/__tests__/kibana_parsed_url.js b/src/legacy/ui/public/url/__tests__/kibana_parsed_url.js index 422b1318bc82c..6ea199c3d22cc 100644 --- a/src/legacy/ui/public/url/__tests__/kibana_parsed_url.js +++ b/src/legacy/ui/public/url/__tests__/kibana_parsed_url.js @@ -21,8 +21,8 @@ import expect from '@kbn/expect'; import { KibanaParsedUrl } from '../kibana_parsed_url'; -describe('KibanaParsedUrl', function() { - it('getHashedAppPath', function() { +describe('KibanaParsedUrl', function () { + it('getHashedAppPath', function () { const kibanaParsedUrl = new KibanaParsedUrl({ basePath: '/hi', appId: 'bye', @@ -31,7 +31,7 @@ describe('KibanaParsedUrl', function() { expect(kibanaParsedUrl.getHashedAppPath()).to.be('#visualize?hi=there&bye'); }); - it('getAppRootPath', function() { + it('getAppRootPath', function () { const kibanaParsedUrl = new KibanaParsedUrl({ basePath: '/hi', appId: 'appId', @@ -40,8 +40,8 @@ describe('KibanaParsedUrl', function() { expect(kibanaParsedUrl.getAppRootPath()).to.be('/app/appId#dashboard?edit=123'); }); - describe('when basePath is specified', function() { - it('getRootRelativePath', function() { + describe('when basePath is specified', function () { + it('getRootRelativePath', function () { const kibanaParsedUrl = new KibanaParsedUrl({ basePath: '/base', appId: 'appId', @@ -50,12 +50,12 @@ describe('KibanaParsedUrl', function() { expect(kibanaParsedUrl.getRootRelativePath()).to.be('/base/app/appId#visualize?hi=there&bye'); }); - describe('getAbsolutePath', function() { + describe('getAbsolutePath', function () { const protocol = 'http'; const hostname = 'www.test.com'; const port = '5601'; - it('returns the absolute url when there is a port', function() { + it('returns the absolute url when there is a port', function () { const kibanaParsedUrl = new KibanaParsedUrl({ basePath: '/base', appId: 'appId', @@ -69,7 +69,7 @@ describe('KibanaParsedUrl', function() { ); }); - it('returns the absolute url when there are no query parameters', function() { + it('returns the absolute url when there are no query parameters', function () { const kibanaParsedUrl = new KibanaParsedUrl({ basePath: '/base', appId: 'appId', @@ -82,7 +82,7 @@ describe('KibanaParsedUrl', function() { ); }); - it('returns the absolute url when the are query parameters', function() { + it('returns the absolute url when the are query parameters', function () { const kibanaParsedUrl = new KibanaParsedUrl({ basePath: '/base', appId: 'appId', @@ -97,8 +97,8 @@ describe('KibanaParsedUrl', function() { }); }); - describe('when basePath is not specified', function() { - it('getRootRelativePath', function() { + describe('when basePath is not specified', function () { + it('getRootRelativePath', function () { const kibanaParsedUrl = new KibanaParsedUrl({ appId: 'appId', appPath: 'visualize?hi=there&bye', @@ -106,12 +106,12 @@ describe('KibanaParsedUrl', function() { expect(kibanaParsedUrl.getRootRelativePath()).to.be('/app/appId#visualize?hi=there&bye'); }); - describe('getAbsolutePath', function() { + describe('getAbsolutePath', function () { const protocol = 'http'; const hostname = 'www.test.com'; const port = '5601'; - it('returns the absolute url when there is a port', function() { + it('returns the absolute url when there is a port', function () { const kibanaParsedUrl = new KibanaParsedUrl({ appId: 'appId', appPath: 'visualize?hi=there&bye', @@ -124,7 +124,7 @@ describe('KibanaParsedUrl', function() { ); }); - it('returns the absolute url when there are no query parameters', function() { + it('returns the absolute url when there are no query parameters', function () { const kibanaParsedUrl = new KibanaParsedUrl({ appId: 'appId', appPath: 'visualize', @@ -134,7 +134,7 @@ describe('KibanaParsedUrl', function() { expect(kibanaParsedUrl.getAbsoluteUrl()).to.be('http://www.test.com/app/appId#visualize'); }); - it('returns the absolute url when there are query parameters', function() { + it('returns the absolute url when there are query parameters', function () { const kibanaParsedUrl = new KibanaParsedUrl({ appId: 'appId', appPath: 'visualize?hi=bye&tata', @@ -148,21 +148,21 @@ describe('KibanaParsedUrl', function() { }); }); - describe('getGlobalState', function() { + describe('getGlobalState', function () { const basePath = '/xyz'; const appId = 'myApp'; - it('returns an empty string when the KibanaParsedUrl is in an invalid state', function() { + it('returns an empty string when the KibanaParsedUrl is in an invalid state', function () { const url = new KibanaParsedUrl({ basePath }); expect(url.getGlobalState()).to.be(''); }); - it('returns an empty string when there is no global state', function() { + it('returns an empty string when there is no global state', function () { const url = new KibanaParsedUrl({ basePath, appId, appPath: '/hi?notg=something' }); expect(url.getGlobalState()).to.be(''); }); - it('returns the global state when it is the last parameter', function() { + it('returns the global state when it is the last parameter', function () { const url = new KibanaParsedUrl({ basePath, appId, @@ -171,7 +171,7 @@ describe('KibanaParsedUrl', function() { expect(url.getGlobalState()).to.be('(thisismyglobalstate)'); }); - it('returns the global state when it is the first parameter', function() { + it('returns the global state when it is the first parameter', function () { const url = new KibanaParsedUrl({ basePath, appId, @@ -181,23 +181,23 @@ describe('KibanaParsedUrl', function() { }); }); - describe('setGlobalState', function() { + describe('setGlobalState', function () { const basePath = '/xyz'; const appId = 'myApp'; - it('does nothing when KibanaParsedUrl is in an invalid state', function() { + it('does nothing when KibanaParsedUrl is in an invalid state', function () { const url = new KibanaParsedUrl({ basePath }); url.setGlobalState('newglobalstate'); expect(url.getGlobalState()).to.be(''); }); - it('clears the global state when setting it to an empty string', function() { + it('clears the global state when setting it to an empty string', function () { const url = new KibanaParsedUrl({ basePath, appId, appPath: '/hi?_g=globalstate' }); url.setGlobalState(''); expect(url.getGlobalState()).to.be(''); }); - it('updates the global state when a string is passed in', function() { + it('updates the global state when a string is passed in', function () { const url = new KibanaParsedUrl({ basePath, appId, @@ -207,7 +207,7 @@ describe('KibanaParsedUrl', function() { expect(url.getGlobalState()).to.be('newstate'); }); - it('adds the global state parameters if it did not exist before', function() { + it('adds the global state parameters if it did not exist before', function () { const url = new KibanaParsedUrl({ basePath, appId, appPath: '/hi' }); url.setGlobalState('newstate'); expect(url.getGlobalState()).to.be('newstate'); diff --git a/src/legacy/ui/public/url/__tests__/prepend_path.js b/src/legacy/ui/public/url/__tests__/prepend_path.js index 488a16e17410e..36991b77553e4 100644 --- a/src/legacy/ui/public/url/__tests__/prepend_path.js +++ b/src/legacy/ui/public/url/__tests__/prepend_path.js @@ -21,59 +21,59 @@ import expect from '@kbn/expect'; import { prependPath } from '../prepend_path'; -describe('url prependPath', function() { - describe('returns the relative path unchanged', function() { - it('if it is null', function() { +describe('url prependPath', function () { + describe('returns the relative path unchanged', function () { + it('if it is null', function () { expect(prependPath(null, 'kittens')).to.be(null); }); - it('if it is undefined', function() { + it('if it is undefined', function () { expect(prependPath(undefined, 'kittens')).to.be(undefined); }); - it('if it is an absolute url', function() { + it('if it is an absolute url', function () { expect(prependPath('http://www.hithere.com/howareyou', 'welcome')).to.be( 'http://www.hithere.com/howareyou' ); }); - it('if it does not start with a /', function() { + it('if it does not start with a /', function () { expect(prependPath('are/so/cool', 'cats')).to.be('are/so/cool'); }); - it('when new path is empty', function() { + it('when new path is empty', function () { expect(prependPath('/are/so/cool', '')).to.be('/are/so/cool'); }); - it('when it is only a slash and new path is empty', function() { + it('when it is only a slash and new path is empty', function () { expect(prependPath('/', '')).to.be('/'); }); }); - describe('returns an updated relative path', function() { - it('when it starts with a slash', function() { + describe('returns an updated relative path', function () { + it('when it starts with a slash', function () { expect(prependPath('/are/so/cool', 'dinosaurs')).to.be('dinosaurs/are/so/cool'); }); - it('when new path starts with a slash', function() { + it('when new path starts with a slash', function () { expect(prependPath('/are/so/cool', '/fish')).to.be('/fish/are/so/cool'); }); - it('with two slashes if new path is a slash', function() { + it('with two slashes if new path is a slash', function () { expect(prependPath('/are/so/cool', '/')).to.be('//are/so/cool'); }); - it('when there is a slash on the end', function() { + it('when there is a slash on the end', function () { expect(prependPath('/are/delicious/', 'lollipops')).to.be('lollipops/are/delicious/'); }); - it('when pathname that ends with a file', function() { + it('when pathname that ends with a file', function () { expect(prependPath('/are/delicious/index.html', 'donuts')).to.be( 'donuts/are/delicious/index.html' ); }); - it('when it is only a slash', function() { + it('when it is only a slash', function () { expect(prependPath('/', 'kittens')).to.be('kittens/'); }); }); diff --git a/src/legacy/ui/public/url/__tests__/url.js b/src/legacy/ui/public/url/__tests__/url.js index 999bef24ac5e2..8b173482e1bb4 100644 --- a/src/legacy/ui/public/url/__tests__/url.js +++ b/src/legacy/ui/public/url/__tests__/url.js @@ -41,22 +41,22 @@ class StubAppState { } function init() { - ngMock.module('kibana/url', 'kibana', function($provide, PrivateProvider) { - $provide.service('$route', function() { + ngMock.module('kibana/url', 'kibana', function ($provide, PrivateProvider) { + $provide.service('$route', function () { return { reload: _.noop, }; }); appState = new StubAppState(); - PrivateProvider.swap(AppStateProvider, $decorate => { + PrivateProvider.swap(AppStateProvider, ($decorate) => { const AppState = $decorate(); AppState.getAppState = () => appState; return AppState; }); }); - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $route = $injector.get('$route'); $location = $injector.get('$location'); $rootScope = $injector.get('$rootScope'); @@ -64,13 +64,13 @@ function init() { }); } -describe('kbnUrl', function() { - beforeEach(function() { +describe('kbnUrl', function () { + beforeEach(function () { init(); }); - describe('forcing reload', function() { - it('schedules a listener for $locationChangeSuccess on the $rootScope', function() { + describe('forcing reload', function () { + it('schedules a listener for $locationChangeSuccess on the $rootScope', function () { $location.url('/url'); $route.current = { $$route: { @@ -87,7 +87,7 @@ describe('kbnUrl', function() { expect($rootScope.$on.firstCall.args[0]).to.be('$locationChangeSuccess'); }); - it('handler unbinds the listener and calls reload', function() { + it('handler unbinds the listener and calls reload', function () { $location.url('/url'); $route.current = { $$route: { @@ -109,7 +109,7 @@ describe('kbnUrl', function() { expect($route.reload.callCount).to.be(1); }); - it('reloads requested before the first are ignored', function() { + it('reloads requested before the first are ignored', function () { $location.url('/url'); $route.current = { $$route: { @@ -130,7 +130,7 @@ describe('kbnUrl', function() { expect($rootScope.$on.callCount).to.be(1); }); - it('one reload can happen once the first has completed', function() { + it('one reload can happen once the first has completed', function () { $location.url('/url'); $route.current = { $$route: { @@ -155,34 +155,34 @@ describe('kbnUrl', function() { }); }); - describe('remove', function() { - it('removes a parameter with a value from the url', function() { + describe('remove', function () { + it('removes a parameter with a value from the url', function () { $location.url('/myurl?exist&WithAParamToRemove=2&anothershouldexist=5'); kbnUrl.removeParam('WithAParamToRemove'); expect($location.url()).to.be('/myurl?exist&anothershouldexist=5'); }); - it('removes a parameter with no value from the url', function() { + it('removes a parameter with no value from the url', function () { $location.url('/myurl?removeme&hi=5'); kbnUrl.removeParam('removeme'); expect($location.url()).to.be('/myurl?hi=5'); }); - it('is noop if the given parameter doesn\t exist in the url', function() { + it('is noop if the given parameter doesn\t exist in the url', function () { $location.url('/myurl?hi&bye'); kbnUrl.removeParam('noexist'); expect($location.url()).to.be('/myurl?hi&bye'); }); - it('is noop if given empty string param', function() { + it('is noop if given empty string param', function () { $location.url('/myurl?hi&bye'); kbnUrl.removeParam(''); expect($location.url()).to.be('/myurl?hi&bye'); }); }); - describe('change', function() { - it('should set $location.url', function() { + describe('change', function () { + it('should set $location.url', function () { sinon.stub($location, 'url'); expect($location.url.callCount).to.be(0); @@ -190,7 +190,7 @@ describe('kbnUrl', function() { expect($location.url.callCount).to.be(1); }); - it('should uri encode replaced params', function() { + it('should uri encode replaced params', function () { const url = '/some/path/'; const params = { replace: faker.Lorem.words(3).join(' ') }; const check = encodeURIComponent(params.replace); @@ -201,7 +201,7 @@ describe('kbnUrl', function() { expect($location.url.firstCall.args[0]).to.be(url + check); }); - it('should parse angular expression in substitutions and uri encode the results', function() { + it('should parse angular expression in substitutions and uri encode the results', function () { // build url by piecing together these parts const urlParts = ['/', '/', '?', '&', '#']; // make sure it can parse templates with weird spacing @@ -219,14 +219,14 @@ describe('kbnUrl', function() { // the words (template keys) used must all be unique const words = _.uniq(faker.Lorem.words(10)) .slice(0, urlParts.length) - .map(function(word, i) { + .map(function (word, i) { if (filters[i].length) { return word + '|' + filters[i]; } return word; }); - const replacements = faker.Lorem.words(urlParts.length).map(function(word, i) { + const replacements = faker.Lorem.words(urlParts.length).map(function (word, i) { // make selected replacement into an object if (i === objIndex) { return { replace: word }; @@ -238,7 +238,7 @@ describe('kbnUrl', function() { // build the url and test url let url = ''; let testUrl = ''; - urlParts.forEach(function(part, i) { + urlParts.forEach(function (part, i) { url += part + wrappers[i][0] + words[i] + wrappers[i][1]; const locals = {}; locals[words[i].split('|')[0]] = replacements[i]; @@ -247,7 +247,7 @@ describe('kbnUrl', function() { // create the locals replacement object const params = {}; - replacements.forEach(function(replacement, i) { + replacements.forEach(function (replacement, i) { const word = words[i].split('|')[0]; params[word] = replacement; }); @@ -260,7 +260,7 @@ describe('kbnUrl', function() { expect($location.url.firstCall.args[0]).to.be(testUrl); }); - it('should handle dot notation', function() { + it('should handle dot notation', function () { const url = '/some/thing/{{that.is.substituted}}'; kbnUrl.change(url, { @@ -274,7 +274,7 @@ describe('kbnUrl', function() { expect($location.url()).to.be('/some/thing/test'); }); - it('should throw when params are missing', function() { + it('should throw when params are missing', function () { const url = '/{{replace_me}}'; const params = {}; @@ -287,7 +287,7 @@ describe('kbnUrl', function() { } }); - it('should throw when filtered params are missing', function() { + it('should throw when filtered params are missing', function () { const url = '/{{replace_me|number}}'; const params = {}; @@ -300,16 +300,13 @@ describe('kbnUrl', function() { } }); - it('should change the entire url', function() { + it('should change the entire url', function () { const path = '/test/path'; const search = { search: 'test' }; const hash = 'hash'; const newPath = '/new/location'; - $location - .path(path) - .search(search) - .hash(hash); + $location.path(path).search(search).hash(hash); // verify the starting state expect($location.path()).to.be(path); @@ -324,16 +321,13 @@ describe('kbnUrl', function() { expect($location.hash()).to.be(''); }); - it('should allow setting app state on the target url', function() { + it('should allow setting app state on the target url', function () { const path = '/test/path'; const search = { search: 'test' }; const hash = 'hash'; const newPath = '/new/location'; - $location - .path(path) - .search(search) - .hash(hash); + $location.path(path).search(search).hash(hash); // verify the starting state expect($location.path()).to.be(path); @@ -349,17 +343,14 @@ describe('kbnUrl', function() { }); }); - describe('changePath', function() { - it('should change just the path', function() { + describe('changePath', function () { + it('should change just the path', function () { const path = '/test/path'; const search = { search: 'test' }; const hash = 'hash'; const newPath = '/new/location'; - $location - .path(path) - .search(search) - .hash(hash); + $location.path(path).search(search).hash(hash); // verify the starting state expect($location.path()).to.be(path); @@ -375,17 +366,14 @@ describe('kbnUrl', function() { }); }); - describe('redirect', function() { - it('should change the entire url', function() { + describe('redirect', function () { + it('should change the entire url', function () { const path = '/test/path'; const search = { search: 'test' }; const hash = 'hash'; const newPath = '/new/location'; - $location - .path(path) - .search(search) - .hash(hash); + $location.path(path).search(search).hash(hash); // verify the starting state expect($location.path()).to.be(path); @@ -400,16 +388,13 @@ describe('kbnUrl', function() { expect($location.hash()).to.be(''); }); - it('should allow setting app state on the target url', function() { + it('should allow setting app state on the target url', function () { const path = '/test/path'; const search = { search: 'test' }; const hash = 'hash'; const newPath = '/new/location'; - $location - .path(path) - .search(search) - .hash(hash); + $location.path(path).search(search).hash(hash); // verify the starting state expect($location.path()).to.be(path); @@ -424,7 +409,7 @@ describe('kbnUrl', function() { expect($location.hash()).to.be(''); }); - it('should replace the current history entry', function() { + it('should replace the current history entry', function () { sinon.stub($location, 'replace'); $location.url('/some/path'); @@ -433,7 +418,7 @@ describe('kbnUrl', function() { expect($location.replace.callCount).to.be(1); }); - it('should call replace on $location', function() { + it('should call replace on $location', function () { sinon.stub(kbnUrl, '_shouldForceReload').returns(false); sinon.stub($location, 'replace'); @@ -443,17 +428,14 @@ describe('kbnUrl', function() { }); }); - describe('redirectPath', function() { - it('should only change the path', function() { + describe('redirectPath', function () { + it('should only change the path', function () { const path = '/test/path'; const search = { search: 'test' }; const hash = 'hash'; const newPath = '/new/location'; - $location - .path(path) - .search(search) - .hash(hash); + $location.path(path).search(search).hash(hash); // verify the starting state expect($location.path()).to.be(path); @@ -468,7 +450,7 @@ describe('kbnUrl', function() { expect($location.hash()).to.be(hash); }); - it('should call replace on $location', function() { + it('should call replace on $location', function () { sinon.stub(kbnUrl, '_shouldForceReload').returns(false); sinon.stub($location, 'replace'); @@ -478,11 +460,11 @@ describe('kbnUrl', function() { }); }); - describe('_shouldForceReload', function() { + describe('_shouldForceReload', function () { let next; let prev; - beforeEach(function() { + beforeEach(function () { $route.current = { $$route: { regexp: /^\/is-current-route\/(\d+)/, @@ -494,21 +476,21 @@ describe('kbnUrl', function() { next = { path: '/is-current-route/1', search: {} }; }); - it("returns false if the passed url doesn't match the current route", function() { + it("returns false if the passed url doesn't match the current route", function () { next.path = '/not current'; expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); }); - describe('if the passed url does match the route', function() { - describe('and the route reloads on search', function() { - describe('and the path is the same', function() { - describe('and the search params are the same', function() { - it('returns true', function() { + describe('if the passed url does match the route', function () { + describe('and the route reloads on search', function () { + describe('and the path is the same', function () { + describe('and the search params are the same', function () { + it('returns true', function () { expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(true); }); }); - describe('but the search params are different', function() { - it('returns false', function() { + describe('but the search params are different', function () { + it('returns false', function () { next.search = {}; prev.search = { q: 'search term' }; expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); @@ -516,18 +498,18 @@ describe('kbnUrl', function() { }); }); - describe('and the path is different', function() { - beforeEach(function() { + describe('and the path is different', function () { + beforeEach(function () { next.path = '/not-same'; }); - describe('and the search params are the same', function() { - it('returns false', function() { + describe('and the search params are the same', function () { + it('returns false', function () { expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); }); }); - describe('but the search params are different', function() { - it('returns false', function() { + describe('but the search params are different', function () { + it('returns false', function () { next.search = {}; prev.search = { q: 'search term' }; expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); @@ -536,19 +518,19 @@ describe('kbnUrl', function() { }); }); - describe('but the route does not reload on search', function() { - beforeEach(function() { + describe('but the route does not reload on search', function () { + beforeEach(function () { $route.current.$$route.reloadOnSearch = false; }); - describe('and the path is the same', function() { - describe('and the search params are the same', function() { - it('returns true', function() { + describe('and the path is the same', function () { + describe('and the search params are the same', function () { + it('returns true', function () { expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(true); }); }); - describe('but the search params are different', function() { - it('returns true', function() { + describe('but the search params are different', function () { + it('returns true', function () { next.search = {}; prev.search = { q: 'search term' }; expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(true); @@ -556,18 +538,18 @@ describe('kbnUrl', function() { }); }); - describe('and the path is different', function() { - beforeEach(function() { + describe('and the path is different', function () { + beforeEach(function () { next.path = '/not-same'; }); - describe('and the search params are the same', function() { - it('returns false', function() { + describe('and the search params are the same', function () { + it('returns false', function () { expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); }); }); - describe('but the search params are different', function() { - it('returns false', function() { + describe('but the search params are different', function () { + it('returns false', function () { next.search = {}; prev.search = { q: 'search term' }; expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); diff --git a/src/legacy/ui/public/url/kibana_parsed_url.ts b/src/legacy/ui/public/url/kibana_parsed_url.ts index 712dfd119c615..1c60e8729e0ff 100644 --- a/src/legacy/ui/public/url/kibana_parsed_url.ts +++ b/src/legacy/ui/public/url/kibana_parsed_url.ts @@ -111,13 +111,13 @@ export class KibanaParsedUrl { return; } - this.appPath = modifyUrl(this.appPath, parsed => { + this.appPath = modifyUrl(this.appPath, (parsed) => { parsed.query._g = newGlobalState; }); } public addQueryParameter(name: string, val: string) { - this.appPath = modifyUrl(this.appPath, parsed => { + this.appPath = modifyUrl(this.appPath, (parsed) => { parsed.query[name] = val; }); } @@ -139,7 +139,7 @@ export class KibanaParsedUrl { } public getAbsoluteUrl() { - return modifyUrl(this.getRootRelativePath(), parsed => { + return modifyUrl(this.getRootRelativePath(), (parsed) => { parsed.protocol = this.protocol; parsed.port = this.port; parsed.hostname = this.hostname; diff --git a/src/legacy/ui/public/url/redirect_when_missing.js b/src/legacy/ui/public/url/redirect_when_missing.js index e6b4a488160dd..85c90a14d9fd7 100644 --- a/src/legacy/ui/public/url/redirect_when_missing.js +++ b/src/legacy/ui/public/url/redirect_when_missing.js @@ -24,7 +24,7 @@ import { toastNotifications } from 'ui/notify'; import { SavedObjectNotFound } from '../../../../plugins/kibana_utils/public'; import { uiModules } from '../modules'; -uiModules.get('kibana/url').service('redirectWhenMissing', function(Private) { +uiModules.get('kibana/url').service('redirectWhenMissing', function (Private) { return Private(RedirectWhenMissingProvider); }); @@ -37,12 +37,12 @@ export function RedirectWhenMissingProvider(kbnUrl, Promise) { * couldn't be found, or just a string that will be used for all types * @return {function} - the handler to pass to .catch() */ - return function(mapping) { + return function (mapping) { if (typeof mapping === 'string') { mapping = { '*': mapping }; } - return function(error) { + return function (error) { // if this error is not "404", rethrow const savedObjectNotFound = error instanceof SavedObjectNotFound; const unknownVisType = error.message.indexOf('Invalid type') === 0; diff --git a/src/legacy/ui/public/url/url.js b/src/legacy/ui/public/url/url.js index 8c854950784c4..fb243b02e05c7 100644 --- a/src/legacy/ui/public/url/url.js +++ b/src/legacy/ui/public/url/url.js @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { uiModules } from '../modules'; import { AppStateProvider } from '../state_management/app_state'; -uiModules.get('kibana/url').service('kbnUrl', function(Private, $injector) { +uiModules.get('kibana/url').service('kbnUrl', function (Private, $injector) { //config is not directly used but registers global event listeners to kbnUrl to function $injector.get('config'); return Private(KbnUrlProvider); @@ -56,7 +56,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {Object} [paramObj] - optional set of parameters for the url template * @return {undefined} */ - self.change = function(url, paramObj, appState) { + self.change = function (url, paramObj, appState) { self._changeLocation('url', url, paramObj, false, appState); }; @@ -68,7 +68,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {Object} [paramObj] - optional set of parameters for the path template * @return {undefined} */ - self.changePath = function(path, paramObj) { + self.changePath = function (path, paramObj) { self._changeLocation('path', path, paramObj); }; @@ -79,7 +79,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {Object} [paramObj] - optional set of parameters for the url template * @return {undefined} */ - self.redirect = function(url, paramObj, appState) { + self.redirect = function (url, paramObj, appState) { self._changeLocation('url', url, paramObj, true, appState); }; @@ -91,7 +91,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {Object} [paramObj] - optional set of parameters for the path template * @return {undefined} */ - self.redirectPath = function(path, paramObj) { + self.redirectPath = function (path, paramObj) { self._changeLocation('path', path, paramObj, true); }; @@ -104,10 +104,10 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @return {String} - the evaluated result * @throws {Error} If any of the expressions can't be parsed. */ - self.eval = function(template, paramObj) { + self.eval = function (template, paramObj) { paramObj = paramObj || {}; - return template.replace(/\{\{([^\}]+)\}\}/g, function(match, expr) { + return template.replace(/\{\{([^\}]+)\}\}/g, function (match, expr) { // remove filters const key = expr.split('|')[0].trim(); @@ -136,7 +136,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {string} route - the route name * @return {string} - the computed href */ - self.getRouteHref = function(obj, route) { + self.getRouteHref = function (obj, route) { return '#' + self.getRouteUrl(obj, route); }; @@ -147,7 +147,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {string} route - the route name * @return {string} - the computed url */ - self.getRouteUrl = function(obj, route) { + self.getRouteUrl = function (obj, route) { const template = obj && obj.routes && obj.routes[route]; if (template) return self.eval(template, obj); }; @@ -160,7 +160,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {string} route - the route name * @return {undefined} */ - self.redirectToRoute = function(obj, route) { + self.redirectToRoute = function (obj, route) { self.redirect(self.getRouteUrl(obj, route)); }; @@ -172,7 +172,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * @param {string} route - the route name * @return {undefined} */ - self.changeToRoute = function(obj, route) { + self.changeToRoute = function (obj, route) { self.change(self.getRouteUrl(obj, route)); }; @@ -181,7 +181,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private * history. * @param param */ - self.removeParam = function(param) { + self.removeParam = function (param) { $location.search(param, null).replace(); }; @@ -190,7 +190,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private ///// let reloading; - self._changeLocation = function(type, url, paramObj, replace, appState) { + self._changeLocation = function (type, url, paramObj, replace, appState) { const prev = { path: $location.path(), search: $location.search(), @@ -216,7 +216,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private const appState = Private(AppStateProvider).getAppState(); if (appState) appState.destroy(); - reloading = $rootScope.$on('$locationChangeSuccess', function() { + reloading = $rootScope.$on('$locationChangeSuccess', function () { // call the "unlisten" function returned by $on reloading(); reloading = false; @@ -228,7 +228,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private }; // determine if the router will automatically reload the route - self._shouldForceReload = function(next, prev, $route) { + self._shouldForceReload = function (next, prev, $route) { if (reloading) return false; const route = $route.current && $route.current.$$route; diff --git a/src/legacy/ui/public/utils/legacy_class.js b/src/legacy/ui/public/utils/legacy_class.js index 0aebdd4f5b667..f47650a77bb6d 100644 --- a/src/legacy/ui/public/utils/legacy_class.js +++ b/src/legacy/ui/public/utils/legacy_class.js @@ -29,7 +29,7 @@ function describeConst(val) { } const props = { - inherits: describeConst(function(SuperClass) { + inherits: describeConst(function (SuperClass) { const prototype = Object.create(SuperClass.prototype, { constructor: describeConst(this), superConstructor: describeConst(SuperClass), diff --git a/src/legacy/ui/ui_apps/__tests__/ui_app.js b/src/legacy/ui/ui_apps/__tests__/ui_app.js index 50cefd7032e11..bb4bcfe2d7443 100644 --- a/src/legacy/ui/ui_apps/__tests__/ui_app.js +++ b/src/legacy/ui/ui_apps/__tests__/ui_app.js @@ -41,10 +41,7 @@ function createStubKbnServer(extraParams) { return { plugins: [], config: { - get: sinon - .stub() - .withArgs('server.basePath') - .returns(''), + get: sinon.stub().withArgs('server.basePath').returns(''), }, server: {}, ...extraParams, @@ -288,7 +285,7 @@ describe('ui apps / UiApp', () => { it('throws an error at instantiation', () => { expect(() => { createUiApp(createStubUiAppSpec({ pluginId: 'foo' })); - }).to.throwException(error => { + }).to.throwException((error) => { expect(error.message).to.match(/Unknown plugin id/); }); }); diff --git a/src/legacy/ui/ui_apps/ui_app.js b/src/legacy/ui/ui_apps/ui_app.js index 926db5836e9d1..7da9e39394deb 100644 --- a/src/legacy/ui/ui_apps/ui_app.js +++ b/src/legacy/ui/ui_apps/ui_app.js @@ -109,7 +109,7 @@ export class UiApp { const pluginId = this._pluginId; const { plugins } = this._kbnServer; - return pluginId ? plugins.find(plugin => plugin.id === pluginId) : undefined; + return pluginId ? plugins.find((plugin) => plugin.id === pluginId) : undefined; } toJSON() { diff --git a/src/legacy/ui/ui_apps/ui_apps_mixin.js b/src/legacy/ui/ui_apps/ui_apps_mixin.js index 5165c9859fec5..c80b12a46bee3 100644 --- a/src/legacy/ui/ui_apps/ui_apps_mixin.js +++ b/src/legacy/ui/ui_apps/ui_apps_mixin.js @@ -34,7 +34,7 @@ export function uiAppsMixin(kbnServer, server) { const appsById = new Map(); const hiddenAppsById = new Map(); - kbnServer.uiApps = uiAppSpecs.map(spec => { + kbnServer.uiApps = uiAppSpecs.map((spec) => { const app = new UiApp(kbnServer, spec); const id = app.getId(); @@ -54,14 +54,14 @@ export function uiAppsMixin(kbnServer, server) { }); server.decorate('server', 'getAllUiApps', () => kbnServer.uiApps.slice(0)); - server.decorate('server', 'getUiAppById', id => appsById.get(id)); - server.decorate('server', 'getHiddenUiAppById', id => hiddenAppsById.get(id)); + server.decorate('server', 'getUiAppById', (id) => appsById.get(id)); + server.decorate('server', 'getHiddenUiAppById', (id) => hiddenAppsById.get(id)); server.decorate('server', 'injectUiAppVars', (appId, provider) => kbnServer.newPlatform.__internals.legacy.injectUiAppVars(appId, provider) ); server.decorate( 'server', 'getInjectedUiAppVars', - async appId => await kbnServer.newPlatform.__internals.legacy.getInjectedUiAppVars(appId) + async (appId) => await kbnServer.newPlatform.__internals.legacy.getInjectedUiAppVars(appId) ); } diff --git a/src/legacy/ui/ui_bundles/app_entry_template.js b/src/legacy/ui/ui_bundles/app_entry_template.js index 683fedd34316f..a48de9a8cf7ee 100644 --- a/src/legacy/ui/ui_bundles/app_entry_template.js +++ b/src/legacy/ui/ui_bundles/app_entry_template.js @@ -19,7 +19,7 @@ import { apmImport, apmInit } from '../apm'; -export const appEntryTemplate = bundle => ` +export const appEntryTemplate = (bundle) => ` /** * Kibana entry file * diff --git a/src/legacy/ui/ui_bundles/ui_bundle.js b/src/legacy/ui/ui_bundles/ui_bundle.js index a8d4259fb98f2..4e853ad410efe 100644 --- a/src/legacy/ui/ui_bundles/ui_bundle.js +++ b/src/legacy/ui/ui_bundles/ui_bundle.js @@ -57,7 +57,7 @@ export class UiBundle { } getRequires() { - return this._modules.map(module => `require('${normalizePath(module)}');`); + return this._modules.map((module) => `require('${normalizePath(module)}');`); } renderContent() { @@ -66,7 +66,7 @@ export class UiBundle { async readEntryFile() { try { - const content = await fcb(cb => readFile(this.getEntryPath(), cb)); + const content = await fcb((cb) => readFile(this.getEntryPath(), cb)); return content.toString('utf8'); } catch (e) { return null; @@ -74,11 +74,11 @@ export class UiBundle { } async writeEntryFile() { - return await fcb(cb => writeFile(this.getEntryPath(), this.renderContent(), 'utf8', cb)); + return await fcb((cb) => writeFile(this.getEntryPath(), this.renderContent(), 'utf8', cb)); } async touchStyleFile() { - return await fcb(cb => writeFile(this.getStylePath(), '', 'utf8', cb)); + return await fcb((cb) => writeFile(this.getStylePath(), '', 'utf8', cb)); } /** @@ -98,8 +98,8 @@ export class UiBundle { } try { - await fcb(cb => stat(this.getOutputPath(), cb)); - await fcb(cb => stat(this.getStylePath(), cb)); + await fcb((cb) => stat(this.getOutputPath(), cb)); + await fcb((cb) => stat(this.getStylePath(), cb)); return true; } catch (e) { return false; diff --git a/src/legacy/ui/ui_bundles/ui_bundles_controller.js b/src/legacy/ui/ui_bundles/ui_bundles_controller.js index 79112fd687e84..dadb28cbb2f3a 100644 --- a/src/legacy/ui/ui_bundles/ui_bundles_controller.js +++ b/src/legacy/ui/ui_bundles/ui_bundles_controller.js @@ -56,7 +56,7 @@ function stableCloneAppExtensions(appExtensions) { Object.entries(appExtensions).map(([extensionType, moduleIds]) => [ extensionType, moduleIds - .map(moduleId => { + .map((moduleId) => { if (isAbsolute(moduleId)) { moduleId = `absolute:${relative(REPO_ROOT, moduleId)}`; } @@ -246,7 +246,7 @@ export class UiBundlesController { } getIds() { - return this._bundles.map(bundle => bundle.getId()); + return this._bundles.map((bundle) => bundle.getId()); } getExtendedConfig(webpackConfig) { diff --git a/src/legacy/ui/ui_exports/__tests__/collect_ui_exports.js b/src/legacy/ui/ui_exports/__tests__/collect_ui_exports.js index 352e8642e930e..3686a2dad8f24 100644 --- a/src/legacy/ui/ui_exports/__tests__/collect_ui_exports.js +++ b/src/legacy/ui/ui_exports/__tests__/collect_ui_exports.js @@ -106,7 +106,7 @@ describe('plugin discovery', () => { uiExports: { migrations: { 'test-type': { - '1.2.3': doc => { + '1.2.3': (doc) => { return doc; }, }, @@ -116,7 +116,7 @@ describe('plugin discovery', () => { ]; }, }).getPluginSpecs(); - expect(() => collectUiExports(invalidSpecs)).to.throwError(err => { + expect(() => collectUiExports(invalidSpecs)).to.throwError((err) => { expect(err).to.be.a(Error); expect(err).to.have.property( 'message', diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 1341777bc0991..d60bf7df899e3 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -33,7 +33,7 @@ export const UI_EXPORT_DEFAULTS = { test_harness: resolve(ROOT, 'src/test_harness/public'), }, - styleSheetPaths: ['light', 'dark'].map(theme => ({ + styleSheetPaths: ['light', 'dark'].map((theme) => ({ theme, localPath: resolve(ROOT, 'src/core/public/index.scss'), publicPath: `core.${theme}.css`, diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js index 511e7c20bd3b0..a894e59a03c81 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js +++ b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js @@ -24,5 +24,5 @@ * @param {String} newType * @return {Function} */ -export const alias = newType => next => (acc, spec, type, pluginSpec) => +export const alias = (newType) => (next) => (acc, spec, type, pluginSpec) => next(acc, spec, newType, pluginSpec); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js index de0cfb177b6e9..5970c45e7445e 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js +++ b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js @@ -25,5 +25,5 @@ * @param {Function} mapFn receives `(specs, type, pluginSpec)` * @return {Function} */ -export const mapSpec = mapFn => next => (acc, spec, type, pluginSpec) => +export const mapSpec = (mapFn) => (next) => (acc, spec, type, pluginSpec) => next(acc, mapFn(spec, type, pluginSpec), type, pluginSpec); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js index 9a2519f79eda8..dedcd057b09e3 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js +++ b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js @@ -17,10 +17,10 @@ * under the License. */ -const pluginId = pluginSpec => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); +const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); -export const uniqueKeys = sourceType => next => (acc, spec, type, pluginSpec) => { - const duplicates = Object.keys(spec).filter(key => acc[type] && acc[type].hasOwnProperty(key)); +export const uniqueKeys = (sourceType) => (next) => (acc, spec, type, pluginSpec) => { + const duplicates = Object.keys(spec).filter((key) => acc[type] && acc[type].hasOwnProperty(key)); if (duplicates.length) { throw new Error( diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js index a4df5debf1316..bf4793c208308 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js +++ b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js @@ -26,7 +26,7 @@ * @param {Function} reducer * @return {Function} */ -export const createTypeReducer = reducer => (acc, spec, type, pluginSpec) => ({ +export const createTypeReducer = (reducer) => (acc, spec, type, pluginSpec) => ({ ...acc, [type]: reducer(acc[type], spec, type, pluginSpec), }); diff --git a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js b/src/legacy/ui/ui_exports/ui_export_types/saved_object.js index 63a3c5f036993..be6898d3e642c 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js +++ b/src/legacy/ui/ui_exports/ui_export_types/saved_object.js @@ -30,7 +30,7 @@ export const mappings = wrap( flatConcatAtType ); -const pluginId = pluginSpec => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); +const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); // Combines the `migrations` property of each plugin, // ensuring that properties are unique across plugins @@ -38,9 +38,9 @@ const pluginId = pluginSpec => (pluginSpec.id ? pluginSpec.id() : pluginSpec.get // See saved_objects/migrations for more details. export const migrations = wrap( alias('savedObjectMigrations'), - next => (acc, spec, type, pluginSpec) => { + (next) => (acc, spec, type, pluginSpec) => { const mappings = pluginSpec.getExportSpecs().mappings || {}; - const invalidMigrationTypes = Object.keys(spec).filter(type => !mappings[type]); + const invalidMigrationTypes = Object.keys(spec).filter((type) => !mappings[type]); if (invalidMigrationTypes.length) { throw new Error( 'Migrations and mappings must be defined together in the uiExports of a single plugin. ' + diff --git a/src/legacy/ui/ui_exports/ui_export_types/style_sheet_paths.test.js b/src/legacy/ui/ui_exports/ui_export_types/style_sheet_paths.test.js index e3e051cbfac7e..6a1fa7bdf3633 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/style_sheet_paths.test.js +++ b/src/legacy/ui/ui_exports/ui_export_types/style_sheet_paths.test.js @@ -28,8 +28,8 @@ const pluginSpec = { }; expect.addSnapshotSerializer({ - test: value => typeof value === 'string' && value.startsWith(dir), - print: value => value.replace(dir, '').replace(/\\/g, '/'), + test: (value) => typeof value === 'string' && value.startsWith(dir), + print: (value) => value.replace(dir, '').replace(/\\/g, '/'), }); describe('uiExports.styleSheetPaths', () => { diff --git a/src/legacy/ui/ui_exports/ui_export_types/webpack_customizations.js b/src/legacy/ui/ui_exports/ui_export_types/webpack_customizations.js index 83aa91b810c5f..3f3ff8b97999c 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/webpack_customizations.js +++ b/src/legacy/ui/ui_exports/ui_export_types/webpack_customizations.js @@ -33,7 +33,7 @@ export const __bundleProvider__ = wrap(alias('uiBundleProviders'), flatConcatAtT export const __webpackPluginProvider__ = wrap(alias('webpackPluginProviders'), flatConcatAtType); export const noParse = wrap( alias('webpackNoParseRules'), - mapSpec(rule => { + mapSpec((rule) => { if (typeof rule === 'string') { return new RegExp(`${isAbsolute(rule) ? '^' : ''}${escapeRegExp(rule)}`); } diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index 1453c974c1180..e8f05b46f7061 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -73,12 +73,23 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { } load([ - {{#each jsDependencyPaths}} - '{{this}}', - {{/each}} + {{#each jsDependencyPaths}} + '{{this}}', + {{/each}} ], function () { + {{#unless legacyBundlePath}} + if (!__kbnBundles__ || !__kbnBundles__['entry/core'] || typeof __kbnBundles__['entry/core'].__kbnBootstrap__ !== 'function') { + console.error('entry/core bundle did not load correctly'); + failure(); + } else { + __kbnBundles__['entry/core'].__kbnBootstrap__() + } + {{/unless}} + load([ - '{{entryBundlePath}}', + {{#if legacyBundlePath}} + '{{legacyBundlePath}}', + {{/if}} {{#each styleSheetPaths}} '{{this}}', {{/each}} diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 8024a6ded2967..b09d4861b343b 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -108,10 +108,10 @@ export function uiRenderMixin(kbnServer, server, config) { const dllBundlePath = `${basePath}/${buildHash}/built_assets/dlls`; const dllStyleChunks = DllCompiler.getRawDllConfig().chunks.map( - chunk => `${dllBundlePath}/vendors${chunk}.style.dll.css` + (chunk) => `${dllBundlePath}/vendors${chunk}.style.dll.css` ); const dllJsChunks = DllCompiler.getRawDllConfig().chunks.map( - chunk => `${dllBundlePath}/vendors${chunk}.bundle.dll.js` + (chunk) => `${dllBundlePath}/vendors${chunk}.bundle.dll.js` ); const styleSheetPaths = [ @@ -139,9 +139,9 @@ export function uiRenderMixin(kbnServer, server, config) { `${regularBundlePath}/${app.getId()}.style.css`, ...kbnServer.uiExports.styleSheetPaths .filter( - path => path.theme === '*' || path.theme === (darkMode ? 'dark' : 'light') + (path) => path.theme === '*' || path.theme === (darkMode ? 'dark' : 'light') ) - .map(path => + .map((path) => path.localPath.endsWith('.scss') ? `${basePath}/${buildHash}/built_assets/css/${path.publicPath}` : `${basePath}/${path.publicPath}` @@ -162,7 +162,7 @@ export function uiRenderMixin(kbnServer, server, config) { const jsDependencyPaths = [ ...UiSharedDeps.jsDepFilenames.map( - filename => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` + (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` ), `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, ...(isCore @@ -173,8 +173,9 @@ export function uiRenderMixin(kbnServer, server, config) { `${regularBundlePath}/commons.bundle.js`, ]), + `${regularBundlePath}/core/core.entry.js`, ...kpPluginIds.map( - pluginId => `${regularBundlePath}/plugin/${pluginId}/${pluginId}.plugin.js` + (pluginId) => `${regularBundlePath}/plugin/${pluginId}/${pluginId}.plugin.js` ), ]; @@ -199,9 +200,7 @@ export function uiRenderMixin(kbnServer, server, config) { jsDependencyPaths, styleSheetPaths, publicPathMap, - entryBundlePath: isCore - ? `${regularBundlePath}/core/core.entry.js` - : `${regularBundlePath}/${app.getId()}.bundle.js`, + legacyBundlePath: isCore ? undefined : `${regularBundlePath}/${app.getId()}.bundle.js`, }, }); @@ -247,9 +246,10 @@ export function uiRenderMixin(kbnServer, server, config) { rendering, legacy, savedObjectsClientProvider: savedObjects, - uiSettings: { asScopedToClient }, } = kbnServer.newPlatform.__internals; - const uiSettings = asScopedToClient(savedObjects.getClient(h.request)); + const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient( + savedObjects.getClient(h.request) + ); const vars = await legacy.getVars(app.getId(), h.request, { apmConfig: getApmConfig(app), ...overrides, @@ -260,17 +260,14 @@ export function uiRenderMixin(kbnServer, server, config) { vars, }); - return h - .response(content) - .type('text/html') - .header('content-security-policy', http.csp.header); + return h.response(content).type('text/html').header('content-security-policy', http.csp.header); } - server.decorate('toolkit', 'renderApp', function(app, overrides) { + server.decorate('toolkit', 'renderApp', function (app, overrides) { return renderApp(this, app, true, overrides); }); - server.decorate('toolkit', 'renderAppWithDefaultConfig', function(app) { + server.decorate('toolkit', 'renderAppWithDefaultConfig', function (app) { return renderApp(this, app, false); }); } diff --git a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts index dd3f12903abca..84a64d3f46f11 100644 --- a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts +++ b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts @@ -52,13 +52,22 @@ describe('uiSettingsMixin()', () => { log: sinon.stub(), route: sinon.stub(), addMemoizedFactoryToRequest(name: string, factory: (...args: any[]) => any) { - this.decorate('request', name, function(this: typeof server) { + this.decorate('request', name, function (this: typeof server) { return factory(this); }); }, decorate: sinon.spy((type: keyof Decorators, name: string, value: any) => { decorations[type][name] = value; }), + newPlatform: { + setup: { + core: { + uiSettings: { + register: sinon.stub(), + }, + }, + }, + }, }; // "promise" returned from kbnServer.ready() @@ -70,13 +79,6 @@ describe('uiSettingsMixin()', () => { server, uiExports: { uiSettingDefaults }, ready: sinon.stub().returns(readyPromise), - newPlatform: { - __internals: { - uiSettings: { - register: sinon.stub(), - }, - }, - }, }; uiSettingsMixin(kbnServer, server); @@ -92,10 +94,10 @@ describe('uiSettingsMixin()', () => { afterEach(() => sandbox.restore()); it('passes uiSettingsDefaults to the new platform', () => { - const { kbnServer } = setup(); - sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.register); + const { server } = setup(); + sinon.assert.calledOnce(server.newPlatform.setup.core.uiSettings.register); sinon.assert.calledWithExactly( - kbnServer.newPlatform.__internals.uiSettings.register, + server.newPlatform.setup.core.uiSettings.register, uiSettingDefaults ); }); @@ -103,9 +105,7 @@ describe('uiSettingsMixin()', () => { describe('server.uiSettingsServiceFactory()', () => { it('decorates server with "uiSettingsServiceFactory"', () => { const { decorations } = setup(); - expect(decorations.server) - .to.have.property('uiSettingsServiceFactory') - .a('function'); + expect(decorations.server).to.have.property('uiSettingsServiceFactory').a('function'); const uiSettingsServiceFactoryStub = sandbox.stub( uiSettingsServiceFactoryNS, @@ -118,9 +118,7 @@ describe('uiSettingsMixin()', () => { it('passes `server` and `options` argument to factory', () => { const { decorations, server } = setup(); - expect(decorations.server) - .to.have.property('uiSettingsServiceFactory') - .a('function'); + expect(decorations.server).to.have.property('uiSettingsServiceFactory').a('function'); const uiSettingsServiceFactoryStub = sandbox.stub( uiSettingsServiceFactoryNS, @@ -143,9 +141,7 @@ describe('uiSettingsMixin()', () => { describe('request.getUiSettingsService()', () => { it('exposes "getUiSettingsService" on requests', () => { const { decorations } = setup(); - expect(decorations.request) - .to.have.property('getUiSettingsService') - .a('function'); + expect(decorations.request).to.have.property('getUiSettingsService').a('function'); const getUiSettingsServiceForRequestStub = sandbox.stub( getUiSettingsServiceForRequestNS, @@ -158,9 +154,7 @@ describe('uiSettingsMixin()', () => { it('passes request to getUiSettingsServiceForRequest', () => { const { server, decorations } = setup(); - expect(decorations.request) - .to.have.property('getUiSettingsService') - .a('function'); + expect(decorations.request).to.have.property('getUiSettingsService').a('function'); const getUiSettingsServiceForRequestStub = sandbox.stub( getUiSettingsServiceForRequestNS, @@ -176,9 +170,7 @@ describe('uiSettingsMixin()', () => { describe('server.uiSettings()', () => { it('throws an error, links to pr', () => { const { decorations } = setup(); - expect(decorations.server) - .to.have.property('uiSettings') - .a('function'); + expect(decorations.server).to.have.property('uiSettings').a('function'); expect(() => { decorations.server.uiSettings(); }).to.throwError('http://github.com' as any); // incorrect typings diff --git a/src/legacy/ui/ui_settings/ui_exports_consumer.js b/src/legacy/ui/ui_settings/ui_exports_consumer.js index 0ced11afc8d2c..d2bb3a00ce0ed 100644 --- a/src/legacy/ui/ui_settings/ui_exports_consumer.js +++ b/src/legacy/ui/ui_settings/ui_exports_consumer.js @@ -41,7 +41,7 @@ export class UiExportsConsumer { switch (type) { case 'uiSettingDefaults': return (plugin, settingDefinitions) => { - Object.keys(settingDefinitions).forEach(key => { + Object.keys(settingDefinitions).forEach((key) => { if (key in this._uiSettingDefaults) { throw new Error(`uiSettingDefaults for key "${key}" are already defined`); } diff --git a/src/legacy/ui/ui_settings/ui_settings_mixin.js b/src/legacy/ui/ui_settings/ui_settings_mixin.js index 64251d290776c..8190b67732dac 100644 --- a/src/legacy/ui/ui_settings/ui_settings_mixin.js +++ b/src/legacy/ui/ui_settings/ui_settings_mixin.js @@ -37,13 +37,13 @@ export function uiSettingsMixin(kbnServer, server) { return acc; }, {}); - kbnServer.newPlatform.__internals.uiSettings.register(mergedUiSettingDefaults); + server.newPlatform.setup.core.uiSettings.register(mergedUiSettingDefaults); server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => { return uiSettingsServiceFactory(server, options); }); - server.addMemoizedFactoryToRequest('getUiSettingsService', request => { + server.addMemoizedFactoryToRequest('getUiSettingsService', (request) => { return getUiSettingsServiceForRequest(server, request); }); diff --git a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts index ab4eb75e4b703..6c3c50d175dc5 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts +++ b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts @@ -32,5 +32,5 @@ export function uiSettingsServiceFactory( server: Legacy.Server, options: UiSettingsServiceFactoryOptions ): IUiSettingsClient { - return server.newPlatform.__internals.uiSettings.asScopedToClient(options.savedObjectsClient); + return server.newPlatform.start.core.uiSettings.asScopedToClient(options.savedObjectsClient); } diff --git a/src/legacy/utils/__tests__/unset.js b/src/legacy/utils/__tests__/unset.js index 6e6840758c7ae..69122e06ac572 100644 --- a/src/legacy/utils/__tests__/unset.js +++ b/src/legacy/utils/__tests__/unset.js @@ -20,77 +20,77 @@ import { unset } from '../unset'; import expect from '@kbn/expect'; -describe('unset(obj, key)', function() { - describe('invalid input', function() { - it('should do nothing if not given an object', function() { +describe('unset(obj, key)', function () { + describe('invalid input', function () { + it('should do nothing if not given an object', function () { const obj = 'hello'; unset(obj, 'e'); expect(obj).to.equal('hello'); }); - it('should do nothing if not given a key', function() { + it('should do nothing if not given a key', function () { const obj = { one: 1 }; unset(obj); expect(obj).to.eql({ one: 1 }); }); - it('should do nothing if given an empty string as a key', function() { + it('should do nothing if given an empty string as a key', function () { const obj = { one: 1 }; unset(obj, ''); expect(obj).to.eql({ one: 1 }); }); }); - describe('shallow removal', function() { + describe('shallow removal', function () { let obj; - beforeEach(function() { + beforeEach(function () { obj = { one: 1, two: 2, deep: { three: 3, four: 4 } }; }); - it('should remove the param using a string key', function() { + it('should remove the param using a string key', function () { unset(obj, 'two'); expect(obj).to.eql({ one: 1, deep: { three: 3, four: 4 } }); }); - it('should remove the param using an array key', function() { + it('should remove the param using an array key', function () { unset(obj, ['two']); expect(obj).to.eql({ one: 1, deep: { three: 3, four: 4 } }); }); }); - describe('deep removal', function() { + describe('deep removal', function () { let obj; - beforeEach(function() { + beforeEach(function () { obj = { one: 1, two: 2, deep: { three: 3, four: 4 } }; }); - it('should remove the param using a string key', function() { + it('should remove the param using a string key', function () { unset(obj, 'deep.three'); expect(obj).to.eql({ one: 1, two: 2, deep: { four: 4 } }); }); - it('should remove the param using an array key', function() { + it('should remove the param using an array key', function () { unset(obj, ['deep', 'three']); expect(obj).to.eql({ one: 1, two: 2, deep: { four: 4 } }); }); }); - describe('recursive removal', function() { - it('should clear object if only value is removed', function() { + describe('recursive removal', function () { + it('should clear object if only value is removed', function () { const obj = { one: { two: { three: 3 } } }; unset(obj, 'one.two.three'); expect(obj).to.eql({}); }); - it('should clear object if no props are left', function() { + it('should clear object if no props are left', function () { const obj = { one: { two: { three: 3 } } }; unset(obj, 'one.two'); expect(obj).to.eql({}); }); - it('should remove deep property, then clear the object', function() { + it('should remove deep property, then clear the object', function () { const obj = { one: { two: { three: 3, four: 4 } } }; unset(obj, 'one.two.three'); expect(obj).to.eql({ one: { two: { four: 4 } } }); diff --git a/src/legacy/utils/__tests__/watch_stdio_for_line.js b/src/legacy/utils/__tests__/watch_stdio_for_line.js index 1bd69cfd8b64d..32d61658c1114 100644 --- a/src/legacy/utils/__tests__/watch_stdio_for_line.js +++ b/src/legacy/utils/__tests__/watch_stdio_for_line.js @@ -23,12 +23,12 @@ import sinon from 'sinon'; import { watchStdioForLine } from '../watch_stdio_for_line'; -describe('src/legacy/utils/watch_stdio_for_line', function() { +describe('src/legacy/utils/watch_stdio_for_line', function () { const sandbox = sinon.sandbox.create(); afterEach(() => sandbox.reset()); const onLogLine = sandbox.stub(); - const logFn = line => onLogLine(stripAnsi(line)); + const logFn = (line) => onLogLine(stripAnsi(line)); it('calls logFn with log lines', async () => { const proc = execa(process.execPath, ['-e', 'console.log("hi")']); @@ -39,7 +39,7 @@ describe('src/legacy/utils/watch_stdio_for_line', function() { sinon.assert.calledWithExactly(onLogLine, sinon.match(/hi/)); }); - it('send the proc SIGKILL if it logs a line matching exitAfter regexp', async function() { + it('send the proc SIGKILL if it logs a line matching exitAfter regexp', async function () { // fixture proc will exit after 10 seconds if sigint not received, but the test won't fail // unless we see the log line `SIGINT not received`, so we let the test take up to 30 seconds // for potentially huge delays here and there diff --git a/src/legacy/utils/binder.ts b/src/legacy/utils/binder.ts index 20c7cb9d4d490..55577e3a69e2b 100644 --- a/src/legacy/utils/binder.ts +++ b/src/legacy/utils/binder.ts @@ -38,6 +38,6 @@ export class BinderBase { public destroy() { const destroyers = this.disposal; this.disposal = []; - destroyers.forEach(fn => fn()); + destroyers.forEach((fn) => fn()); } } diff --git a/src/legacy/utils/streams/concat_stream_providers.js b/src/legacy/utils/streams/concat_stream_providers.js index 64643787ea771..11dfb84284df3 100644 --- a/src/legacy/utils/streams/concat_stream_providers.js +++ b/src/legacy/utils/streams/concat_stream_providers.js @@ -51,7 +51,7 @@ export function concatStreamProviders(sourceProviders, options = {}) { source // proxy errors from the source to the destination - .once('error', error => destination.emit('error', error)) + .once('error', (error) => destination.emit('error', error)) // pipe the source to the destination but only proxy the // end event if this is the last source .pipe(destination, { end: isLast }); diff --git a/src/legacy/utils/streams/filter_stream.test.ts b/src/legacy/utils/streams/filter_stream.test.ts index f5140b7639c74..7f4901f31c173 100644 --- a/src/legacy/utils/streams/filter_stream.test.ts +++ b/src/legacy/utils/streams/filter_stream.test.ts @@ -64,7 +64,7 @@ describe('createFilterStream()', () => { test('send the filtered values on the output stream', async () => { const result = await createPromiseFromStreams([ createListStream([1, 2, 3]), - createFilterStream(n => n % 2 === 0), + createFilterStream((n) => n % 2 === 0), createConcatStream([]), ]); diff --git a/src/legacy/utils/streams/list_stream.js b/src/legacy/utils/streams/list_stream.js index 703d664a97ec7..a614620b054b7 100644 --- a/src/legacy/utils/streams/list_stream.js +++ b/src/legacy/utils/streams/list_stream.js @@ -32,7 +32,7 @@ export function createListStream(items = []) { return new Readable({ objectMode: true, read(size) { - queue.splice(0, size).forEach(item => { + queue.splice(0, size).forEach((item) => { this.push(item); }); diff --git a/src/legacy/utils/streams/list_stream.test.js b/src/legacy/utils/streams/list_stream.test.js index 9052127793c6e..12e20696b0510 100644 --- a/src/legacy/utils/streams/list_stream.test.js +++ b/src/legacy/utils/streams/list_stream.test.js @@ -25,7 +25,7 @@ describe('listStream', () => { const onData = jest.fn(); str.on('data', onData); - await new Promise(resolve => str.on('end', resolve)); + await new Promise((resolve) => str.on('end', resolve)); expect(onData).toHaveBeenCalledTimes(4); expect(onData.mock.calls[0]).toEqual([1]); @@ -38,7 +38,7 @@ describe('listStream', () => { const list = [1, 2, 3, 4]; const str = createListStream(list); str.resume(); - await new Promise(resolve => str.on('end', resolve)); + await new Promise((resolve) => str.on('end', resolve)); expect(list).toEqual([1, 2, 3, 4]); }); }); diff --git a/src/legacy/utils/streams/map_stream.test.js b/src/legacy/utils/streams/map_stream.test.js index 4ffabe7477d73..d86da178f0c1b 100644 --- a/src/legacy/utils/streams/map_stream.test.js +++ b/src/legacy/utils/streams/map_stream.test.js @@ -39,7 +39,7 @@ describe('createMapStream()', () => { test('send the return value from the mapper on the output stream', async () => { const result = await createPromiseFromStreams([ createListStream([1, 2, 3]), - createMapStream(n => n * 100), + createMapStream((n) => n * 100), createConcatStream([]), ]); diff --git a/src/legacy/utils/streams/promise_from_streams.js b/src/legacy/utils/streams/promise_from_streams.js index 9c8205702f254..05f6a08aa1a09 100644 --- a/src/legacy/utils/streams/promise_from_streams.js +++ b/src/legacy/utils/streams/promise_from_streams.js @@ -58,7 +58,7 @@ export async function createPromiseFromStreams(streams) { ); } return new Promise((resolve, reject) => { - pipeline(...streams, err => { + pipeline(...streams, (err) => { if (err) return reject(err); resolve(finalChunk); }); diff --git a/src/legacy/utils/streams/promise_from_streams.test.js b/src/legacy/utils/streams/promise_from_streams.test.js index 9876e452e262b..e4d9835106f12 100644 --- a/src/legacy/utils/streams/promise_from_streams.test.js +++ b/src/legacy/utils/streams/promise_from_streams.test.js @@ -25,9 +25,9 @@ describe('promiseFromStreams', () => { test('pipes together an array of streams', async () => { const str1 = createListStream([1, 2, 3]); const str2 = createReduceStream((acc, n) => acc + n, 0); - const sumPromise = new Promise(resolve => str2.once('data', resolve)); + const sumPromise = new Promise((resolve) => str2.once('data', resolve)); createPromiseFromStreams([str1, str2]); - await new Promise(resolve => str2.once('end', resolve)); + await new Promise((resolve) => str2.once('end', resolve)); expect(await sumPromise).toBe(6); }); @@ -88,7 +88,7 @@ describe('promiseFromStreams', () => { write(chunk, enc, cb) { duplexReadQueue.push( - new Promise(resolve => { + new Promise((resolve) => { setTimeout(() => { written += chunk; cb(); diff --git a/src/legacy/utils/streams/reduce_stream.test.js b/src/legacy/utils/streams/reduce_stream.test.js index 98d01ec2c773a..2c073f67f82a8 100644 --- a/src/legacy/utils/streams/reduce_stream.test.js +++ b/src/legacy/utils/streams/reduce_stream.test.js @@ -20,7 +20,7 @@ import { createReduceStream, createPromiseFromStreams, createListStream } from './'; const promiseFromEvent = (name, emitter) => - new Promise(resolve => emitter.on(name, () => resolve(name))); + new Promise((resolve) => emitter.on(name, () => resolve(name))); describe('reduceStream', () => { test('calls the reducer for each item provided', async () => { @@ -41,7 +41,7 @@ describe('reduceStream', () => { test('provides the return value of the last iteration of the reducer', async () => { const result = await createPromiseFromStreams([ createListStream('abcdefg'.split('')), - createReduceStream(acc => acc + 1, 0), + createReduceStream((acc) => acc + 1, 0), ]); expect(result).toBe(7); }); diff --git a/src/legacy/utils/streams/replace_stream.test.js b/src/legacy/utils/streams/replace_stream.test.js index 219d4fb18a59e..01b89f93e5af0 100644 --- a/src/legacy/utils/streams/replace_stream.test.js +++ b/src/legacy/utils/streams/replace_stream.test.js @@ -28,7 +28,7 @@ import { async function concatToString(streams) { return await createPromiseFromStreams([ ...streams, - createMapStream(buff => buff.toString('utf8')), + createMapStream((buff) => buff.toString('utf8')), createConcatStream(''), ]); } @@ -41,7 +41,7 @@ describe('replaceStream', () => { createConcatStream([]), ]); - chunks.forEach(chunk => { + chunks.forEach((chunk) => { expect(chunk).toBeInstanceOf(Buffer); }); }); @@ -53,7 +53,7 @@ describe('replaceStream', () => { createConcatStream([]), ]); - chunks.forEach(chunk => { + chunks.forEach((chunk) => { expect(chunk).toBeInstanceOf(Buffer); }); }); diff --git a/src/legacy/utils/streams/split_stream.test.js b/src/legacy/utils/streams/split_stream.test.js index 148b9211b81bb..e0736d220ba5c 100644 --- a/src/legacy/utils/streams/split_stream.test.js +++ b/src/legacy/utils/streams/split_stream.test.js @@ -25,7 +25,7 @@ async function split(stream, input) { stream.pipe(concat); const output = createPromiseFromStreams([concat]); - input.forEach(i => { + input.forEach((i) => { stream.write(i); }); stream.end(); diff --git a/src/legacy/utils/watch_stdio_for_line.js b/src/legacy/utils/watch_stdio_for_line.js index dc822602931b7..01323b4d4e967 100644 --- a/src/legacy/utils/watch_stdio_for_line.js +++ b/src/legacy/utils/watch_stdio_for_line.js @@ -53,7 +53,7 @@ export async function watchStdioForLine(proc, logFn, exitAfter) { } await Promise.all([ - proc.catch(error => { + proc.catch((error) => { // ignore the error thrown by execa if it's because we killed with SIGINT if (error.signal !== 'SIGINT') { throw error; diff --git a/src/optimize/base_optimizer.js b/src/optimize/base_optimizer.js index 9d3a834b455db..12131b89e03c1 100644 --- a/src/optimize/base_optimizer.js +++ b/src/optimize/base_optimizer.js @@ -109,7 +109,7 @@ export default class BaseOptimizer { } registerCompilerDoneHook() { - this.compiler.hooks.done.tap('base_optimizer-done', stats => { + this.compiler.hooks.done.tap('base_optimizer-done', (stats) => { // We are not done while we have an additional // compilation pass to run // We also don't need to emit the stats if we don't have @@ -120,7 +120,7 @@ export default class BaseOptimizer { const path = this.uiBundles.resolvePath('stats.json'); const content = JSON.stringify(stats.toJson()); - writeFile(path, content, function(err) { + writeFile(path, content, function (err) { if (err) throw err; }); }); @@ -226,7 +226,7 @@ export default class BaseOptimizer { * Creates the selection rules for a loader that will only pass for * source files that are eligible for automatic transpilation. */ - const createSourceFileResourceSelector = test => { + const createSourceFileResourceSelector = (test) => { return [ { test, @@ -268,20 +268,22 @@ export default class BaseOptimizer { cacheGroups: { commons: { name: 'commons', - chunks: chunk => + chunks: (chunk) => chunk.canBeInitial() && chunk.name !== 'light_theme' && chunk.name !== 'dark_theme', minChunks: 2, reuseExistingChunk: true, }, light_theme: { name: 'light_theme', - test: m => m.constructor.name === 'CssModule' && recursiveIssuer(m) === 'light_theme', + test: (m) => + m.constructor.name === 'CssModule' && recursiveIssuer(m) === 'light_theme', chunks: 'all', enforce: true, }, dark_theme: { name: 'dark_theme', - test: m => m.constructor.name === 'CssModule' && recursiveIssuer(m) === 'dark_theme', + test: (m) => + m.constructor.name === 'CssModule' && recursiveIssuer(m) === 'dark_theme', chunks: 'all', enforce: true, }, @@ -302,13 +304,13 @@ export default class BaseOptimizer { }), // ignore scss imports in new-platform code that finds its way into legacy bundles - new webpack.NormalModuleReplacementPlugin(/\.scss$/, resource => { + new webpack.NormalModuleReplacementPlugin(/\.scss$/, (resource) => { resource.request = EMPTY_MODULE_PATH; }), // replace imports for `uiExports/*` modules with a synthetic module // created by create_ui_exports_module.js - new webpack.NormalModuleReplacementPlugin(/^uiExports\//, resource => { + new webpack.NormalModuleReplacementPlugin(/^uiExports\//, (resource) => { // the map of uiExport types to module ids const extensions = this.uiBundles.getAppExtensions(); @@ -330,7 +332,7 @@ export default class BaseOptimizer { ].join(''); }), - ...this.uiBundles.getWebpackPluginProviders().map(provider => provider(webpack)), + ...this.uiBundles.getWebpackPluginProviders().map((provider) => provider(webpack)), ], module: { @@ -376,7 +378,7 @@ export default class BaseOptimizer { }, ]), }, - ...this.uiBundles.getPostLoaders().map(loader => ({ + ...this.uiBundles.getPostLoaders().map((loader) => ({ enforce: 'post', ...loader, })), diff --git a/src/optimize/bundles_route/file_hash.ts b/src/optimize/bundles_route/file_hash.ts index 7b0801098ed10..d8b4c4419b844 100644 --- a/src/optimize/bundles_route/file_hash.ts +++ b/src/optimize/bundles_route/file_hash.ts @@ -46,15 +46,15 @@ export async function getFileHash(cache: FileHashCache, path: string, stat: Fs.S const promise = Rx.merge( Rx.fromEvent(read, 'data'), Rx.fromEvent(read, 'error').pipe( - map(error => { + map((error) => { throw error; }) ) ) .pipe(takeUntil(Rx.fromEvent(read, 'end'))) - .forEach(chunk => hash.update(chunk)) + .forEach((chunk) => hash.update(chunk)) .then(() => hash.digest('hex')) - .catch(error => { + .catch((error) => { // don't cache failed attempts cache.del(key); throw error; diff --git a/src/optimize/create_ui_exports_module.js b/src/optimize/create_ui_exports_module.js index 2ab19d71e1f1c..d20814b10931b 100644 --- a/src/optimize/create_ui_exports_module.js +++ b/src/optimize/create_ui_exports_module.js @@ -22,7 +22,7 @@ function normalizePath(path) { return path.replace(/[\\\/]+/g, '/'); } -module.exports = function() { +module.exports = function () { if (!module.id.includes('?')) { throw new Error('create_ui_exports_module loaded without JSON args in module.id'); } @@ -31,7 +31,7 @@ module.exports = function() { const comment = `// dynamically generated to load ${type} uiExports from plugins`; const requires = modules .sort((a, b) => a.localeCompare(b)) - .map(m => `require('${normalizePath(m)}')`) + .map((m) => `require('${normalizePath(m)}')`) .join('\n '); return { diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js index 9889c1f71c3bf..9ab21ee0e9076 100644 --- a/src/optimize/dynamic_dll_plugin/dll_compiler.js +++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js @@ -48,7 +48,7 @@ export class DllCompiler { uiBundles = {}, babelLoaderCacheDir = '', threadLoaderPoolConfig = {}, - chunks = Array.from(Array(4).keys()).map(chunkN => `_${chunkN}`) + chunks = Array.from(Array(4).keys()).map((chunkN) => `_${chunkN}`) ) { return { uiBundles, @@ -130,25 +130,25 @@ export class DllCompiler { } getDllPaths() { - return this.rawDllConfig.chunks.map(chunk => + return this.rawDllConfig.chunks.map((chunk) => this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.dllExt}`) ); } getEntryPaths() { - return this.rawDllConfig.chunks.map(chunk => + return this.rawDllConfig.chunks.map((chunk) => this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.entryExt}`) ); } getManifestPaths() { - return this.rawDllConfig.chunks.map(chunk => + return this.rawDllConfig.chunks.map((chunk) => this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.manifestExt}`) ); } getStylePaths() { - return this.rawDllConfig.chunks.map(chunk => + return this.rawDllConfig.chunks.map((chunk) => this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.styleExt}`) ); } @@ -156,7 +156,7 @@ export class DllCompiler { async ensureEntryFilesExists() { const entryPaths = this.getEntryPaths(); - await Promise.all(entryPaths.map(async entryPath => await this.ensureFileExists(entryPath))); + await Promise.all(entryPaths.map(async (entryPath) => await this.ensureFileExists(entryPath))); } async ensureManifestFilesExists() { @@ -179,7 +179,7 @@ export class DllCompiler { async ensureStyleFileExists() { const stylePaths = this.getStylePaths(); - await Promise.all(stylePaths.map(async stylePath => await this.ensureFileExists(stylePath))); + await Promise.all(stylePaths.map(async (stylePath) => await this.ensureFileExists(stylePath))); } async ensureFileExists(filePath, content) { @@ -208,7 +208,7 @@ export class DllCompiler { dllsExistsSync() { const dllPaths = this.getDllPaths(); - return dllPaths.every(dllPath => this.existsSync(dllPath)); + return dllPaths.every((dllPath) => this.existsSync(dllPath)); } existsSync(filePath) { @@ -223,7 +223,7 @@ export class DllCompiler { const entryPaths = this.getEntryPaths(); const entryFilesContent = await Promise.all( - entryPaths.map(async entryPath => await this.readFile(entryPath)) + entryPaths.map(async (entryPath) => await this.readFile(entryPath)) ); // merge all the module contents from entry files again into @@ -304,7 +304,7 @@ export class DllCompiler { // bundled inside the dll bundle const notAllowedModules = []; - stats.compilation.modules.forEach(module => { + stats.compilation.modules.forEach((module) => { // ignore if no module or userRequest are defined if (!module || !module.resource) { return; @@ -327,7 +327,7 @@ export class DllCompiler { if (notInNodeModulesOrWebpackShims(module.resource)) { const reasons = module.reasons || []; - reasons.forEach(reason => { + reasons.forEach((reason) => { // Skip if we can't read the reason info if (!reason || !reason.module || !reason.module.resource) { return; diff --git a/src/optimize/dynamic_dll_plugin/dll_entry_template.js b/src/optimize/dynamic_dll_plugin/dll_entry_template.js index 0c286896d0b71..351bed4e591ba 100644 --- a/src/optimize/dynamic_dll_plugin/dll_entry_template.js +++ b/src/optimize/dynamic_dll_plugin/dll_entry_template.js @@ -19,7 +19,7 @@ export function dllEntryTemplate(requirePaths = []) { return requirePaths - .map(path => `require('${path}');`) + .map((path) => `require('${path}');`) .sort() .join('\n'); } @@ -33,9 +33,5 @@ export function dllEntryFileContentArrayToString(content = []) { } export function dllMergeAllEntryFilesContent(content = []) { - return content - .join('\n') - .split('\n') - .sort() - .join('\n'); + return content.join('\n').split('\n').sort().join('\n'); } diff --git a/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js b/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js index 484c7dfbfd595..fb6f6e097b5cd 100644 --- a/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js +++ b/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js @@ -72,7 +72,7 @@ export class DynamicDllPlugin { const dllContext = rawDllConfig.context; const dllManifestPaths = this.dllCompiler.getManifestPaths(); - dllManifestPaths.forEach(dllChunkManifestPath => { + dllManifestPaths.forEach((dllChunkManifestPath) => { new webpack.DllReferencePlugin({ context: dllContext, manifest: dllChunkManifestPath, @@ -109,10 +109,10 @@ export class DynamicDllPlugin { registerBeforeCompileHook(compiler) { compiler.hooks.beforeCompile.tapPromise('DynamicDllPlugin', async ({ normalModuleFactory }) => { - normalModuleFactory.hooks.factory.tap('DynamicDllPlugin', actualFactory => (params, cb) => { + normalModuleFactory.hooks.factory.tap('DynamicDllPlugin', (actualFactory) => (params, cb) => { // This is used in order to avoid the cache for DLL modules // resolved from other dependencies - normalModuleFactory.cachePredicate = module => + normalModuleFactory.cachePredicate = (module) => !(module.stubType === DLL_ENTRY_STUB_MODULE_TYPE); // Overrides the normalModuleFactory module creation behaviour @@ -123,7 +123,7 @@ export class DynamicDllPlugin { } else { this.mapNormalModule(module).then( (m = module) => cb(undefined, m), - error => cb(error) + (error) => cb(error) ); } }); @@ -132,7 +132,7 @@ export class DynamicDllPlugin { } registerCompilationHook(compiler) { - compiler.hooks.compilation.tap('DynamicDllPlugin', compilation => { + compiler.hooks.compilation.tap('DynamicDllPlugin', (compilation) => { compilation.hooks.needAdditionalPass.tap('DynamicDllPlugin', () => { // Run the procedures in order to execute our dll compilation // The process is very straightforward in it's conception: @@ -215,7 +215,7 @@ export class DynamicDllPlugin { } registerDoneHook(compiler) { - compiler.hooks.done.tapPromise('DynamicDllPlugin', async stats => { + compiler.hooks.done.tapPromise('DynamicDllPlugin', async (stats) => { if (stats.compilation.needsDLLCompilation) { // Run the dlls compiler and increment // the performed compilations @@ -341,7 +341,7 @@ export class DynamicDllPlugin { // that we rely in next ones. this.dllCompiler .getManifestPaths() - .forEach(chunkDllManifestPath => mainCompiler.inputFileSystem.purge(chunkDllManifestPath)); + .forEach((chunkDllManifestPath) => mainCompiler.inputFileSystem.purge(chunkDllManifestPath)); this.performedCompilations++; diff --git a/src/optimize/fs_optimizer.js b/src/optimize/fs_optimizer.js index 03e413f04eb46..71a4c85a9e5a5 100644 --- a/src/optimize/fs_optimizer.js +++ b/src/optimize/fs_optimizer.js @@ -26,7 +26,7 @@ export default class FsOptimizer extends BaseOptimizer { await this.init(); } - await fromNode(cb => { + await fromNode((cb) => { this.compiler.run((err, stats) => { if (err || !stats) return cb(err); diff --git a/src/optimize/np_ui_plugin_public_dirs.ts b/src/optimize/np_ui_plugin_public_dirs.ts index e7c3207948f6a..8b49ffb74cb08 100644 --- a/src/optimize/np_ui_plugin_public_dirs.ts +++ b/src/optimize/np_ui_plugin_public_dirs.ts @@ -36,7 +36,7 @@ export function isNpUiPluginPublicDirs(x: any): x is NpUiPluginPublicDirs { return ( Array.isArray(x) && x.every( - s => typeof s === 'object' && s && typeof s.id === 'string' && typeof s.path === 'string' + (s) => typeof s === 'object' && s && typeof s.id === 'string' && typeof s.path === 'string' ) ); } diff --git a/src/optimize/public_path_placeholder.ts b/src/optimize/public_path_placeholder.ts index 1ec2b4a431aa6..d5f57a1586774 100644 --- a/src/optimize/public_path_placeholder.ts +++ b/src/optimize/public_path_placeholder.ts @@ -38,7 +38,7 @@ export function replacePlaceholder(read: Stream.Readable, replacement: string) { // choose what to do with them. Rx.fromEvent(read, 'error') .pipe(take(1), takeUntil(Rx.fromEvent(read, 'end'))) - .forEach(error => { + .forEach((error) => { replace.emit('error', error); replace.end(); }); diff --git a/src/optimize/watch/optmzr_role.js b/src/optimize/watch/optmzr_role.js index 1f6107996277c..ba8007e1065b4 100644 --- a/src/optimize/watch/optmzr_role.js +++ b/src/optimize/watch/optmzr_role.js @@ -71,7 +71,7 @@ export default async (kbnServer, kibanaHapiServer, config) => { process.send(['WORKER_BROADCAST', { optimizeReady: ready }]); }; - process.on('message', msg => { + process.on('message', (msg) => { if (msg && msg.optimizeReady === '?') sendReady(); }); diff --git a/src/optimize/watch/proxy_role.js b/src/optimize/watch/proxy_role.js index 0f6f3b2d4b622..ce2d63aa2eff0 100644 --- a/src/optimize/watch/proxy_role.js +++ b/src/optimize/watch/proxy_role.js @@ -30,7 +30,7 @@ export default (kbnServer, server, config) => { }) ); - return fromNode(cb => { + return fromNode((cb) => { const timeout = setTimeout(() => { cb(new Error('Timeout waiting for the optimizer to become ready')); }, config.get('optimize.watchProxyTimeout')); @@ -42,7 +42,7 @@ export default (kbnServer, server, config) => { if (!process.connected) return; process.send(['WORKER_BROADCAST', { optimizeReady: '?' }]); - process.on('message', msg => { + process.on('message', (msg) => { switch (get(msg, 'optimizeReady')) { case true: clearTimeout(timeout); diff --git a/src/optimize/watch/watch.js b/src/optimize/watch/watch.js index a84fcc59e13f5..a284da11f294f 100644 --- a/src/optimize/watch/watch.js +++ b/src/optimize/watch/watch.js @@ -19,7 +19,7 @@ import { isWorker } from 'cluster'; -export default async kbnServer => { +export default async (kbnServer) => { if (!isWorker) { throw new Error(`watch optimization is only available when using the "--dev" cli flag`); } diff --git a/src/optimize/watch/watch_cache.ts b/src/optimize/watch/watch_cache.ts index b6784c1734a17..40bd1d6075f47 100644 --- a/src/optimize/watch/watch_cache.ts +++ b/src/optimize/watch/watch_cache.ts @@ -174,7 +174,7 @@ async function recursiveDelete(directory: string) { const entries = await readdirAsync(directory, { withFileTypes: true }); await Promise.all( - entries.map(entry => { + entries.map((entry) => { const absolutePath = path.join(directory, entry.name); return entry.isDirectory() ? recursiveDelete(absolutePath) : unlinkAsync(absolutePath); }) diff --git a/src/optimize/watch/watch_optimizer.js b/src/optimize/watch/watch_optimizer.js index cdff57a00c2e0..816185e544ab5 100644 --- a/src/optimize/watch/watch_optimizer.js +++ b/src/optimize/watch/watch_optimizer.js @@ -83,7 +83,7 @@ export default class WatchOptimizer extends BaseOptimizer { registerCompilerDoneHook() { super.registerCompilerDoneHook(); - this.compiler.hooks.done.tap('watch_optimizer-done', stats => { + this.compiler.hooks.done.tap('watch_optimizer-done', (stats) => { if (stats.compilation.needAdditionalPass) { return; } @@ -145,7 +145,7 @@ export default class WatchOptimizer extends BaseOptimizer { } } - compilerWatchErrorHandler = error => { + compilerWatchErrorHandler = (error) => { if (error) { this.status$.next({ type: STATUS.FATAL, diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx index 97abdcb10016d..d8853015d362a 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx @@ -125,7 +125,7 @@ export class AdvancedSettingsComponent extends Component< mapConfig(config: IUiSettingsClient) { const all = config.getAll(); return Object.entries(all) - .map(setting => { + .map((setting) => { return toEditableConfig({ def: setting[1], name: setting[0], @@ -134,7 +134,7 @@ export class AdvancedSettingsComponent extends Component< isOverridden: config.isOverridden(setting[0]), }); }) - .filter(c => !c.readonly) + .filter((c) => !c.readonly) .sort(Comparators.property('name', Comparators.default('asc'))); } diff --git a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx b/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx index cb2999830b053..9e06da5f79008 100644 --- a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx @@ -60,8 +60,8 @@ export class AdvancedSettingsVoiceAnnouncement extends Component { }; render() { - const filteredSections = Object.values(this.props.settings).map(setting => - setting.map(option => option.ariaName) + const filteredSections = Object.values(this.props.settings).map((setting) => + setting.map((option) => option.ariaName) ); const filteredOptions = [...filteredSections]; return ( diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx index 356e38c799659..72992c190e8ae 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx @@ -205,7 +205,7 @@ const getFieldSettingValue = (wrapper: ReactWrapper, name: string, type: string) }; describe('Field', () => { - Object.keys(settings).forEach(type => { + Object.keys(settings).forEach((type) => { const setting = settings[type]; describe(`for ${type} setting`, () => { @@ -325,10 +325,10 @@ describe('Field', () => { ); const select = findTestSubject(component, `advancedSetting-editField-${setting.name}`); // @ts-ignore - const values = select.find('option').map(option => option.prop('value')); + const values = select.find('option').map((option) => option.prop('value')); expect(values).toEqual(['apple', 'orange', 'banana']); // @ts-ignore - const labels = select.find('option').map(option => option.text()); + const labels = select.find('option').map((option) => option.text()); expect(labels).toEqual(['Apple', 'Orange', 'banana']); }); } diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.tsx index 60d2b55dfceb4..32bfc0826e7c4 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/field/field.tsx @@ -266,7 +266,7 @@ export class Field extends PureComponent { reader.onload = () => { resolve(reader.result || undefined); }; - reader.onerror = err => { + reader.onerror = (err) => { reject(err); }; }); @@ -378,7 +378,7 @@ export class Field extends PureComponent { { + options={(options as string[]).map((option) => { return { text: optionLabels.hasOwnProperty(option) ? optionLabels[option] : option, value: option, diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx index 2cd4d3c0b43ec..142ea06c7dce4 100644 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx @@ -82,7 +82,7 @@ export class Form extends PureComponent { getSettingByKey = (key: string): FieldSetting | undefined => { return Object.values(this.props.settings) .flat() - .find(el => el.name === key); + .find((el) => el.name === key); }; getCountOfUnsavedChanges = (): number => { @@ -92,8 +92,8 @@ export class Form extends PureComponent { getCountOfHiddenUnsavedChanges = (): number => { const shownSettings = Object.values(this.props.visibleSettings) .flat() - .map(setting => setting.name); - return Object.keys(this.state.unsavedChanges).filter(key => !shownSettings.includes(key)) + .map((setting) => setting.name); + return Object.keys(this.state.unsavedChanges).filter((key) => !shownSettings.includes(key)) .length; }; @@ -256,7 +256,7 @@ export class Form extends PureComponent { - {settings.map(setting => { + {settings.map((setting) => { return ( { // TODO #64541 // Delete these classes let bottomBarClasses = ''; - const pageNav = this.props.settings.general.find(setting => setting.name === 'pageNavigation'); + const pageNav = this.props.settings.general.find( + (setting) => setting.name === 'pageNavigation' + ); if (pageNav?.value === 'legacy') { bottomBarClasses = classNames('mgtAdvancedSettingsForm__bottomBar', { @@ -401,7 +403,7 @@ export class Form extends PureComponent { const { visibleSettings, categories, categoryCounts, clearQuery } = this.props; const currentCategories: Category[] = []; - categories.forEach(category => { + categories.forEach((category) => { if (visibleSettings[category] && visibleSettings[category].length) { currentCategories.push(category); } @@ -411,7 +413,7 @@ export class Form extends PureComponent {

{currentCategories.length - ? currentCategories.map(category => { + ? currentCategories.map((category) => { return this.renderCategory( category, visibleSettings[category], diff --git a/src/plugins/advanced_settings/public/management_app/components/search/search.tsx b/src/plugins/advanced_settings/public/management_app/components/search/search.tsx index 74e4894a27a6d..e440159b0ed71 100644 --- a/src/plugins/advanced_settings/public/management_app/components/search/search.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/search/search.tsx @@ -35,7 +35,7 @@ export class Search extends PureComponent { constructor(props: SearchProps) { super(props); const { categories } = props; - this.categories = categories.map(category => { + this.categories = categories.map((category) => { return { value: category, name: getCategoryName(category), diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts index e129481a397c1..f27f58c599cca 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts @@ -20,16 +20,16 @@ import expect from '@kbn/expect'; import { getAriaName } from './get_aria_name'; -describe('Settings', function() { - describe('Advanced', function() { - describe('getAriaName(name)', function() { - it('should return a space delimited lower-case string with no special characters', function() { +describe('Settings', function () { + describe('Advanced', function () { + describe('getAriaName(name)', function () { + it('should return a space delimited lower-case string with no special characters', function () { expect(getAriaName('xPack:defaultAdminEmail')).to.be('x pack default admin email'); expect(getAriaName('doc_table:highlight')).to.be('doc table highlight'); expect(getAriaName('foo')).to.be('foo'); }); - it('should return an empty string if passed undefined or null', function() { + it('should return an empty string if passed undefined or null', function () { expect(getAriaName()).to.be(''); expect(getAriaName(undefined)).to.be(''); }); diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts index d5c2be752a278..c33ac6abafb54 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts @@ -28,6 +28,6 @@ import { words } from 'lodash'; */ export function getAriaName(name?: string) { return words(name || '') - .map(word => word.toLowerCase()) + .map((word) => word.toLowerCase()) .join(' '); } diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts index 73e303e20c64d..74b61587524f5 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts @@ -20,14 +20,14 @@ import expect from '@kbn/expect'; import { getCategoryName } from './get_category_name'; -describe('Settings', function() { - describe('Advanced', function() { - describe('getCategoryName(category)', function() { - it('should capitalize unknown category', function() { +describe('Settings', function () { + describe('Advanced', function () { + describe('getCategoryName(category)', function () { + it('should capitalize unknown category', function () { expect(getCategoryName('elasticsearch')).to.be('Elasticsearch'); }); - it('should return empty string for no category', function() { + it('should return empty string for no category', function () { expect(getCategoryName()).to.be(''); expect(getCategoryName('')).to.be(''); }); diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts index 46d28ce9d5c40..31df6875c97d9 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -const upperFirst = (str = '') => str.replace(/^./, strng => strng.toUpperCase()); +const upperFirst = (str = '') => str.replace(/^./, (strng) => strng.toUpperCase()); const names: Record = { general: i18n.translate('advancedSettings.categoryNames.generalLabel', { @@ -46,8 +46,8 @@ const names: Record = { search: i18n.translate('advancedSettings.categoryNames.searchLabel', { defaultMessage: 'Search', }), - siem: i18n.translate('advancedSettings.categoryNames.siemLabel', { - defaultMessage: 'SIEM', + securitySolution: i18n.translate('advancedSettings.categoryNames.securitySolutionLabel', { + defaultMessage: 'Security Solution', }), }; diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts b/src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts index ec59dcaa1ea29..e971f545fd605 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts @@ -20,16 +20,16 @@ import expect from '@kbn/expect'; import { getValType } from './get_val_type'; -describe('Settings', function() { - describe('Advanced', function() { - describe('getValType(def, val)', function() { - it('should return the explicitly defined type of a setting', function() { +describe('Settings', function () { + describe('Advanced', function () { + describe('getValType(def, val)', function () { + it('should return the explicitly defined type of a setting', function () { expect(getValType({ type: 'string' })).to.be('string'); expect(getValType({ type: 'json' })).to.be('json'); expect(getValType({ type: 'string', value: 5 })).to.be('string'); }); - it('should return array if the value is an Array and there is no defined type', function() { + it('should return array if the value is an Array and there is no defined type', function () { expect(getValType({ type: 'string' }, [1, 2, 3])).to.be('string'); expect(getValType({ type: 'json', value: [1, 2, 3] })).to.be('json'); @@ -37,12 +37,12 @@ describe('Settings', function() { expect(getValType({ value: [1, 2, 3] }, 'someString')).to.be('array'); }); - it('should return the type of the default value if there is no type and it is not an array', function() { + it('should return the type of the default value if there is no type and it is not an array', function () { expect(getValType({ value: 'someString' })).to.be('string'); expect(getValType({ value: 'someString' }, 42)).to.be('string'); }); - it('should return the type of the value if the default value is null', function() { + it('should return the type of the value if the default value is null', function () { expect(getValType({ value: null }, 'someString')).to.be('string'); }); }); diff --git a/src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts b/src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts index 836dcb6b87676..4cde8bf517499 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts @@ -21,10 +21,10 @@ import expect from '@kbn/expect'; import { isDefaultValue } from './is_default_value'; import { UiSettingsType } from '../../../../../core/public'; -describe('Settings', function() { - describe('Advanced', function() { - describe('getCategoryName(category)', function() { - describe('when given a setting definition object', function() { +describe('Settings', function () { + describe('Advanced', function () { + describe('getCategoryName(category)', function () { + describe('when given a setting definition object', function () { const setting = { isCustom: false, value: 'value', @@ -43,21 +43,21 @@ describe('Settings', function() { validation: { regex: /regexString/, message: 'validation description' }, }; - describe('that is custom', function() { - it('should return true', function() { + describe('that is custom', function () { + it('should return true', function () { expect(isDefaultValue({ ...setting, isCustom: true })).to.be(true); }); }); - describe('without a value', function() { - it('should return true', function() { + describe('without a value', function () { + it('should return true', function () { expect(isDefaultValue({ ...setting, value: undefined })).to.be(true); expect(isDefaultValue({ ...setting, value: '' })).to.be(true); }); }); - describe('with a value that is the same as the default value', function() { - it('should return true', function() { + describe('with a value that is the same as the default value', function () { + it('should return true', function () { expect(isDefaultValue({ ...setting, value: 'defaultValue' })).to.be(true); expect(isDefaultValue({ ...setting, value: [], defVal: [] })).to.be(true); expect( @@ -69,8 +69,8 @@ describe('Settings', function() { }); }); - describe('with a value that is different than the default value', function() { - it('should return false', function() { + describe('with a value that is different than the default value', function () { + it('should return false', function () { expect(isDefaultValue({ ...setting })).to.be(false); expect(isDefaultValue({ ...setting, value: [1], defVal: [2] })).to.be(false); expect( diff --git a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts index 7ac9b281eb99a..0f7edb4bd6332 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts @@ -37,26 +37,26 @@ function invoke({ return toEditableConfig({ def, name, value, isCustom: def === defDefault, isOverridden: true }); } -describe('Settings', function() { - describe('Advanced', function() { - describe('toEditableConfig(def, name, value)', function() { - it('sets name', function() { +describe('Settings', function () { + describe('Advanced', function () { + describe('toEditableConfig(def, name, value)', function () { + it('sets name', function () { expect(invoke({ name: 'who' }).name).to.equal('who'); }); - it('sets value', function() { + it('sets value', function () { expect(invoke({ value: 'what' }).value).to.equal('what'); }); - it('sets type', function() { + it('sets type', function () { expect(invoke({ value: 'what' }).type).to.be('string'); expect(invoke({ value: 0 }).type).to.be('number'); expect(invoke({ value: [] }).type).to.be('array'); }); - describe('when given a setting definition object', function() { + describe('when given a setting definition object', function () { let def: PublicUiSettingsParams & { isOverridden?: boolean }; - beforeEach(function() { + beforeEach(function () { def = { value: 'the original', description: 'the one and only', @@ -64,38 +64,38 @@ describe('Settings', function() { }; }); - it('is not marked as custom', function() { + it('is not marked as custom', function () { expect(invoke({ def }).isCustom).to.be(false); }); - it('sets a default value', function() { + it('sets a default value', function () { expect(invoke({ def }).defVal).to.equal(def.value); }); - it('sets a description', function() { + it('sets a description', function () { expect(invoke({ def }).description).to.equal(def.description); }); - it('sets options', function() { + it('sets options', function () { expect(invoke({ def }).options).to.equal(def.options); }); - describe('that contains a type', function() { - it('sets that type', function() { + describe('that contains a type', function () { + it('sets that type', function () { def.type = 'string'; expect(invoke({ def }).type).to.equal(def.type); }); }); - describe('that contains a value of type array', function() { - it('sets type to array', function() { + describe('that contains a value of type array', function () { + it('sets type to array', function () { def.value = []; expect(invoke({ def }).type).to.equal('array'); }); }); - describe('that contains a validation object', function() { - it('constructs a validation regex with message', function() { + describe('that contains a validation object', function () { + it('constructs a validation regex with message', function () { def.validation = { regexString: '^foo', message: 'must start with "foo"', @@ -108,24 +108,24 @@ describe('Settings', function() { }); }); - describe('when not given a setting definition object', function() { - it('is marked as custom', function() { + describe('when not given a setting definition object', function () { + it('is marked as custom', function () { expect(invoke({}).isCustom).to.be(true); }); - it('sets defVal to undefined', function() { + it('sets defVal to undefined', function () { expect(invoke({}).defVal).to.be(undefined); }); - it('sets description to undefined', function() { + it('sets description to undefined', function () { expect(invoke({}).description).to.be(undefined); }); - it('sets options to undefined', function() { + it('sets options to undefined', function () { expect(invoke({}).options).to.be(undefined); }); - it('sets validation to undefined', function() { + it('sets validation to undefined', function () { expect(invoke({}).validation).to.be(undefined); }); }); diff --git a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx index df44ea45e9d01..b4779d051ab02 100644 --- a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx +++ b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx @@ -19,7 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter, Switch, Route } from 'react-router-dom'; +import { Router, Switch, Route } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; @@ -60,7 +60,7 @@ export async function mountManagementSection( ReactDOM.render( - + - + , params.element ); diff --git a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts b/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts index 271bcab21172f..d2a4ee8297a11 100644 --- a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts +++ b/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts @@ -644,8 +644,9 @@ export const createDotNetAgentInstructions = (apmServerUrl = '', secretToken = ' commands: `{curlyOpen} "ElasticApm": {curlyOpen} "SecretToken": "${secretToken}", - "ServerUrls": "${apmServerUrl || - 'http://localhost:8200'}", //Set custom APM Server URL (default: http://localhost:8200) + "ServerUrls": "${ + apmServerUrl || 'http://localhost:8200' + }", //Set custom APM Server URL (default: http://localhost:8200) "ServiceName" : "MyApp", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application {curlyClose} {curlyClose}`.split('\n'), diff --git a/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts b/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts index 5b145a2523070..c924cf3a04f1c 100644 --- a/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts +++ b/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts @@ -66,7 +66,7 @@ describe('createBatchedFunction', () => { expect(onCall).toHaveBeenCalledWith(123); expect(onBatch).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 15)); + await new Promise((r) => setTimeout(r, 15)); expect(onCall).toHaveBeenCalledTimes(1); expect(onBatch).toHaveBeenCalledTimes(1); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts index 064b791327e69..da6c940c48d0a 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -24,17 +24,17 @@ import { Subject } from 'rxjs'; const getPromiseState = (promise: Promise): Promise<'resolved' | 'rejected' | 'pending'> => Promise.race<'resolved' | 'rejected' | 'pending'>([ - new Promise(resolve => + new Promise((resolve) => promise.then( () => resolve('resolved'), () => resolve('rejected') ) ), - new Promise<'pending'>(resolve => resolve()).then(() => 'pending'), + new Promise<'pending'>((resolve) => resolve()).then(() => 'pending'), ]); const isPending = (promise: Promise): Promise => - getPromiseState(promise).then(state => state === 'pending'); + getPromiseState(promise).then((state) => state === 'pending'); const setup = () => { const xhr = ({} as unknown) as XMLHttpRequest; @@ -93,7 +93,7 @@ describe('createStreamingBatchedFunction()', () => { fn({ baz: 'quix' }); expect(fetchStreaming).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); expect(fetchStreaming).toHaveBeenCalledTimes(1); }); @@ -107,7 +107,7 @@ describe('createStreamingBatchedFunction()', () => { }); expect(fetchStreaming).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); expect(fetchStreaming).toHaveBeenCalledTimes(0); }); @@ -121,7 +121,7 @@ describe('createStreamingBatchedFunction()', () => { }); fn({ foo: 'bar' }); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); expect(fetchStreaming.mock.calls[0][0]).toMatchObject({ url: '/test', @@ -141,7 +141,7 @@ describe('createStreamingBatchedFunction()', () => { fn({ foo: 'bar' }); fn({ baz: 'quix' }); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); const { body } = fetchStreaming.mock.calls[0][0]; expect(JSON.parse(body)).toEqual({ batch: [{ foo: 'bar' }, { baz: 'quix' }], @@ -205,7 +205,7 @@ describe('createStreamingBatchedFunction()', () => { fn({ c: '3' }); expect(fetchStreaming).toHaveBeenCalledTimes(1); fn({ d: '4' }); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); expect(fetchStreaming).toHaveBeenCalledTimes(2); }); }); @@ -222,7 +222,7 @@ describe('createStreamingBatchedFunction()', () => { const promise1 = fn({ a: '1' }); const promise2 = fn({ b: '2' }); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); expect(await isPending(promise1)).toBe(true); expect(await isPending(promise2)).toBe(true); @@ -240,7 +240,7 @@ describe('createStreamingBatchedFunction()', () => { const promise1 = fn({ a: '1' }); const promise2 = fn({ b: '2' }); const promise3 = fn({ c: '3' }); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); expect(await isPending(promise1)).toBe(true); expect(await isPending(promise2)).toBe(true); @@ -281,7 +281,7 @@ describe('createStreamingBatchedFunction()', () => { const promise1 = fn({ a: '1' }); const promise2 = fn({ b: '2' }); const promise3 = fn({ c: '3' }); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); stream.next( JSON.stringify({ @@ -303,6 +303,47 @@ describe('createStreamingBatchedFunction()', () => { expect(await promise3).toEqual({ foo: 'bar 2' }); }); + test('resolves falsy results', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + const promise3 = fn({ c: '3' }); + await new Promise((r) => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 0, + result: false, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 1, + result: 0, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 2, + result: '', + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(false); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(false); + expect(await promise1).toEqual(false); + expect(await promise2).toEqual(0); + expect(await promise3).toEqual(''); + }); + test('rejects promise on error response', async () => { const { fetchStreaming, stream } = setup(); const fn = createStreamingBatchedFunction({ @@ -313,7 +354,7 @@ describe('createStreamingBatchedFunction()', () => { }); const promise = fn({ a: '1' }); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); expect(await isPending(promise)).toBe(true); @@ -344,7 +385,7 @@ describe('createStreamingBatchedFunction()', () => { const promise2 = of(fn({ a: '2' })); const promise3 = of(fn({ a: '3' })); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); stream.next( JSON.stringify({ @@ -353,7 +394,7 @@ describe('createStreamingBatchedFunction()', () => { }) + '\n' ); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); stream.next( JSON.stringify({ @@ -362,7 +403,7 @@ describe('createStreamingBatchedFunction()', () => { }) + '\n' ); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); stream.next( JSON.stringify({ @@ -371,7 +412,7 @@ describe('createStreamingBatchedFunction()', () => { }) + '\n' ); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); const [result1] = await promise1; const [, error2] = await promise2; @@ -395,11 +436,11 @@ describe('createStreamingBatchedFunction()', () => { const promise1 = of(fn({ a: '1' })); const promise2 = of(fn({ a: '2' })); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); stream.complete(); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); const [, error1] = await promise1; const [, error2] = await promise2; @@ -425,7 +466,7 @@ describe('createStreamingBatchedFunction()', () => { const promise1 = of(fn({ a: '1' })); const promise2 = of(fn({ a: '2' })); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); stream.next( JSON.stringify({ @@ -435,7 +476,7 @@ describe('createStreamingBatchedFunction()', () => { ); stream.complete(); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); const [, error1] = await promise1; const [result1] = await promise2; @@ -462,13 +503,13 @@ describe('createStreamingBatchedFunction()', () => { const promise1 = of(fn({ a: '1' })); const promise2 = of(fn({ a: '2' })); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); stream.error({ message: 'something went wrong', }); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); const [, error1] = await promise1; const [, error2] = await promise2; @@ -494,7 +535,7 @@ describe('createStreamingBatchedFunction()', () => { const promise1 = of(fn({ a: '1' })); const promise2 = of(fn({ a: '2' })); - await new Promise(r => setTimeout(r, 6)); + await new Promise((r) => setTimeout(r, 6)); stream.next( JSON.stringify({ @@ -504,7 +545,7 @@ describe('createStreamingBatchedFunction()', () => { ); stream.error('oops'); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); const [, error1] = await promise1; const [result1] = await promise2; diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts index 07d5724a2520d..89793fff6b325 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -91,7 +91,7 @@ export const createStreamingBatchedFunction = ( }; return [future.promise, entry]; }, - onBatch: async items => { + onBatch: async (items) => { try { let responsesReceived = 0; const batch = items.map(({ payload }) => payload); @@ -106,12 +106,12 @@ export const createStreamingBatchedFunction = ( if (response.error) { responsesReceived++; items[response.id].future.reject(response.error); - } else if (response.result) { + } else if (response.result !== undefined) { responsesReceived++; items[response.id].future.resolve(response.result); } }, - error: error => { + error: (error) => { const normalizedError = normalizeError(error); normalizedError.code = 'STREAM'; for (const { future } of items) future.reject(normalizedError); diff --git a/src/plugins/bfetch/public/plugin.ts b/src/plugins/bfetch/public/plugin.ts index 783c448c567e5..5f01957c0908e 100644 --- a/src/plugins/bfetch/public/plugin.ts +++ b/src/plugins/bfetch/public/plugin.ts @@ -78,7 +78,7 @@ export class BfetchPublicPlugin private fetchStreaming = ( version: string, basePath: string - ): BfetchPublicSetup['fetchStreaming'] => params => + ): BfetchPublicSetup['fetchStreaming'] => (params) => fetchStreamingStatic({ ...params, url: `${basePath}/${removeLeadingSlash(params.url)}`, @@ -91,7 +91,7 @@ export class BfetchPublicPlugin private batchedFunction = ( fetchStreaming: BfetchPublicContract['fetchStreaming'] - ): BfetchPublicContract['batchedFunction'] => params => + ): BfetchPublicContract['batchedFunction'] => (params) => createStreamingBatchedFunction({ ...params, fetchStreaming: params.fetchStreaming || fetchStreaming, diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts index 7845616026ea1..27adc6dc8b549 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts @@ -20,7 +20,7 @@ import { fetchStreaming } from './fetch_streaming'; import { mockXMLHttpRequest } from '../test_helpers/xhr'; -const tick = () => new Promise(resolve => setTimeout(resolve, 1)); +const tick = () => new Promise((resolve) => setTimeout(resolve, 1)); const setup = () => { const { xhr, XMLHttpRequest } = mockXMLHttpRequest(); diff --git a/src/plugins/bfetch/public/streaming/split.test.ts b/src/plugins/bfetch/public/streaming/split.test.ts index 6eb3e27ad8598..fb9dcfb210d36 100644 --- a/src/plugins/bfetch/public/streaming/split.test.ts +++ b/src/plugins/bfetch/public/streaming/split.test.ts @@ -26,7 +26,7 @@ test('splits a single IP address', () => { const subject = new Subject(); const splitted = split('.')(subject); - splitted.subscribe(value => list.push(value)); + splitted.subscribe((value) => list.push(value)); subject.next(ip); subject.complete(); @@ -56,7 +56,7 @@ for (const stream of streams) { const list: string[] = []; const subject = new Subject(); const splitted = split('.')(subject); - splitted.subscribe(value => list.push(value)); + splitted.subscribe((value) => list.push(value)); let i = 0; while (i < stream.length) { const len = Math.round(Math.random() * 10); diff --git a/src/plugins/bfetch/public/streaming/split.ts b/src/plugins/bfetch/public/streaming/split.ts index 665411f472ac3..c970e07c97e9f 100644 --- a/src/plugins/bfetch/public/streaming/split.ts +++ b/src/plugins/bfetch/public/streaming/split.ts @@ -40,7 +40,7 @@ export const split = (delimiter: string = '\n') => ( let startingText = ''; in$.subscribe( - chunk => { + (chunk) => { const messages = (startingText + chunk).split(delimiter); // We don't want to send the last message here, since it may or diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 0502781e34ce2..c27f34b8233cb 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -161,7 +161,7 @@ export class BfetchServerPlugin logger, }: { logger: Logger; - }): BfetchServerSetup['createStreamingRequestHandler'] => streamHandler => async ( + }): BfetchServerSetup['createStreamingRequestHandler'] => (streamHandler) => async ( context, request, response @@ -186,7 +186,7 @@ export class BfetchServerPlugin addStreamingResponseRoute< BatchRequestData, BatchResponseItem - >(path, request => { + >(path, (request) => { const handlerInstance = handler(request); return { getResponseStream: ({ batch }) => { diff --git a/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts index c567784becd16..2f476e95497e5 100644 --- a/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts +++ b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts @@ -39,7 +39,7 @@ export const createNDJSONStream = ( logger.error(error); } }, - error: error => { + error: (error) => { stream.end(); logger.error(error); }, diff --git a/src/plugins/charts/public/services/colors/color_palette.ts b/src/plugins/charts/public/services/colors/color_palette.ts index 7b8e93bbde240..464e9e3a66101 100644 --- a/src/plugins/charts/public/services/colors/color_palette.ts +++ b/src/plugins/charts/public/services/colors/color_palette.ts @@ -24,7 +24,7 @@ import { seedColors } from './seed_colors'; const offset = 300; // Hue offset to start at -const fraction = function(goal: number) { +const fraction = function (goal: number) { const walkTree = (numerator: number, denominator: number, bytes: number[]): number => { if (bytes.length) { return walkTree(numerator * 2 + (bytes.pop() ? 1 : -1), denominator * 2, bytes); @@ -36,7 +36,7 @@ const fraction = function(goal: number) { const b = (goal + 2) .toString(2) .split('') - .map(function(num) { + .map(function (num) { return parseInt(num, 10); }); b.shift(); @@ -57,7 +57,7 @@ export function createColorPalette(num?: any): string[] { const colors = seedColors; const seedLength = seedColors.length; - _.times(num - seedLength, function(i) { + _.times(num - seedLength, function (i) { colors.push(d3.hsl((fraction(i + seedLength + 1) * 360 + offset) % 360, 0.5, 0.5).toString()); }); diff --git a/src/plugins/charts/public/services/colors/colors.test.ts b/src/plugins/charts/public/services/colors/colors.test.ts index e3f99f2407f75..a4d7a0781eabd 100644 --- a/src/plugins/charts/public/services/colors/colors.test.ts +++ b/src/plugins/charts/public/services/colors/colors.test.ts @@ -28,7 +28,7 @@ const config = new Map(); describe('Vislib Color Service', () => { const colors = new ColorsService(); const mockUiSettings = coreMock.createSetup().uiSettings; - mockUiSettings.get.mockImplementation(a => config.get(a)); + mockUiSettings.get.mockImplementation((a) => config.get(a)); mockUiSettings.set.mockImplementation((...a) => config.set(...a) as any); colors.init(mockUiSettings); diff --git a/src/plugins/charts/public/services/colors/colors.ts b/src/plugins/charts/public/services/colors/colors.ts index 3c12adf84d8b2..7a1ffc433ee87 100644 --- a/src/plugins/charts/public/services/colors/colors.ts +++ b/src/plugins/charts/public/services/colors/colors.ts @@ -57,7 +57,7 @@ export class ColorsService { ); } - arrayOfStringsOrNumbers.forEach(function(val) { + arrayOfStringsOrNumbers.forEach(function (val) { if (!_.isString(val) && !_.isNumber(val) && !_.isUndefined(val)) { throw new TypeError( 'createColorLookupFunction expects an array of strings, numbers, or undefined values' diff --git a/src/plugins/charts/public/services/colors/mapped_colors.test.ts b/src/plugins/charts/public/services/colors/mapped_colors.test.ts index c3b9b0909051c..2c9f37afc14c5 100644 --- a/src/plugins/charts/public/services/colors/mapped_colors.test.ts +++ b/src/plugins/charts/public/services/colors/mapped_colors.test.ts @@ -30,7 +30,7 @@ const config = new Map(); describe('Mapped Colors', () => { const mockUiSettings = coreMock.createSetup().uiSettings; - mockUiSettings.get.mockImplementation(a => config.get(a)); + mockUiSettings.get.mockImplementation((a) => config.get(a)); mockUiSettings.set.mockImplementation((...a) => config.set(...a) as any); const mappedColors = new MappedColors(mockUiSettings); @@ -50,12 +50,7 @@ describe('Mapped Colors', () => { const arr = [1, 2, 3, 4, 5]; mappedColors.mapKeys(arr); - expect( - _(mappedColors.mapping) - .values() - .uniq() - .size() - ).toBe(arr.length); + expect(_(mappedColors.mapping).values().uniq().size()).toBe(arr.length); }); it('should not include colors used by the config', () => { @@ -77,15 +72,8 @@ describe('Mapped Colors', () => { const arr = ['foo', 'bar', 'baz', 'qux']; mappedColors.mapKeys(arr); - const expectedSize = _(arr) - .difference(_.keys(newConfig)) - .size(); - expect( - _(mappedColors.mapping) - .values() - .uniq() - .size() - ).toBe(expectedSize); + const expectedSize = _(arr).difference(_.keys(newConfig)).size(); + expect(_(mappedColors.mapping).values().uniq().size()).toBe(expectedSize); expect(mappedColors.get(arr[0])).not.toBe(seedColors[0]); }); @@ -98,20 +86,13 @@ describe('Mapped Colors', () => { const arr = ['foo', 'bar', 'baz', 'qux']; mappedColors.mapKeys(arr); - const expectedSize = _(arr) - .difference(_.keys(newConfig)) - .size(); - expect( - _(mappedColors.mapping) - .values() - .uniq() - .size() - ).toBe(expectedSize); + const expectedSize = _(arr).difference(_.keys(newConfig)).size(); + expect(_(mappedColors.mapping).values().uniq().size()).toBe(expectedSize); expect(mappedColors.get(arr[0])).not.toBe(seedColors[0]); expect(mappedColors.get('bar')).toBe(seedColors[0]); }); - it('should have a flush method that moves the current map to the old map', function() { + it('should have a flush method that moves the current map to the old map', function () { const arr = [1, 2, 3, 4, 5]; mappedColors.mapKeys(arr); expect(_.keys(mappedColors.mapping).length).toBe(5); @@ -128,7 +109,7 @@ describe('Mapped Colors', () => { expect(_.keys(mappedColors.mapping).length).toBe(0); }); - it('should use colors in the oldMap if they are available', function() { + it('should use colors in the oldMap if they are available', function () { const arr = [1, 2, 3, 4, 5]; mappedColors.mapKeys(arr); expect(_.keys(mappedColors.mapping).length).toBe(5); @@ -147,7 +128,7 @@ describe('Mapped Colors', () => { expect(mappedColors.mapping[5]).toEqual(mappedColors.oldMap[5]); }); - it('should have a purge method that clears both maps', function() { + it('should have a purge method that clears both maps', function () { const arr = [1, 2, 3, 4, 5]; mappedColors.mapKeys(arr); mappedColors.flush(); diff --git a/src/plugins/charts/public/services/colors/mapped_colors.ts b/src/plugins/charts/public/services/colors/mapped_colors.ts index 1469d357e7e79..fe0deac734e6b 100644 --- a/src/plugins/charts/public/services/colors/mapped_colors.ts +++ b/src/plugins/charts/public/services/colors/mapped_colors.ts @@ -73,7 +73,7 @@ export class MappedColors { const oldColors = _.values(this._oldMap); const keysToMap: Array = []; - _.each(keys, key => { + _.each(keys, (key) => { // If this key is mapped in the config, it's unnecessary to have it mapped here if (configMapping[key]) delete this._mapping[key]; @@ -88,11 +88,7 @@ export class MappedColors { }); // Generate a color palette big enough that all new keys can have unique color values - const allColors = _(this._mapping) - .values() - .union(configColors) - .union(oldColors) - .value(); + const allColors = _(this._mapping).values().union(configColors).union(oldColors).value(); const colorPalette = createColorPalette(allColors.length + keysToMap.length); let newColors = _.difference(colorPalette, allColors); diff --git a/src/plugins/charts/public/services/colors/seed_colors.test.ts b/src/plugins/charts/public/services/colors/seed_colors.test.ts index c4aac726e49e7..51a96409a4ec1 100644 --- a/src/plugins/charts/public/services/colors/seed_colors.test.ts +++ b/src/plugins/charts/public/services/colors/seed_colors.test.ts @@ -19,8 +19,8 @@ import { seedColors } from './seed_colors'; -describe('Seed Colors', function() { - it('should return an array', function() { +describe('Seed Colors', function () { + it('should return an array', function () { expect(seedColors).toBeInstanceOf(Array); }); }); diff --git a/src/plugins/charts/public/services/theme/theme.ts b/src/plugins/charts/public/services/theme/theme.ts index 65d4dc3393658..166e1c539688a 100644 --- a/src/plugins/charts/public/services/theme/theme.ts +++ b/src/plugins/charts/public/services/theme/theme.ts @@ -57,7 +57,7 @@ export class ThemeService { this._chartsTheme$ = uiSettings .get$('theme:darkMode') .pipe( - map(darkMode => (darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme)) + map((darkMode) => (darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme)) ); } } diff --git a/src/plugins/charts/public/static/color_maps/heatmap_color.ts b/src/plugins/charts/public/static/color_maps/heatmap_color.ts index afc649b68826e..9c3067fd8d6ac 100644 --- a/src/plugins/charts/public/static/color_maps/heatmap_color.ts +++ b/src/plugins/charts/public/static/color_maps/heatmap_color.ts @@ -37,7 +37,7 @@ function interpolateLinearly(x: number, values: RawColorSchema['value']) { const rValues: number[] = []; const gValues: number[] = []; const bValues: number[] = []; - values.forEach(value => { + values.forEach((value) => { xValues.push(value[0]); rValues.push(value[1][0]); gValues.push(value[1][1]); diff --git a/src/plugins/charts/public/static/components/number_input.tsx b/src/plugins/charts/public/static/components/number_input.tsx index b614e00ba8cd0..8c2874f522902 100644 --- a/src/plugins/charts/public/static/components/number_input.tsx +++ b/src/plugins/charts/public/static/components/number_input.tsx @@ -65,7 +65,7 @@ function NumberInputOption({ max={max} min={min} value={value} - onChange={ev => + onChange={(ev) => setValue(paramName, isNaN(ev.target.valueAsNumber) ? '' : ev.target.valueAsNumber) } /> diff --git a/src/plugins/charts/public/static/components/select.tsx b/src/plugins/charts/public/static/components/select.tsx index 8ce7f4d640898..31cc1cd14e2f6 100644 --- a/src/plugins/charts/public/static/components/select.tsx +++ b/src/plugins/charts/public/static/components/select.tsx @@ -63,7 +63,7 @@ function SelectOption setValue(paramName, ev.target.value as ValidParamValues)} + onChange={(ev) => setValue(paramName, ev.target.value as ValidParamValues)} fullWidth={true} data-test-subj={dataTestSubj} /> diff --git a/src/plugins/charts/public/static/components/switch.tsx b/src/plugins/charts/public/static/components/switch.tsx index bdaf602cad5c6..a873e6d15feeb 100644 --- a/src/plugins/charts/public/static/components/switch.tsx +++ b/src/plugins/charts/public/static/components/switch.tsx @@ -49,7 +49,7 @@ function SwitchOption({ checked={value} disabled={disabled} data-test-subj={dataTestSubj} - onChange={ev => setValue(paramName, ev.target.checked)} + onChange={(ev) => setValue(paramName, ev.target.checked)} /> diff --git a/src/plugins/charts/public/static/components/text_input.tsx b/src/plugins/charts/public/static/components/text_input.tsx index 0ab004102bdb8..03486c7056d62 100644 --- a/src/plugins/charts/public/static/components/text_input.tsx +++ b/src/plugins/charts/public/static/components/text_input.tsx @@ -47,7 +47,7 @@ function TextInputOption({ data-test-subj={dataTestSubj} disabled={disabled} value={value} - onChange={ev => setValue(paramName, ev.target.value)} + onChange={(ev) => setValue(paramName, ev.target.value)} /> ); diff --git a/src/plugins/console/public/application/components/console_menu.tsx b/src/plugins/console/public/application/components/console_menu.tsx index 7842c15be267f..df0c8145fd5ce 100644 --- a/src/plugins/console/public/application/components/console_menu.tsx +++ b/src/plugins/console/public/application/components/console_menu.tsx @@ -48,7 +48,7 @@ export class ConsoleMenu extends Component { mouseEnter = () => { if (this.state.isPopoverOpen) return; - this.props.getCurl().then(text => { + this.props.getCurl().then((text) => { this.setState({ curlCode: text }); }); }; @@ -75,7 +75,7 @@ export class ConsoleMenu extends Component { } onButtonClick = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; diff --git a/src/plugins/console/public/application/components/settings_modal.tsx b/src/plugins/console/public/application/components/settings_modal.tsx index 3cd20bf1bc343..377e739a0c59a 100644 --- a/src/plugins/console/public/application/components/settings_modal.tsx +++ b/src/plugins/console/public/application/components/settings_modal.tsx @@ -87,7 +87,7 @@ export function DevToolsSettingsModal(props: Props) { }; const onAutocompleteChange = (optionId: AutocompleteOptions) => { - const option = _.find(autoCompleteCheckboxes, item => item.id === optionId); + const option = _.find(autoCompleteCheckboxes, (item) => item.id === optionId); if (option) { option.stateSetter(!checkboxIdToSelectedMap[optionId]); } @@ -136,7 +136,7 @@ export function DevToolsSettingsModal(props: Props) { id="console.settingsPage.pollingLabelText" /> } - onChange={e => setPolling(e.target.checked)} + onChange={(e) => setPolling(e.target.checked)} /> @@ -158,9 +158,7 @@ export function DevToolsSettingsModal(props: Props) { /> - ) : ( - undefined - ); + ) : undefined; return ( @@ -193,7 +191,7 @@ export function DevToolsSettingsModal(props: Props) { value={fontSize} min={6} max={50} - onChange={e => { + onChange={(e) => { const val = parseInt(e.target.value, 10); if (!val) return; setFontSize(val); @@ -212,7 +210,7 @@ export function DevToolsSettingsModal(props: Props) { id="console.settingsPage.wrapLongLinesLabelText" /> } - onChange={e => setWrapMode(e.target.checked)} + onChange={(e) => setWrapMode(e.target.checked)} /> @@ -234,7 +232,7 @@ export function DevToolsSettingsModal(props: Props) { id="console.settingsPage.tripleQuotesMessage" /> } - onChange={e => setTripleQuotes(e.target.checked)} + onChange={(e) => setTripleQuotes(e.target.checked)} /> @@ -248,7 +246,7 @@ export function DevToolsSettingsModal(props: Props) { } > { + options={autoCompleteCheckboxes.map((opts) => { const { stateSetter, ...rest } = opts; return rest; })} diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index 47f7aa1635205..6d4f532887cd9 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -119,7 +119,7 @@ function EditorUI({ initialTextValue }: EditorProps) { } // Fire and forget. - $.ajax(loadFrom).done(async data => { + $.ajax(loadFrom).done(async (data) => { const coreEditor = editor.getCoreEditor(); await editor.update(data, true); editor.moveToNextRequestEdge(false); @@ -188,10 +188,7 @@ function EditorUI({ initialTextValue }: EditorProps) { const { current: editor } = editorInstanceRef; applyCurrentSettings(editor!.getCoreEditor(), settings); // Preserve legacy focus behavior after settings have updated. - editor! - .getCoreEditor() - .getContainer() - .focus(); + editor!.getCoreEditor().getContainer().focus(); }, [settings]); useEffect(() => { diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx index 8510aaebfaa1e..dd5ef5209a244 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx @@ -72,8 +72,8 @@ function EditorOutputUI() { editor.session.setMode(mode); editor.update( data - .map(d => d.response.value as string) - .map(readOnlySettings.tripleQuotes ? expandLiteralStrings : a => a) + .map((d) => d.response.value as string) + .map(readOnlySettings.tripleQuotes ? expandLiteralStrings : (a) => a) .join('\n') ); } else if (error) { diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts b/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts index 2bbe49cd53eac..a10458aaf28bd 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts +++ b/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts @@ -23,17 +23,14 @@ import { SenseEditor } from '../../../models/sense_editor'; export async function autoIndent(editor: SenseEditor, event: Event) { event.preventDefault(); await editor.autoIndent(); - editor - .getCoreEditor() - .getContainer() - .focus(); + editor.getCoreEditor().getContainer().focus(); } export function getDocumentation( editor: SenseEditor, docLinkVersion: string ): Promise { - return editor.getRequestsInRange().then(requests => { + return editor.getRequestsInRange().then((requests) => { if (!requests || requests.length === 0) { return null; } diff --git a/src/plugins/console/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts b/src/plugins/console/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts index 2d992aadbf15a..01233a7c42d52 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts +++ b/src/plugins/console/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts @@ -22,7 +22,7 @@ import { ResizeChecker } from '../../../../../../kibana_utils/public'; export function subscribeResizeChecker(el: HTMLElement, ...editors: any[]) { const checker = new ResizeChecker(el); checker.on('resize', () => - editors.forEach(e => { + editors.forEach((e) => { if (e.getCoreEditor) { e.getCoreEditor().resize(); } else { diff --git a/src/plugins/console/public/application/containers/settings.tsx b/src/plugins/console/public/application/containers/settings.tsx index 81938a83435de..acd9b88d8b5b9 100644 --- a/src/plugins/console/public/application/containers/settings.tsx +++ b/src/plugins/console/public/application/containers/settings.tsx @@ -26,7 +26,7 @@ import { useServicesContext, useEditorActionContext } from '../contexts'; import { DevToolsSettings, Settings as SettingsService } from '../../services'; const getAutocompleteDiff = (newSettings: DevToolsSettings, prevSettings: DevToolsSettings) => { - return Object.keys(newSettings.autocomplete).filter(key => { + return Object.keys(newSettings.autocomplete).filter((key) => { // @ts-ignore return prevSettings.autocomplete[key] !== newSettings.autocomplete[key]; }); diff --git a/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx b/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx index d5ed44e3f6ba2..884ad7d7e3585 100644 --- a/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx +++ b/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx @@ -31,7 +31,7 @@ export interface EditorContextArgs { } export function EditorContextProvider({ children, settings }: EditorContextArgs) { - const [state, dispatch] = useReducer(editor.reducer, editor.initialValue, value => ({ + const [state, dispatch] = useReducer(editor.reducer, editor.initialValue, (value) => ({ ...value, settings, })); diff --git a/src/plugins/console/public/application/models/legacy_core_editor/__tests__/input.test.js b/src/plugins/console/public/application/models/legacy_core_editor/__tests__/input.test.js index a68a2b3939864..81171c2bd26fe 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/__tests__/input.test.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/__tests__/input.test.js @@ -81,7 +81,7 @@ describe('Input', () => { data = prefix; } - test('Token test ' + testCount++ + ' prefix: ' + prefix, async function() { + test('Token test ' + testCount++ + ' prefix: ' + prefix, async function () { await coreEditor.setValue(data, true); const tokens = tokensAsList(); const normTokenList = []; @@ -473,7 +473,7 @@ describe('Input', () => { data = prefix; } - test('States test ' + testCount++ + ' prefix: ' + prefix, async function() { + test('States test ' + testCount++ + ' prefix: ' + prefix, async function () { await coreEditor.setValue(data, true); const modes = statesAsList(); expect(modes).toEqual(statesList); diff --git a/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js b/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js index 1db9ca7bc0a86..ea7530bd21387 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js @@ -57,8 +57,8 @@ describe('Output Tokenization', () => { data = JSON.stringify(data, null, 3); } - test('Token test ' + testCount++, async function(done) { - output.update(data, function() { + test('Token test ' + testCount++, async function (done) { + output.update(data, function () { const tokens = tokensAsList(); const normTokenList = []; for (let i = 0; i < tokenList.length; i++) { diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts index 66673ff42e4e8..f90b803a77004 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts @@ -22,7 +22,7 @@ jest.mock('./mode/worker', () => { }); // @ts-ignore -window.Worker = function() { +window.Worker = function () { this.postMessage = () => {}; (this as any).terminate = () => {}; }; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts index fc419b0f10dca..396356f98500d 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts @@ -74,7 +74,7 @@ export class LegacyCoreEditor implements CoreEditor { // dirty check for tokenizer state, uses a lot less cycles // than listening for tokenizerUpdate waitForLatestTokens(): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { const session = this.editor.getSession(); const checkInterval = 25; @@ -239,9 +239,9 @@ export class LegacyCoreEditor implements CoreEditor { private forceRetokenize() { const session = this.editor.getSession(); - return new Promise(resolve => { + return new Promise((resolve) => { // force update of tokens, but not on this thread to allow for ace rendering. - setTimeout(function() { + setTimeout(function () { let i; for (i = 0; i < session.getLength(); i++) { session.getTokens(i); @@ -368,11 +368,11 @@ export class LegacyCoreEditor implements CoreEditor { // disable standard context based autocompletion. // @ts-ignore - ace.define('ace/autocomplete/text_completer', ['require', 'exports', 'module'], function( + ace.define('ace/autocomplete/text_completer', ['require', 'exports', 'module'], function ( require: any, exports: any ) { - exports.getCompletions = function( + exports.getCompletions = function ( innerEditor: any, session: any, pos: any, diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/input.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/input.js index 77b4ba8cea6ff..4031167f72c91 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/input.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/input.js @@ -43,13 +43,13 @@ export function Mode() { } oop.inherits(Mode, TextMode); -(function() { - this.getCompletions = function() { +(function () { + this.getCompletions = function () { // autocomplete is done by the autocomplete module. return []; }; - this.getNextLineIndent = function(state, line, tab) { + this.getNextLineIndent = function (state, line, tab) { let indent = this.$getIndent(line); if (state !== 'string_literal') { @@ -62,21 +62,21 @@ oop.inherits(Mode, TextMode); return indent; }; - this.checkOutdent = function(state, line, input) { + this.checkOutdent = function (state, line, input) { return this.$outdent.checkOutdent(line, input); }; - this.autoOutdent = function(state, doc, row) { + this.autoOutdent = function (state, doc, row) { this.$outdent.autoOutdent(doc, row); }; - this.createWorker = function(session) { + this.createWorker = function (session) { const worker = new WorkerClient(['ace', 'sense_editor'], workerModule, 'SenseWorker'); worker.attachToDocument(session.getDocument()); - worker.on('error', function(e) { + worker.on('error', function (e) { session.setAnnotations([e.data]); }); - worker.on('ok', function(anno) { + worker.on('ok', function (anno) { session.setAnnotations(anno.data); }); diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/output.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/output.js index 5ad34532d1861..da9ce41611671 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/output.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/output.js @@ -37,8 +37,8 @@ export function Mode() { } oop.inherits(Mode, JSONMode); -(function() { - this.createWorker = function() { +(function () { + this.createWorker = function () { return null; }; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js index 89adea8921c07..6079c9db40eef 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js @@ -35,10 +35,10 @@ export function ScriptMode() { oop.inherits(ScriptMode, TextMode); -(function() { +(function () { this.HighlightRules = ScriptHighlightRules; - this.getNextLineIndent = function(state, line, tab) { + this.getNextLineIndent = function (state, line, tab) { let indent = this.$getIndent(line); const match = line.match(/^.*[\{\[]\s*$/); if (match) { @@ -48,11 +48,11 @@ oop.inherits(ScriptMode, TextMode); return indent; }; - this.checkOutdent = function(state, line, input) { + this.checkOutdent = function (state, line, input) { return this.$outdent.checkOutdent(line, input); }; - this.autoOutdent = function(state, doc, row) { + this.autoOutdent = function (state, doc, row) { this.$outdent.autoOutdent(doc, row); }; }.call(ScriptMode.prototype)); diff --git a/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts b/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts index 7c4d871c4d73e..e6d7d8272b35b 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts @@ -20,7 +20,7 @@ import { get, throttle } from 'lodash'; // eslint-disable-next-line import/no-default-export -export default function(editor: any) { +export default function (editor: any) { const resize = editor.resize; const throttledResize = throttle(() => { diff --git a/src/plugins/console/public/application/models/legacy_core_editor/theme_sense_dark.js b/src/plugins/console/public/application/models/legacy_core_editor/theme_sense_dark.js index 8a0eb9a03480b..86d17933c746f 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/theme_sense_dark.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/theme_sense_dark.js @@ -20,7 +20,7 @@ /* eslint import/no-unresolved: 0 */ import ace from 'brace'; -ace.define('ace/theme/sense-dark', ['require', 'exports', 'module'], function(require, exports) { +ace.define('ace/theme/sense-dark', ['require', 'exports', 'module'], function (require, exports) { exports.isDark = true; exports.cssClass = 'ace-sense-dark'; exports.cssText = diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/integration.test.js b/src/plugins/console/public/application/models/sense_editor/__tests__/integration.test.js index edd09885c1ad2..b7cc8f2f4b72f 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/integration.test.js +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/integration.test.js @@ -41,7 +41,7 @@ describe('Integration', () => { }); function processContextTest(data, mapping, kbSchemes, requestLine, testToRun) { - test(testToRun.name, async function(done) { + test(testToRun.name, async function (done) { let lineOffset = 0; // add one for the extra method line let editorValue = data; if (requestLine != null) { @@ -67,7 +67,7 @@ describe('Integration', () => { // }); // } if (kbSchemes.endpoints) { - $.each(kbSchemes.endpoints, function(endpoint, scheme) { + $.each(kbSchemes.endpoints, function (endpoint, scheme) { testApi.addEndpointDescription(endpoint, scheme); }); } @@ -81,10 +81,10 @@ describe('Integration', () => { //setTimeout(function () { senseEditor.completer = { base: {}, - changeListener: function() {}, + changeListener: function () {}, }; // mimic auto complete - senseEditor.autocomplete._test.getCompletions(senseEditor, null, cursor, '', function( + senseEditor.autocomplete._test.getCompletions(senseEditor, null, cursor, '', function ( err, terms ) { @@ -110,7 +110,7 @@ describe('Integration', () => { } if (testToRun.autoCompleteSet) { - const expectedTerms = _.map(testToRun.autoCompleteSet, function(t) { + const expectedTerms = _.map(testToRun.autoCompleteSet, function (t) { if (typeof t !== 'object') { t = { name: t }; } @@ -119,10 +119,10 @@ describe('Integration', () => { if (terms.length !== expectedTerms.length) { expect(_.pluck(terms, 'name')).toEqual(_.pluck(expectedTerms, 'name')); } else { - const filteredActualTerms = _.map(terms, function(actualTerm, i) { + const filteredActualTerms = _.map(terms, function (actualTerm, i) { const expectedTerm = expectedTerms[i]; const filteredTerm = {}; - _.each(expectedTerm, function(v, p) { + _.each(expectedTerm, function (v, p) { filteredTerm[p] = actualTerm[p]; }); return filteredTerm; @@ -739,7 +739,7 @@ describe('Integration', () => { }, ], g: { - __scope_link: function() { + __scope_link: function () { return { a: 1, b: 2, diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js index 219e6262ab346..c3fb879f2eeeb 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js @@ -28,7 +28,7 @@ import editorInput1 from './editor_input1.txt'; describe('Editor', () => { let input; - beforeEach(function() { + beforeEach(function () { // Set up our document body document.body.innerHTML = `
@@ -40,14 +40,14 @@ describe('Editor', () => { $(input.getCoreEditor().getContainer()).show(); input.autocomplete._test.removeChangeListener(); }); - afterEach(function() { + afterEach(function () { $(input.getCoreEditor().getContainer()).hide(); input.autocomplete._test.addChangeListener(); }); let testCount = 0; - const callWithEditorMethod = (editorMethod, fn) => async done => { + const callWithEditorMethod = (editorMethod, fn) => async (done) => { const results = await input[editorMethod](); fn(results, done); }; @@ -69,7 +69,7 @@ describe('Editor', () => { data = prefix; } - test('Utils test ' + id + ' : ' + name, async function(done) { + test('Utils test ' + id + ' : ' + name, async function (done) { await input.update(data, true); testToRun(done); }); @@ -81,7 +81,7 @@ describe('Editor', () => { expected = [expected]; } - _.each(requests, function(r) { + _.each(requests, function (r) { delete r.range; }); expect(requests).toEqual(expected); @@ -340,10 +340,10 @@ describe('Editor', () => { ); function multiReqTest(name, editorInput, range, expected) { - utilsTest('multi request select - ' + name, editorInput, async function(done) { + utilsTest('multi request select - ' + name, editorInput, async function (done) { const requests = await input.getRequestsInRange(range, false); // convert to format returned by request. - _.each(expected, function(req) { + _.each(expected, function (req) { req.data = req.data == null ? [] : [JSON.stringify(req.data, null, 2)]; }); @@ -451,7 +451,7 @@ describe('Editor', () => { ); function multiReqCopyAsCurlTest(name, editorInput, range, expected) { - utilsTest('multi request copy as curl - ' + name, editorInput, async function(done) { + utilsTest('multi request copy as curl - ' + name, editorInput, async function (done) { const curl = await input.getRequestsAsCURL('http://localhost:9200', range); expect(curl).toEqual(expected); done(); diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index d326543bbe00b..dbf4f1adcba0a 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -425,8 +425,8 @@ export class SenseEditor { } const column = - (this.coreEditor.getLineValue(curLineNumber) || '').length + - 1 /* Range goes to 1 after last char */; + (this.coreEditor.getLineValue(curLineNumber) || '').length + + 1; /* Range goes to 1 after last char */ return { lineNumber: curLineNumber, @@ -467,7 +467,7 @@ export class SenseEditor { getRequestsAsCURL = async (elasticsearchBaseUrl: string, range?: Range): Promise => { const requests = await this.getRequestsInRange(range, true); - const result = _.map(requests, req => { + const result = _.map(requests, (req) => { if (typeof req === 'string') { // no request block return req; diff --git a/src/plugins/console/public/application/stores/editor.ts b/src/plugins/console/public/application/stores/editor.ts index 73c29e7ff8575..c8a5b2421d52d 100644 --- a/src/plugins/console/public/application/stores/editor.ts +++ b/src/plugins/console/public/application/stores/editor.ts @@ -44,7 +44,7 @@ export type Action = | { type: 'updateSettings'; payload: DevToolsSettings }; export const reducer: Reducer = (state, action) => - produce(state, draft => { + produce(state, (draft) => { if (action.type === 'setInputEditor') { if (action.payload) { draft.ready = true; diff --git a/src/plugins/console/public/application/stores/request.ts b/src/plugins/console/public/application/stores/request.ts index f711330df3911..4ec9eae7e10d4 100644 --- a/src/plugins/console/public/application/stores/request.ts +++ b/src/plugins/console/public/application/stores/request.ts @@ -50,7 +50,7 @@ export const initialValue: Store = produce( ); export const reducer: Reducer = (state, action) => - produce(state, draft => { + produce(state, (draft) => { if (action.type === 'sendRequest') { draft.requestInFlight = true; draft.lastResult = initialResultValue; diff --git a/src/plugins/console/public/lib/ace_token_provider/token_provider.test.ts b/src/plugins/console/public/lib/ace_token_provider/token_provider.test.ts index d5465ebe96514..75b1bb802327e 100644 --- a/src/plugins/console/public/lib/ace_token_provider/token_provider.test.ts +++ b/src/plugins/console/public/lib/ace_token_provider/token_provider.test.ts @@ -74,7 +74,7 @@ describe('Ace (legacy) token provider', () => { }; describe('base cases', () => { - test('case 1 - only url', done => { + test('case 1 - only url', (done) => { runTest({ input: `GET http://somehost/_search`, expectedTokens: [ @@ -92,7 +92,7 @@ describe('Ace (legacy) token provider', () => { }); }); - test('case 2 - basic auth in host name', done => { + test('case 2 - basic auth in host name', (done) => { runTest({ input: `GET http://test:user@somehost/`, expectedTokens: [ @@ -109,7 +109,7 @@ describe('Ace (legacy) token provider', () => { }); }); - test('case 3 - handles empty lines', done => { + test('case 3 - handles empty lines', (done) => { runTest({ input: `POST abc @@ -128,7 +128,7 @@ describe('Ace (legacy) token provider', () => { }); describe('with newlines', () => { - test('case 1 - newlines base case', done => { + test('case 1 - newlines base case', (done) => { runTest({ input: `GET http://test:user@somehost/ { @@ -148,7 +148,7 @@ describe('Ace (legacy) token provider', () => { }); describe('edge cases', () => { - test('case 1 - getting token outside of document', done => { + test('case 1 - getting token outside of document', (done) => { runTest({ input: `GET http://test:user@somehost/ { @@ -160,7 +160,7 @@ describe('Ace (legacy) token provider', () => { }); }); - test('case 2 - empty lines', done => { + test('case 2 - empty lines', (done) => { runTest({ input: `GET http://test:user@somehost/ @@ -193,7 +193,7 @@ describe('Ace (legacy) token provider', () => { }; describe('base cases', () => { - it('case 1 - gets a token from the url', done => { + it('case 1 - gets a token from the url', (done) => { const input = `GET http://test:user@somehost/`; runTest({ input, @@ -219,7 +219,7 @@ describe('Ace (legacy) token provider', () => { }); describe('special cases', () => { - it('case 1 - handles input outside of range', done => { + it('case 1 - handles input outside of range', (done) => { runTest({ input: `GET abc`, expectedToken: null, diff --git a/src/plugins/console/public/lib/ace_token_provider/token_provider.ts b/src/plugins/console/public/lib/ace_token_provider/token_provider.ts index 134ab6c0e82d5..93bf7ce4c76d5 100644 --- a/src/plugins/console/public/lib/ace_token_provider/token_provider.ts +++ b/src/plugins/console/public/lib/ace_token_provider/token_provider.ts @@ -36,7 +36,7 @@ const toToken = (lineNumber: number, column: number, token: TokenInfo): Token => const toTokens = (lineNumber: number, tokens: TokenInfo[]): Token[] => { let acc = ''; - return tokens.map(token => { + return tokens.map((token) => { const column = acc.length + 1; acc += token.value; return toToken(lineNumber, column, token); diff --git a/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.js b/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.js index ebde8c39cffbc..0c3fcbafbe9f9 100644 --- a/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.js +++ b/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.js @@ -28,11 +28,11 @@ import { populateContext } from '../../autocomplete/engine'; describe('Url autocomplete', () => { function patternsTest(name, endpoints, tokenPath, expectedContext, globalUrlComponentFactories) { - test(name, function() { + test(name, function () { const patternMatcher = new UrlPatternMatcher(globalUrlComponentFactories); - _.each(endpoints, function(e, id) { + _.each(endpoints, function (e, id) { e.id = id; - _.each(e.patterns, function(p) { + _.each(e.patterns, function (p) { patternMatcher.addEndpoint(p, e); }); }); @@ -40,7 +40,7 @@ describe('Url autocomplete', () => { if (tokenPath[tokenPath.length - 1] === '$') { tokenPath = tokenPath.substr(0, tokenPath.length - 1) + '/' + URL_PATH_END_MARKER; } - tokenPath = _.map(tokenPath.split('/'), function(p) { + tokenPath = _.map(tokenPath.split('/'), function (p) { p = p.split(','); if (p.length === 1) { return p[0]; @@ -50,7 +50,7 @@ describe('Url autocomplete', () => { } if (expectedContext.autoCompleteSet) { - expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function(t) { + expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) { if (_.isString(t)) { t = { name: t }; } @@ -91,7 +91,7 @@ describe('Url autocomplete', () => { return name; } - (function() { + (function () { const endpoints = { '1': { patterns: ['a/b'], @@ -123,7 +123,7 @@ describe('Url autocomplete', () => { patternsTest('simple single path - different path', endpoints, 'a/c', {}); })(); - (function() { + (function () { const endpoints = { '1': { patterns: ['a/b', 'a/b/{p}'], @@ -174,7 +174,7 @@ describe('Url autocomplete', () => { }); })(); - (function() { + (function () { const endpoints = { '1': { patterns: ['a/{p}'], @@ -228,7 +228,7 @@ describe('Url autocomplete', () => { }); })(); - (function() { + (function () { const endpoints = { '1': { patterns: ['a/{p}'], @@ -254,7 +254,7 @@ describe('Url autocomplete', () => { }, }; const globalFactories = { - p: function(name, parent) { + p: function (name, parent) { return new ListComponent(name, ['g1', 'g2'], parent); }, getComponent(name) { @@ -313,7 +313,7 @@ describe('Url autocomplete', () => { ); })(); - (function() { + (function () { const endpoints = { '1': { patterns: ['a/b/{p}/c/e'], @@ -343,7 +343,7 @@ describe('Url autocomplete', () => { }); })(); - (function() { + (function () { const endpoints = { '1_param': { patterns: ['a/{p}'], @@ -378,7 +378,7 @@ describe('Url autocomplete', () => { }); })(); - (function() { + (function () { const endpoints = { '1_GET': { patterns: ['a'], diff --git a/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.js b/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.js index 286aefcd133a0..e624e7ba57b61 100644 --- a/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.js +++ b/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.js @@ -22,10 +22,10 @@ import { populateContext } from '../../autocomplete/engine'; describe('Url params', () => { function paramTest(name, description, tokenPath, expectedContext, globalParams) { - test(name, function() { + test(name, function () { const urlParams = new UrlParams(description, globalParams || {}); if (typeof tokenPath === 'string') { - tokenPath = _.map(tokenPath.split('/'), function(p) { + tokenPath = _.map(tokenPath.split('/'), function (p) { p = p.split(','); if (p.length === 1) { return p[0]; @@ -35,7 +35,7 @@ describe('Url params', () => { } if (expectedContext.autoCompleteSet) { - expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function(t) { + expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) { if (_.isString(t)) { t = { name: t }; } @@ -79,7 +79,7 @@ describe('Url params', () => { return r; } - (function() { + (function () { const params = { a: ['1', '2'], b: '__flag__', diff --git a/src/plugins/console/public/lib/autocomplete/autocomplete.ts b/src/plugins/console/public/lib/autocomplete/autocomplete.ts index d4f10ff4e4277..b05c7ddbb020d 100644 --- a/src/plugins/console/public/lib/autocomplete/autocomplete.ts +++ b/src/plugins/console/public/lib/autocomplete/autocomplete.ts @@ -321,7 +321,7 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor } function addMetaToTermsList(list: any, meta: any, template?: string) { - return _.map(list, function(t: any) { + return _.map(list, function (t: any) { if (typeof t !== 'object') { t = { name: t }; } @@ -955,7 +955,7 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor } else { const terms = _.map( context.autoCompleteSet.filter((term: any) => Boolean(term) && term.name != null), - function(term: any) { + function (term: any) { if (typeof term !== 'object') { term = { name: term, @@ -981,7 +981,7 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor } ); - terms.sort(function(t1: any, t2: any) { + terms.sort(function (t1: any, t2: any) { /* score sorts from high to low */ if (t1.score > t2.score) { return -1; @@ -1001,7 +1001,7 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor callback( null, - _.map(terms, function(t: any, i: any) { + _.map(terms, function (t: any, i: any) { t.insertValue = t.insertValue || t.value; t.value = '' + t.value; // normalize to strings t.score = -i; diff --git a/src/plugins/console/public/lib/autocomplete/body_completer.js b/src/plugins/console/public/lib/autocomplete/body_completer.js index 9bb1f14a6266a..f37b3ac0cca9c 100644 --- a/src/plugins/console/public/lib/autocomplete/body_completer.js +++ b/src/plugins/console/public/lib/autocomplete/body_completer.js @@ -104,7 +104,7 @@ class ScopeResolver extends SharedComponent { getTerms(context, editor) { const options = []; const components = this.resolveLinkToComponents(context, editor); - _.each(components, function(component) { + _.each(components, function (component) { options.push.apply(options, component.getTerms(context, editor)); }); return options; @@ -115,7 +115,7 @@ class ScopeResolver extends SharedComponent { next: [], }; const components = this.resolveLinkToComponents(context, editor); - _.each(components, function(component) { + _.each(components, function (component) { const componentResult = component.match(token, context, editor); if (componentResult && componentResult.next) { result.next.push.apply(result.next, componentResult.next); @@ -193,7 +193,7 @@ function compileDescription(description, compilingContext) { } if (description.__one_of) { return _.flatten( - _.map(description.__one_of, function(d) { + _.map(description.__one_of, function (d) { return compileDescription(d, compilingContext); }) ); @@ -228,7 +228,7 @@ function compileObject(objDescription, compilingContext) { const objectC = new ConstantComponent('{'); const constants = []; const patterns = []; - _.each(objDescription, function(desc, key) { + _.each(objDescription, function (desc, key) { if (key.indexOf('__') === 0) { // meta key return; @@ -247,7 +247,7 @@ function compileObject(objDescription, compilingContext) { component = new ConstantComponent(key, null, [options]); constants.push(component); } - _.map(compileDescription(desc, compilingContext), function(subComponent) { + _.map(compileDescription(desc, compilingContext), function (subComponent) { component.addComponent(subComponent); }); }); @@ -257,8 +257,8 @@ function compileObject(objDescription, compilingContext) { function compileList(listRule, compilingContext) { const listC = new ConstantComponent('['); - _.each(listRule, function(desc) { - _.each(compileDescription(desc, compilingContext), function(component) { + _.each(listRule, function (desc) { + _.each(compileDescription(desc, compilingContext), function (component) { listC.addComponent(component); }); }); @@ -268,7 +268,7 @@ function compileList(listRule, compilingContext) { /** takes a compiled object and wraps in a {@link ConditionalProxy }*/ function compileCondition(description, compiledObject) { if (description.lines_regex) { - return new ConditionalProxy(function(context, editor) { + return new ConditionalProxy(function (context, editor) { const lines = editor .getLines(context.requestStartRow, editor.getCurrentPosition().lineNumber) .join('\n'); diff --git a/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js index 80f65406cf5d3..05c72ea8a8dc5 100644 --- a/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js @@ -21,7 +21,7 @@ import { getFields } from '../../mappings/mappings'; import { ListComponent } from './list_component'; function FieldGenerator(context) { - return _.map(getFields(context.indices, context.types), function(field) { + return _.map(getFields(context.indices, context.types), function (field) { return { name: field.name, meta: field.type }; }); } @@ -35,7 +35,7 @@ export class FieldAutocompleteComponent extends ListComponent { return false; } - return !_.find(tokens, function(token) { + return !_.find(tokens, function (token) { return token.match(/[^\w.?*]/); }); } diff --git a/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.js index d6deadc75ed74..88b9320b3c45e 100644 --- a/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.js @@ -33,7 +33,7 @@ export class IdAutocompleteComponent extends SharedComponent { } token = Array.isArray(token) ? token : [token]; if ( - _.find(token, function(t) { + _.find(token, function (t) { return t.match(/[\/,]/); }) ) { diff --git a/src/plugins/console/public/lib/autocomplete/components/list_component.js b/src/plugins/console/public/lib/autocomplete/components/list_component.js index 2f443ffaf6c93..b770638a61ff7 100644 --- a/src/plugins/console/public/lib/autocomplete/components/list_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/list_component.js @@ -24,7 +24,7 @@ export class ListComponent extends SharedComponent { constructor(name, list, parent, multiValued, allowNonValidValues) { super(name, parent); this.listGenerator = Array.isArray(list) - ? function() { + ? function () { return list; } : list; @@ -44,7 +44,7 @@ export class ListComponent extends SharedComponent { if (this.getDefaultTermMeta()) { const meta = this.getDefaultTermMeta(); - ret = _.map(ret, function(term) { + ret = _.map(ret, function (term) { if (_.isString(term)) { term = { name: term }; } @@ -62,7 +62,7 @@ export class ListComponent extends SharedComponent { // verify we have all tokens const list = this.listGenerator(); - const notFound = _.any(tokens, function(token) { + const notFound = _.any(tokens, function (token) { return list.indexOf(token) === -1; }); diff --git a/src/plugins/console/public/lib/autocomplete/components/object_component.js b/src/plugins/console/public/lib/autocomplete/components/object_component.js index f73625cccf38b..34cfb892a65d9 100644 --- a/src/plugins/console/public/lib/autocomplete/components/object_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/object_component.js @@ -32,10 +32,10 @@ export class ObjectComponent extends SharedComponent { } getTerms(context, editor) { const options = []; - _.each(this.constants, function(component) { + _.each(this.constants, function (component) { options.push.apply(options, component.getTerms(context, editor)); }); - _.each(this.patternsAndWildCards, function(component) { + _.each(this.patternsAndWildCards, function (component) { options.push.apply(options, component.getTerms(context, editor)); }); return options; @@ -45,7 +45,7 @@ export class ObjectComponent extends SharedComponent { const result = { next: [], }; - _.each(this.constants, function(component) { + _.each(this.constants, function (component) { const componentResult = component.match(token, context, editor); if (componentResult && componentResult.next) { result.next.push.apply(result.next, componentResult.next); @@ -61,7 +61,7 @@ export class ObjectComponent extends SharedComponent { if (result.next.length) { return result; } - _.each(this.patternsAndWildCards, function(component) { + _.each(this.patternsAndWildCards, function (component) { const componentResult = component.match(token, context, editor); if (componentResult && componentResult.next) { result.next.push.apply(result.next, componentResult.next); diff --git a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js index 5921a910f4175..79a332624e5e1 100644 --- a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js +++ b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js @@ -43,7 +43,7 @@ export class UrlPatternMatcher { // We'll group endpoints by the methods which are attached to them, //to avoid suggesting endpoints that are incompatible with the //method that the user has entered. - ['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach(method => { + ['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach((method) => { this[method] = { rootComponent: new SharedComponent('ROOT'), parametrizedComponentFactories: parametrizedComponentFactories || { @@ -53,7 +53,7 @@ export class UrlPatternMatcher { }); } addEndpoint(pattern, endpoint) { - endpoint.methods.forEach(method => { + endpoint.methods.forEach((method) => { let c; let activeComponent = this[method].rootComponent; if (endpoint.template) { @@ -63,7 +63,7 @@ export class UrlPatternMatcher { const partList = pattern.split('/'); _.each( partList, - function(part, partIndex) { + function (part, partIndex) { if (part.search(/^{.+}$/) >= 0) { part = part.substr(1, part.length - 2); if (activeComponent.getComponent(part)) { @@ -133,7 +133,7 @@ export class UrlPatternMatcher { }); } - getTopLevelComponents = function(method) { + getTopLevelComponents = function (method) { const methodRoot = this[method]; if (!methodRoot) { return []; diff --git a/src/plugins/console/public/lib/autocomplete/engine.js b/src/plugins/console/public/lib/autocomplete/engine.js index ae943a74ffb3a..38be0d8a7e4c9 100644 --- a/src/plugins/console/public/lib/autocomplete/engine.js +++ b/src/plugins/console/public/lib/autocomplete/engine.js @@ -21,14 +21,14 @@ import _ from 'lodash'; export function wrapComponentWithDefaults(component, defaults) { const originalGetTerms = component.getTerms; - component.getTerms = function(context, editor) { + component.getTerms = function (context, editor) { let result = originalGetTerms.call(component, context, editor); if (!result) { return result; } result = _.map( result, - function(term) { + function (term) { if (!_.isObject(term)) { term = { name: term }; } @@ -41,7 +41,7 @@ export function wrapComponentWithDefaults(component, defaults) { return component; } -const tracer = function() { +const tracer = function () { if (window.engine_trace) { console.log.call(console, ...arguments); } @@ -77,9 +77,9 @@ export function walkTokenPath(tokenPath, walkingStates, context, editor) { tracer('starting token evaluation [' + token + ']'); - _.each(walkingStates, function(ws) { + _.each(walkingStates, function (ws) { const contextForState = passThroughContext(context, ws.contextExtensionList); - _.each(ws.components, function(component) { + _.each(ws.components, function (component) { tracer('evaluating [' + token + '] with [' + component.name + ']', component); const result = component.match(token, contextForState, editor); if (result && !_.isEmpty(result)) { @@ -117,7 +117,7 @@ export function walkTokenPath(tokenPath, walkingStates, context, editor) { if (nextWalkingStates.length === 0) { // no where to go, still return context variables returned so far.. - return _.map(walkingStates, function(ws) { + return _.map(walkingStates, function (ws) { return new WalkingState(ws.name, [], ws.contextExtensionList); }); } @@ -134,10 +134,10 @@ export function populateContext(tokenPath, context, editor, includeAutoComplete, ); if (includeAutoComplete) { let autoCompleteSet = []; - _.each(walkStates, function(ws) { + _.each(walkStates, function (ws) { const contextForState = passThroughContext(context, ws.contextExtensionList); - _.each(ws.components, function(component) { - _.each(component.getTerms(contextForState, editor), function(term) { + _.each(ws.components, function (component) { + _.each(component.getTerms(contextForState, editor), function (term) { if (!_.isObject(term)) { term = { name: term }; } @@ -152,10 +152,10 @@ export function populateContext(tokenPath, context, editor, includeAutoComplete, // apply what values were set so far to context, selecting the deepest on which sets the context if (walkStates.length !== 0) { let wsToUse; - walkStates = _.sortBy(walkStates, function(ws) { + walkStates = _.sortBy(walkStates, function (ws) { return _.isNumber(ws.priority) ? ws.priority : Number.MAX_VALUE; }); - wsToUse = _.find(walkStates, function(ws) { + wsToUse = _.find(walkStates, function (ws) { return _.isEmpty(ws.components); }); @@ -170,7 +170,7 @@ export function populateContext(tokenPath, context, editor, includeAutoComplete, wsToUse = walkStates[0]; } - _.each(wsToUse.contextExtensionList, function(extension) { + _.each(wsToUse.contextExtensionList, function (extension) { _.assign(context, extension); }); } diff --git a/src/plugins/console/public/lib/autocomplete/url_params.js b/src/plugins/console/public/lib/autocomplete/url_params.js index 0519a2daade87..a237fe5dd59d6 100644 --- a/src/plugins/console/public/lib/autocomplete/url_params.js +++ b/src/plugins/console/public/lib/autocomplete/url_params.js @@ -52,7 +52,7 @@ export class UrlParams { _.defaults(description, defaults); _.each( description, - function(pDescription, param) { + function (pDescription, param) { const component = new ParamComponent(param, this.rootComponent, pDescription); if (Array.isArray(pDescription)) { new ListComponent(param, pDescription, component); diff --git a/src/plugins/console/public/lib/curl_parsing/__tests__/curl_parsing.test.js b/src/plugins/console/public/lib/curl_parsing/__tests__/curl_parsing.test.js index 6a8caebfc6874..068dd68be4ba8 100644 --- a/src/plugins/console/public/lib/curl_parsing/__tests__/curl_parsing.test.js +++ b/src/plugins/console/public/lib/curl_parsing/__tests__/curl_parsing.test.js @@ -23,13 +23,13 @@ import curlTests from './curl_parsing.txt'; describe('CURL', () => { const notCURLS = ['sldhfsljfhs', 's;kdjfsldkfj curl -XDELETE ""', '{ "hello": 1 }']; - _.each(notCURLS, function(notCURL, i) { - test('cURL Detection - broken strings ' + i, function() { + _.each(notCURLS, function (notCURL, i) { + test('cURL Detection - broken strings ' + i, function () { expect(detectCURL(notCURL)).toEqual(false); }); }); - curlTests.split(/^=+$/m).forEach(function(fixture) { + curlTests.split(/^=+$/m).forEach(function (fixture) { if (fixture.trim() === '') { return; } @@ -38,7 +38,7 @@ describe('CURL', () => { const curlText = fixture[1]; const response = fixture[2].trim(); - test('cURL Detection - ' + name, function() { + test('cURL Detection - ' + name, function () { expect(detectCURL(curlText)).toBe(true); const r = parseCURL(curlText); expect(r).toEqual(response); diff --git a/src/plugins/console/public/lib/kb/__tests__/kb.test.js b/src/plugins/console/public/lib/kb/__tests__/kb.test.js index c80f5671449b3..eaf5023053880 100644 --- a/src/plugins/console/public/lib/kb/__tests__/kb.test.js +++ b/src/plugins/console/public/lib/kb/__tests__/kb.test.js @@ -58,7 +58,7 @@ describe('Knowledge base', () => { function testUrlContext(tokenPath, otherTokenValues, expectedContext) { if (expectedContext.autoCompleteSet) { - expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function(t) { + expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) { if (_.isString(t)) { t = { name: t }; } @@ -111,7 +111,7 @@ describe('Knowledge base', () => { } function indexTest(name, tokenPath, otherTokenValues, expectedContext) { - test(name, function() { + test(name, function () { // eslint-disable-next-line new-cap const testApi = new kb._test.loadApisFromJson( { @@ -161,7 +161,7 @@ describe('Knowledge base', () => { }); function typeTest(name, tokenPath, otherTokenValues, expectedContext) { - test(name, function() { + test(name, function () { const testApi = kb._test.loadApisFromJson( { typeTest: { diff --git a/src/plugins/console/public/lib/kb/api.js b/src/plugins/console/public/lib/kb/api.js index c418a7cb414ef..aafb234b0f446 100644 --- a/src/plugins/console/public/lib/kb/api.js +++ b/src/plugins/console/public/lib/kb/api.js @@ -41,8 +41,8 @@ function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactori this.name = ''; } -(function(cls) { - cls.addGlobalAutocompleteRules = function(parentNode, rules) { +(function (cls) { + cls.addGlobalAutocompleteRules = function (parentNode, rules) { this.globalRules[parentNode] = compileBodyDescription( 'GLOBAL.' + parentNode, rules, @@ -50,7 +50,7 @@ function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactori ); }; - cls.getGlobalAutocompleteComponents = function(term, throwOnMissing) { + cls.getGlobalAutocompleteComponents = function (term, throwOnMissing) { const result = this.globalRules[term]; if (_.isUndefined(result) && (throwOnMissing || _.isUndefined(throwOnMissing))) { throw new Error("failed to resolve global components for ['" + term + "']"); @@ -58,7 +58,7 @@ function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactori return result; }; - cls.addEndpointDescription = function(endpoint, description) { + cls.addEndpointDescription = function (endpoint, description) { const copiedDescription = {}; _.extend(copiedDescription, description || {}); _.defaults(copiedDescription, { @@ -68,7 +68,7 @@ function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactori }); _.each( copiedDescription.patterns, - function(p) { + function (p) { this.urlPatternMatcher.addEndpoint(p, copiedDescription); }, this @@ -84,19 +84,19 @@ function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactori this.endpoints[endpoint] = copiedDescription; }; - cls.getEndpointDescriptionByEndpoint = function(endpoint) { + cls.getEndpointDescriptionByEndpoint = function (endpoint) { return this.endpoints[endpoint]; }; - cls.getTopLevelUrlCompleteComponents = function(method) { + cls.getTopLevelUrlCompleteComponents = function (method) { return this.urlPatternMatcher.getTopLevelComponents(method); }; - cls.getUnmatchedEndpointComponents = function() { + cls.getUnmatchedEndpointComponents = function () { return globalsOnlyAutocompleteComponents(); }; - cls.clear = function() { + cls.clear = function () { this.endpoints = {}; this.globalRules = {}; }; diff --git a/src/plugins/console/public/lib/kb/kb.js b/src/plugins/console/public/lib/kb/kb.js index 0d0f42319d0d8..e0bf1ef09c4d3 100644 --- a/src/plugins/console/public/lib/kb/kb.js +++ b/src/plugins/console/public/lib/kb/kb.js @@ -32,68 +32,68 @@ import _ from 'lodash'; import Api from './api'; let ACTIVE_API = new Api(); -const isNotAnIndexName = name => name[0] === '_' && name !== '_all'; +const isNotAnIndexName = (name) => name[0] === '_' && name !== '_all'; const idAutocompleteComponentFactory = (name, parent) => { return new IdAutocompleteComponent(name, parent); }; const parametrizedComponentFactories = { - getComponent: function(name, parent, provideDefault) { + getComponent: function (name, parent, provideDefault) { if (this[name]) { return this[name]; } else if (provideDefault) { return idAutocompleteComponentFactory; } }, - index: function(name, parent) { + index: function (name, parent) { if (isNotAnIndexName(name)) return; return new IndexAutocompleteComponent(name, parent, false); }, - indices: function(name, parent) { + indices: function (name, parent) { if (isNotAnIndexName(name)) return; return new IndexAutocompleteComponent(name, parent, true); }, - type: function(name, parent) { + type: function (name, parent) { return new TypeAutocompleteComponent(name, parent, false); }, - types: function(name, parent) { + types: function (name, parent) { return new TypeAutocompleteComponent(name, parent, true); }, - id: function(name, parent) { + id: function (name, parent) { return idAutocompleteComponentFactory(name, parent); }, - transform_id: function(name, parent) { + transform_id: function (name, parent) { return idAutocompleteComponentFactory(name, parent); }, - username: function(name, parent) { + username: function (name, parent) { return new UsernameAutocompleteComponent(name, parent); }, - user: function(name, parent) { + user: function (name, parent) { return new UsernameAutocompleteComponent(name, parent); }, - template: function(name, parent) { + template: function (name, parent) { return new TemplateAutocompleteComponent(name, parent); }, - task_id: function(name, parent) { + task_id: function (name, parent) { return idAutocompleteComponentFactory(name, parent); }, - ids: function(name, parent) { + ids: function (name, parent) { return idAutocompleteComponentFactory(name, parent, true); }, - fields: function(name, parent) { + fields: function (name, parent) { return new FieldAutocompleteComponent(name, parent, true); }, - field: function(name, parent) { + field: function (name, parent) { return new FieldAutocompleteComponent(name, parent, false); }, - nodes: function(name, parent) { + nodes: function (name, parent) { return new ListComponent( name, ['_local', '_master', 'data:true', 'data:false', 'master:true', 'master:false'], parent ); }, - node: function(name, parent) { + node: function (name, parent) { return new ListComponent(name, [], parent, false); }, }; @@ -133,12 +133,12 @@ function loadApisFromJson( bodyParametrizedComponentFactories || urlParametrizedComponentFactories; const api = new Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactories); const names = []; - _.each(json, function(apiJson, name) { + _.each(json, function (apiJson, name) { names.unshift(name); - _.each(apiJson.globals || {}, function(globalJson, globalName) { + _.each(apiJson.globals || {}, function (globalJson, globalName) { api.addGlobalAutocompleteRules(globalName, globalJson); }); - _.each(apiJson.endpoints || {}, function(endpointJson, endpointName) { + _.each(apiJson.endpoints || {}, function (endpointJson, endpointName) { api.addEndpointDescription(endpointName, endpointJson); }); }); @@ -159,10 +159,10 @@ export function setActiveApi(api) { 'kbn-xsrf': 'kibana', }, }).then( - function(data) { + function (data) { setActiveApi(loadApisFromJson(data)); }, - function(jqXHR) { + function (jqXHR) { console.log("failed to load API '" + api + "': " + jqXHR.responseText); } ); diff --git a/src/plugins/console/public/lib/local_storage_object_client/local_storage_object_client.ts b/src/plugins/console/public/lib/local_storage_object_client/local_storage_object_client.ts index 8eac345898f9a..a6b47559a4ef5 100644 --- a/src/plugins/console/public/lib/local_storage_object_client/local_storage_object_client.ts +++ b/src/plugins/console/public/lib/local_storage_object_client/local_storage_object_client.ts @@ -40,7 +40,7 @@ export class LocalObjectStorage implements ObjectStorage } async findAll(): Promise { - const allLocalKeys = this.client.keys().filter(key => { + const allLocalKeys = this.client.keys().filter((key) => { return key.includes(this.prefix); }); diff --git a/src/plugins/console/public/lib/mappings/__tests__/mapping.test.js b/src/plugins/console/public/lib/mappings/__tests__/mapping.test.js index 292b1b4fb1bf5..ce52b060f418f 100644 --- a/src/plugins/console/public/lib/mappings/__tests__/mapping.test.js +++ b/src/plugins/console/public/lib/mappings/__tests__/mapping.test.js @@ -41,7 +41,7 @@ describe('Mappings', () => { return { name: name, type: type || 'string' }; } - test('Multi fields 1.0 style', function() { + test('Multi fields 1.0 style', function () { mappings.loadMappings({ index: { properties: { @@ -72,7 +72,7 @@ describe('Mappings', () => { ]); }); - test('Simple fields', function() { + test('Simple fields', function () { mappings.loadMappings({ index: { properties: { @@ -89,7 +89,7 @@ describe('Mappings', () => { expect(mappings.getFields('index').sort(fc)).toEqual([f('number', 'int'), f('str', 'string')]); }); - test('Simple fields - 1.0 style', function() { + test('Simple fields - 1.0 style', function () { mappings.loadMappings({ index: { mappings: { @@ -108,7 +108,7 @@ describe('Mappings', () => { expect(mappings.getFields('index').sort(fc)).toEqual([f('number', 'int'), f('str', 'string')]); }); - test('Nested fields', function() { + test('Nested fields', function () { mappings.loadMappings({ index: { properties: { @@ -137,7 +137,7 @@ describe('Mappings', () => { ]); }); - test('Enabled fields', function() { + test('Enabled fields', function () { mappings.loadMappings({ index: { properties: { @@ -159,7 +159,7 @@ describe('Mappings', () => { expect(mappings.getFields('index', []).sort(fc)).toEqual([f('message'), f('person.sid')]); }); - test('Path tests', function() { + test('Path tests', function () { mappings.loadMappings({ index: { properties: { @@ -191,7 +191,7 @@ describe('Mappings', () => { ]); }); - test('Use index_name tests', function() { + test('Use index_name tests', function () { mappings.loadMappings({ index: { properties: { @@ -203,7 +203,7 @@ describe('Mappings', () => { expect(mappings.getFields().sort(fc)).toEqual([f('i_last_1')]); }); - test('Aliases', function() { + test('Aliases', function () { mappings.loadAliases({ test_index1: { aliases: { diff --git a/src/plugins/console/public/lib/mappings/mappings.js b/src/plugins/console/public/lib/mappings/mappings.js index b5bcc2b105996..22aae8da030d4 100644 --- a/src/plugins/console/public/lib/mappings/mappings.js +++ b/src/plugins/console/public/lib/mappings/mappings.js @@ -43,7 +43,7 @@ export function expandAliases(indicesOrAliases) { if (typeof indicesOrAliases === 'string') { indicesOrAliases = [indicesOrAliases]; } - indicesOrAliases = $.map(indicesOrAliases, function(iOrA) { + indicesOrAliases = $.map(indicesOrAliases, function (iOrA) { if (perAliasIndexes[iOrA]) { return perAliasIndexes[iOrA]; } @@ -52,7 +52,7 @@ export function expandAliases(indicesOrAliases) { let ret = [].concat.apply([], indicesOrAliases); ret.sort(); let last; - ret = $.map(ret, function(v) { + ret = $.map(ret, function (v) { const r = last === v ? null : v; last = v; return r; @@ -80,7 +80,7 @@ export function getFields(indices, types) { ret = f ? f : []; } else { // filter what we need - $.each(typeDict, function(type, fields) { + $.each(typeDict, function (type, fields) { if (!types || types.length === 0 || $.inArray(type, types) !== -1) { ret.push(fields); } @@ -90,7 +90,7 @@ export function getFields(indices, types) { } } else { // multi index mode. - $.each(perIndexTypes, function(index) { + $.each(perIndexTypes, function (index) { if (!indices || indices.length === 0 || $.inArray(index, indices) !== -1) { ret.push(getFields(index, types)); } @@ -98,7 +98,7 @@ export function getFields(indices, types) { ret = [].concat.apply([], ret); } - return _.uniq(ret, function(f) { + return _.uniq(ret, function (f) { return f.name + ':' + f.type; }); } @@ -113,12 +113,12 @@ export function getTypes(indices) { } // filter what we need - $.each(typeDict, function(type) { + $.each(typeDict, function (type) { ret.push(type); }); } else { // multi index mode. - $.each(perIndexTypes, function(index) { + $.each(perIndexTypes, function (index) { if (!indices || $.inArray(index, indices) !== -1) { ret.push(getTypes(index)); } @@ -131,11 +131,11 @@ export function getTypes(indices) { export function getIndices(includeAliases) { const ret = []; - $.each(perIndexTypes, function(index) { + $.each(perIndexTypes, function (index) { ret.push(index); }); if (typeof includeAliases === 'undefined' ? true : includeAliases) { - $.each(perAliasIndexes, function(alias) { + $.each(perAliasIndexes, function (alias) { ret.push(alias); }); } @@ -151,7 +151,7 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) { function applyPathSettings(nestedFieldNames) { const pathType = fieldMapping.path || 'full'; if (pathType === 'full') { - return $.map(nestedFieldNames, function(f) { + return $.map(nestedFieldNames, function (f) { f.name = fieldName + '.' + f.name; return f; }); @@ -174,7 +174,7 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) { } if (fieldMapping.fields) { - nestedFields = $.map(fieldMapping.fields, function(fieldMapping, fieldName) { + nestedFields = $.map(fieldMapping.fields, function (fieldMapping, fieldName) { return getFieldNamesFromFieldMapping(fieldName, fieldMapping); }); nestedFields = applyPathSettings(nestedFields); @@ -186,12 +186,12 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) { } function getFieldNamesFromProperties(properties = {}) { - const fieldList = $.map(properties, function(fieldMapping, fieldName) { + const fieldList = $.map(properties, function (fieldMapping, fieldName) { return getFieldNamesFromFieldMapping(fieldName, fieldMapping); }); // deduping - return _.uniq(fieldList, function(f) { + return _.uniq(fieldList, function (f) { return f.name + ':' + f.type; }); } @@ -203,7 +203,7 @@ function loadTemplates(templatesObject = {}) { export function loadMappings(mappings) { perIndexTypes = {}; - $.each(mappings, function(index, indexMapping) { + $.each(mappings, function (index, indexMapping) { const normalizedIndexMappings = {}; // Migrate 1.0.0 mappings. This format has changed, so we need to extract the underlying mapping. @@ -211,7 +211,7 @@ export function loadMappings(mappings) { indexMapping = indexMapping.mappings; } - $.each(indexMapping, function(typeName, typeMapping) { + $.each(indexMapping, function (typeName, typeMapping) { if (typeName === 'properties') { const fieldList = getFieldNamesFromProperties(typeMapping); normalizedIndexMappings[typeName] = fieldList; @@ -226,11 +226,11 @@ export function loadMappings(mappings) { export function loadAliases(aliases) { perAliasIndexes = {}; - $.each(aliases || {}, function(index, omdexAliases) { + $.each(aliases || {}, function (index, omdexAliases) { // verify we have an index defined. useful when mapping loading is disabled perIndexTypes[index] = perIndexTypes[index] || {}; - $.each(omdexAliases.aliases || {}, function(alias) { + $.each(omdexAliases.aliases || {}, function (alias) { if (alias === index) { return; } // alias which is identical to index means no index. diff --git a/src/plugins/console/public/lib/token_iterator/token_iterator.ts b/src/plugins/console/public/lib/token_iterator/token_iterator.ts index 643938536b306..5939741bc96e6 100644 --- a/src/plugins/console/public/lib/token_iterator/token_iterator.ts +++ b/src/plugins/console/public/lib/token_iterator/token_iterator.ts @@ -33,7 +33,7 @@ export class TokenIterator { constructor(private readonly provider: TokensProvider, startPosition: Position) { this.tokensLineCache = this.provider.getTokens(startPosition.lineNumber) || []; - const tokenIdx = this.tokensLineCache.findIndex(token => + const tokenIdx = this.tokensLineCache.findIndex((token) => isColumnInTokenRange(startPosition.column, token) ); if (tokenIdx > -1) { diff --git a/src/plugins/console/public/lib/utils/__tests__/utils.test.js b/src/plugins/console/public/lib/utils/__tests__/utils.test.js index 3a2e6a54c1328..e47e71c742a81 100644 --- a/src/plugins/console/public/lib/utils/__tests__/utils.test.js +++ b/src/plugins/console/public/lib/utils/__tests__/utils.test.js @@ -20,7 +20,7 @@ import * as utils from '../'; describe('Utils class', () => { - test('extract deprecation messages', function() { + test('extract deprecation messages', function () { expect( utils.extractDeprecationMessages( '299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a warning" "Mon, 27 Feb 2017 14:52:14 GMT"' @@ -70,7 +70,7 @@ describe('Utils class', () => { ]); }); - test('unescape', function() { + test('unescape', function () { expect(utils.unescape('escaped backslash \\\\')).toEqual('escaped backslash \\'); expect(utils.unescape('a pair of \\"escaped quotes\\"')).toEqual('a pair of "escaped quotes"'); expect(utils.unescape('escaped quotes do not have to come in pairs: \\"')).toEqual( @@ -78,7 +78,7 @@ describe('Utils class', () => { ); }); - test('split on unquoted comma followed by space', function() { + test('split on unquoted comma followed by space', function () { expect(utils.splitOnUnquotedCommaSpace('a, b')).toEqual(['a', 'b']); expect(utils.splitOnUnquotedCommaSpace('a,b, c')).toEqual(['a,b', 'c']); expect(utils.splitOnUnquotedCommaSpace('"a, b"')).toEqual(['"a, b"']); diff --git a/src/plugins/console/public/lib/utils/index.ts b/src/plugins/console/public/lib/utils/index.ts index 0ebea0f9d4055..917988e0e811b 100644 --- a/src/plugins/console/public/lib/utils/index.ts +++ b/src/plugins/console/public/lib/utils/index.ts @@ -61,7 +61,7 @@ export function extractDeprecationMessages(warnings: string) { // pattern for valid warning header const re = /\d{3} [0-9a-zA-Z!#$%&'*+-.^_`|~]+ \"((?:\t| |!|[\x23-\x5b]|[\x5d-\x7e]|[\x80-\xff]|\\\\|\\")*)\"(?: \"[^"]*\")?/; // split on any comma that is followed by an even number of quotes - return _.map(splitOnUnquotedCommaSpace(warnings), warning => { + return _.map(splitOnUnquotedCommaSpace(warnings), (warning) => { const match = re.exec(warning); // extract the actual warning if there was a match return '#! Deprecation: ' + (match !== null ? unescape(match[1]) : warning); diff --git a/src/plugins/console/public/services/history.ts b/src/plugins/console/public/services/history.ts index 04dae0beacefe..440014b362c7a 100644 --- a/src/plugins/console/public/services/history.ts +++ b/src/plugins/console/public/services/history.ts @@ -34,7 +34,7 @@ export class History { } getHistory() { - return this.getHistoryKeys().map(key => this.storage.get(key)); + return this.getHistoryKeys().map((key) => this.storage.get(key)); } // This is used as an optimization mechanism so that different components @@ -50,7 +50,7 @@ export class History { addToHistory(endpoint: string, method: string, data: any) { const keys = this.getHistoryKeys(); keys.splice(0, 500); // only maintain most recent X; - keys.forEach(key => { + keys.forEach((key) => { this.storage.delete(key); }); @@ -89,7 +89,7 @@ export class History { } clearHistory() { - this.getHistoryKeys().forEach(key => this.storage.delete(key)); + this.getHistoryKeys().forEach((key) => this.storage.delete(key)); } } diff --git a/src/plugins/console/server/__tests__/elasticsearch_proxy_config.js b/src/plugins/console/server/__tests__/elasticsearch_proxy_config.js index df8b49b0a089e..fcf385165a591 100644 --- a/src/plugins/console/server/__tests__/elasticsearch_proxy_config.js +++ b/src/plugins/console/server/__tests__/elasticsearch_proxy_config.js @@ -31,9 +31,9 @@ const getDefaultElasticsearchConfig = () => { }; }; -describe('plugins/console', function() { - describe('#getElasticsearchProxyConfig', function() { - it('sets timeout', function() { +describe('plugins/console', function () { + describe('#getElasticsearchProxyConfig', function () { + it('sets timeout', function () { const value = 1000; const proxyConfig = getElasticsearchProxyConfig({ ...getDefaultElasticsearchConfig(), @@ -42,7 +42,7 @@ describe('plugins/console', function() { expect(proxyConfig.timeout).to.be(value); }); - it(`uses https.Agent when url's protocol is https`, function() { + it(`uses https.Agent when url's protocol is https`, function () { const { agent } = getElasticsearchProxyConfig({ ...getDefaultElasticsearchConfig(), hosts: ['https://localhost:9200'], @@ -50,21 +50,21 @@ describe('plugins/console', function() { expect(agent).to.be.a(https.Agent); }); - it(`uses http.Agent when url's protocol is http`, function() { + it(`uses http.Agent when url's protocol is http`, function () { const { agent } = getElasticsearchProxyConfig(getDefaultElasticsearchConfig()); expect(agent).to.be.a(http.Agent); }); - describe('ssl', function() { + describe('ssl', function () { let config; - beforeEach(function() { + beforeEach(function () { config = { ...getDefaultElasticsearchConfig(), hosts: ['https://localhost:9200'], }; }); - it('sets rejectUnauthorized to false when verificationMode is none', function() { + it('sets rejectUnauthorized to false when verificationMode is none', function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { ...config.ssl, verificationMode: 'none' }, @@ -72,7 +72,7 @@ describe('plugins/console', function() { expect(agent.options.rejectUnauthorized).to.be(false); }); - it('sets rejectUnauthorized to true when verificationMode is certificate', function() { + it('sets rejectUnauthorized to true when verificationMode is certificate', function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { ...config.ssl, verificationMode: 'certificate' }, @@ -80,7 +80,7 @@ describe('plugins/console', function() { expect(agent.options.rejectUnauthorized).to.be(true); }); - it('sets checkServerIdentity to not check hostname when verificationMode is certificate', function() { + it('sets checkServerIdentity to not check hostname when verificationMode is certificate', function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { ...config.ssl, verificationMode: 'certificate' }, @@ -99,7 +99,7 @@ describe('plugins/console', function() { expect(result).to.be(undefined); }); - it('sets rejectUnauthorized to true when verificationMode is full', function() { + it('sets rejectUnauthorized to true when verificationMode is full', function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { ...config.ssl, verificationMode: 'full' }, @@ -108,7 +108,7 @@ describe('plugins/console', function() { expect(agent.options.rejectUnauthorized).to.be(true); }); - it(`doesn't set checkServerIdentity when verificationMode is full`, function() { + it(`doesn't set checkServerIdentity when verificationMode is full`, function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { ...config.ssl, verificationMode: 'full' }, @@ -117,7 +117,7 @@ describe('plugins/console', function() { expect(agent.options.checkServerIdentity).to.be(undefined); }); - it(`sets ca when certificateAuthorities are specified`, function() { + it(`sets ca when certificateAuthorities are specified`, function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { ...config.ssl, certificateAuthorities: ['content-of-some-path'] }, @@ -127,7 +127,7 @@ describe('plugins/console', function() { }); describe('when alwaysPresentCertificate is false', () => { - it(`doesn't set cert and key when certificate and key paths are specified`, function() { + it(`doesn't set cert and key when certificate and key paths are specified`, function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { @@ -142,7 +142,7 @@ describe('plugins/console', function() { expect(agent.options.key).to.be(undefined); }); - it(`doesn't set passphrase when certificate, key and keyPassphrase are specified`, function() { + it(`doesn't set passphrase when certificate, key and keyPassphrase are specified`, function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { @@ -159,7 +159,7 @@ describe('plugins/console', function() { }); describe('when alwaysPresentCertificate is true', () => { - it(`sets cert and key when certificate and key are specified`, async function() { + it(`sets cert and key when certificate and key are specified`, async function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { @@ -174,7 +174,7 @@ describe('plugins/console', function() { expect(agent.options.key).to.be('content-of-another-path'); }); - it(`sets passphrase when certificate, key and keyPassphrase are specified`, function() { + it(`sets passphrase when certificate, key and keyPassphrase are specified`, function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { @@ -189,7 +189,7 @@ describe('plugins/console', function() { expect(agent.options.passphrase).to.be('secret'); }); - it(`doesn't set cert when only certificate path is specified`, async function() { + it(`doesn't set cert when only certificate path is specified`, async function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { @@ -204,7 +204,7 @@ describe('plugins/console', function() { expect(agent.options.key).to.be(undefined); }); - it(`doesn't set key when only key path is specified`, async function() { + it(`doesn't set key when only key path is specified`, async function () { const { agent } = getElasticsearchProxyConfig({ ...config, ssl: { diff --git a/src/plugins/console/server/__tests__/proxy_config.js b/src/plugins/console/server/__tests__/proxy_config.js index b0b85e6bb7d06..1f3a94c4fe20f 100644 --- a/src/plugins/console/server/__tests__/proxy_config.js +++ b/src/plugins/console/server/__tests__/proxy_config.js @@ -34,17 +34,17 @@ const matchGoogle = { const parsedGoogle = parseUrl('https://google.com/search'); const parsedLocalEs = parseUrl('https://localhost:5601/search'); -describe('ProxyConfig', function() { - describe('constructor', function() { - beforeEach(function() { +describe('ProxyConfig', function () { + describe('constructor', function () { + beforeEach(function () { sinon.stub(https, 'Agent'); }); - afterEach(function() { + afterEach(function () { https.Agent.restore(); }); - it('uses ca to create sslAgent', function() { + it('uses ca to create sslAgent', function () { const config = new ProxyConfig({ ssl: { ca: ['content-of-some-path'], @@ -62,7 +62,7 @@ describe('ProxyConfig', function() { }); }); - it('uses cert, and key to create sslAgent', function() { + it('uses cert, and key to create sslAgent', function () { const config = new ProxyConfig({ ssl: { cert: 'content-of-some-path', @@ -81,7 +81,7 @@ describe('ProxyConfig', function() { }); }); - it('uses ca, cert, and key to create sslAgent', function() { + it('uses ca, cert, and key to create sslAgent', function () { const config = new ProxyConfig({ ssl: { ca: ['content-of-some-path'], @@ -103,9 +103,9 @@ describe('ProxyConfig', function() { }); }); - describe('#getForParsedUri', function() { - describe('parsed url does not match', function() { - it('returns {}', function() { + describe('#getForParsedUri', function () { + describe('parsed url does not match', function () { + it('returns {}', function () { const config = new ProxyConfig({ match: matchGoogle, timeout: 100, @@ -115,8 +115,8 @@ describe('ProxyConfig', function() { }); }); - describe('parsed url does match', function() { - it('assigns timeout value', function() { + describe('parsed url does match', function () { + it('assigns timeout value', function () { const football = {}; const config = new ProxyConfig({ match: matchGoogle, @@ -126,7 +126,7 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri(parsedGoogle).timeout).to.be(football); }); - it('assigns ssl.verify to rejectUnauthorized', function() { + it('assigns ssl.verify to rejectUnauthorized', function () { const football = {}; const config = new ProxyConfig({ match: matchGoogle, @@ -138,9 +138,9 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri(parsedGoogle).rejectUnauthorized).to.be(football); }); - describe('uri us http', function() { - describe('ca is set', function() { - it('creates but does not output the agent', function() { + describe('uri us http', function () { + describe('ca is set', function () { + it('creates but does not output the agent', function () { const config = new ProxyConfig({ ssl: { ca: ['path/to/ca'], @@ -151,8 +151,8 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri({ protocol: 'http:' }).agent).to.be(undefined); }); }); - describe('cert is set', function() { - it('creates but does not output the agent', function() { + describe('cert is set', function () { + it('creates but does not output the agent', function () { const config = new ProxyConfig({ ssl: { cert: 'path/to/cert', @@ -163,8 +163,8 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri({ protocol: 'http:' }).agent).to.be(undefined); }); }); - describe('key is set', function() { - it('creates but does not output the agent', function() { + describe('key is set', function () { + it('creates but does not output the agent', function () { const config = new ProxyConfig({ ssl: { key: 'path/to/key', @@ -175,8 +175,8 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri({ protocol: 'http:' }).agent).to.be(undefined); }); }); - describe('cert + key are set', function() { - it('creates but does not output the agent', function() { + describe('cert + key are set', function () { + it('creates but does not output the agent', function () { const config = new ProxyConfig({ ssl: { cert: 'path/to/cert', @@ -190,9 +190,9 @@ describe('ProxyConfig', function() { }); }); - describe('uri us https', function() { - describe('ca is set', function() { - it('creates and outputs the agent', function() { + describe('uri us https', function () { + describe('ca is set', function () { + it('creates and outputs the agent', function () { const config = new ProxyConfig({ ssl: { ca: ['path/to/ca'], @@ -203,8 +203,8 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri({ protocol: 'https:' }).agent).to.be(config.sslAgent); }); }); - describe('cert is set', function() { - it('creates and outputs the agent', function() { + describe('cert is set', function () { + it('creates and outputs the agent', function () { const config = new ProxyConfig({ ssl: { cert: 'path/to/cert', @@ -215,8 +215,8 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri({ protocol: 'https:' }).agent).to.be(config.sslAgent); }); }); - describe('key is set', function() { - it('creates and outputs the agent', function() { + describe('key is set', function () { + it('creates and outputs the agent', function () { const config = new ProxyConfig({ ssl: { key: 'path/to/key', @@ -227,8 +227,8 @@ describe('ProxyConfig', function() { expect(config.getForParsedUri({ protocol: 'https:' }).agent).to.be(config.sslAgent); }); }); - describe('cert + key are set', function() { - it('creates and outputs the agent', function() { + describe('cert + key are set', function () { + it('creates and outputs the agent', function () { const config = new ProxyConfig({ ssl: { cert: 'path/to/cert', diff --git a/src/plugins/console/server/__tests__/proxy_config_collection.js b/src/plugins/console/server/__tests__/proxy_config_collection.js index e1bc099ac1e85..729972399f0bb 100644 --- a/src/plugins/console/server/__tests__/proxy_config_collection.js +++ b/src/plugins/console/server/__tests__/proxy_config_collection.js @@ -26,12 +26,12 @@ import { Agent as HttpsAgent } from 'https'; import { ProxyConfigCollection } from '../lib/proxy_config_collection'; -describe('ProxyConfigCollection', function() { - beforeEach(function() { +describe('ProxyConfigCollection', function () { + beforeEach(function () { sinon.stub(fs, 'readFileSync').callsFake(() => Buffer.alloc(0)); }); - afterEach(function() { + afterEach(function () { fs.readFileSync.restore(); }); @@ -86,67 +86,67 @@ describe('ProxyConfigCollection', function() { return collection.configForUri(uri).timeout; } - describe('http://localhost:5601', function() { - it('defaults to the first matching timeout', function() { + describe('http://localhost:5601', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('http://localhost:5601')).to.be(3); }); }); - describe('https://localhost:5601/.kibana', function() { - it('defaults to the first matching timeout', function() { + describe('https://localhost:5601/.kibana', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('https://localhost:5601/.kibana')).to.be(1); }); }); - describe('http://localhost:5602', function() { - it('defaults to the first matching timeout', function() { + describe('http://localhost:5602', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('http://localhost:5602')).to.be(4); }); }); - describe('https://localhost:5602', function() { - it('defaults to the first matching timeout', function() { + describe('https://localhost:5602', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('https://localhost:5602')).to.be(4); }); }); - describe('http://localhost:5603', function() { - it('defaults to the first matching timeout', function() { + describe('http://localhost:5603', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('http://localhost:5603')).to.be(4); }); }); - describe('https://localhost:5603', function() { - it('defaults to the first matching timeout', function() { + describe('https://localhost:5603', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('https://localhost:5603')).to.be(4); }); }); - describe('https://localhost:5601/index', function() { - it('defaults to the first matching timeout', function() { + describe('https://localhost:5601/index', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('https://localhost:5601/index')).to.be(2); }); }); - describe('http://localhost:5601/index', function() { - it('defaults to the first matching timeout', function() { + describe('http://localhost:5601/index', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('http://localhost:5601/index')).to.be(3); }); }); - describe('https://localhost:5601/index/type', function() { - it('defaults to the first matching timeout', function() { + describe('https://localhost:5601/index/type', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('https://localhost:5601/index/type')).to.be(2); }); }); - describe('http://notlocalhost', function() { - it('defaults to the first matching timeout', function() { + describe('http://notlocalhost', function () { + it('defaults to the first matching timeout', function () { expect(getTimeout('http://notlocalhost')).to.be(5); }); }); - describe('collection with ssl config and root level verify:false', function() { + describe('collection with ssl config and root level verify:false', function () { function makeCollection() { return new ProxyConfigCollection([ { @@ -160,13 +160,13 @@ describe('ProxyConfigCollection', function() { ]); } - it('verifies for config that produces ssl agent', function() { + it('verifies for config that produces ssl agent', function () { const conf = makeCollection().configForUri('https://es.internal.org/_search'); expect(conf.agent.options).to.have.property('rejectUnauthorized', true); expect(conf.agent).to.be.an(HttpsAgent); }); - it('disabled verification for * config', function() { + it('disabled verification for * config', function () { const conf = makeCollection().configForUri('https://extenal.org/_search'); expect(conf).to.have.property('rejectUnauthorized', false); expect(conf.agent).to.be(undefined); diff --git a/src/plugins/console/server/__tests__/proxy_route/body.test.ts b/src/plugins/console/server/__tests__/proxy_route/body.test.ts index 252009a8977f3..526c2e4c78ea4 100644 --- a/src/plugins/console/server/__tests__/proxy_route/body.test.ts +++ b/src/plugins/console/server/__tests__/proxy_route/body.test.ts @@ -46,9 +46,9 @@ describe('Console Proxy Route', () => { }); const readStream = (s: Readable) => - new Promise(resolve => { + new Promise((resolve) => { let v = ''; - s.on('data', data => { + s.on('data', (data) => { v += data; }); s.on('end', () => resolve(v)); diff --git a/src/plugins/console/server/__tests__/set_headers.js b/src/plugins/console/server/__tests__/set_headers.js index 1f349cbbb571e..3ddd30777bb5b 100644 --- a/src/plugins/console/server/__tests__/set_headers.js +++ b/src/plugins/console/server/__tests__/set_headers.js @@ -20,18 +20,18 @@ import expect from '@kbn/expect'; import { setHeaders } from '../lib'; -describe('#set_headers', function() { - it('throws if not given an object as the first argument', function() { +describe('#set_headers', function () { + it('throws if not given an object as the first argument', function () { const fn = () => setHeaders(null, {}); expect(fn).to.throwError(); }); - it('throws if not given an object as the second argument', function() { + it('throws if not given an object as the second argument', function () { const fn = () => setHeaders({}, null); expect(fn).to.throwError(); }); - it('returns a new object', function() { + it('returns a new object', function () { const originalHeaders = {}; const newHeaders = {}; const returnedHeaders = setHeaders(originalHeaders, newHeaders); @@ -39,14 +39,14 @@ describe('#set_headers', function() { expect(returnedHeaders).not.to.be(newHeaders); }); - it('returns object with newHeaders merged with originalHeaders', function() { + it('returns object with newHeaders merged with originalHeaders', function () { const originalHeaders = { foo: 'bar' }; const newHeaders = { one: 'two' }; const returnedHeaders = setHeaders(originalHeaders, newHeaders); expect(returnedHeaders).to.eql({ foo: 'bar', one: 'two' }); }); - it('returns object where newHeaders takes precedence for any matching keys', function() { + it('returns object where newHeaders takes precedence for any matching keys', function () { const originalHeaders = { foo: 'bar' }; const newHeaders = { one: 'two', foo: 'notbar' }; const returnedHeaders = setHeaders(originalHeaders, newHeaders); diff --git a/src/plugins/console/server/__tests__/wildcard_matcher.js b/src/plugins/console/server/__tests__/wildcard_matcher.js index ccf68f3c16ca3..3e0e06efad50f 100644 --- a/src/plugins/console/server/__tests__/wildcard_matcher.js +++ b/src/plugins/console/server/__tests__/wildcard_matcher.js @@ -32,8 +32,8 @@ function shouldNot(candidate, ...constructorArgs) { } } -describe('WildcardMatcher', function() { - describe('pattern = *', function() { +describe('WildcardMatcher', function () { + describe('pattern = *', function () { it('matches http', () => should('http', '*')); it('matches https', () => should('https', '*')); it('matches nothing', () => should('', '*')); @@ -41,12 +41,12 @@ describe('WildcardMatcher', function() { it('matches localhost', () => should('localhost', '*')); it('matches a path', () => should('/index/_search', '*')); - describe('defaultValue = /', function() { + describe('defaultValue = /', function () { it('matches /', () => should('/', '*', '/')); }); }); - describe('pattern = http', function() { + describe('pattern = http', function () { it('matches http', () => should('http', 'http')); it('does not match https', () => shouldNot('https', 'http')); it('does not match nothing', () => shouldNot('', 'http')); @@ -54,7 +54,7 @@ describe('WildcardMatcher', function() { it('does not match a path', () => shouldNot('/index/_search', 'http')); }); - describe('pattern = 560{1..9}', function() { + describe('pattern = 560{1..9}', function () { it('does not match http', () => shouldNot('http', '560{1..9}')); it('does not matches 5600', () => shouldNot('5600', '560{1..9}')); it('matches 5601', () => should('5601', '560{1..9}')); diff --git a/src/plugins/console/server/lib/proxy_config_collection.ts b/src/plugins/console/server/lib/proxy_config_collection.ts index 5d0b02fed5b18..36f4f8359c5fe 100644 --- a/src/plugins/console/server/lib/proxy_config_collection.ts +++ b/src/plugins/console/server/lib/proxy_config_collection.ts @@ -26,7 +26,7 @@ export class ProxyConfigCollection { private configs: ProxyConfig[]; constructor(configs: Array<{ match: any; timeout: number }> = []) { - this.configs = configs.map(settings => new ProxyConfig(settings)); + this.configs = configs.map((settings) => new ProxyConfig(settings)); } hasConfig() { @@ -35,7 +35,7 @@ export class ProxyConfigCollection { configForUri(uri: string): object { const parsedUri = parseUrl(uri); - const settings = this.configs.map(config => config.getForParsedUri(parsedUri as any)); + const settings = this.configs.map((config) => config.getForParsedUri(parsedUri as any)); return defaultsDeep({}, ...settings); } } diff --git a/src/plugins/console/server/lib/proxy_request.ts b/src/plugins/console/server/lib/proxy_request.ts index cc957551e47a7..4c6c7c21f32a4 100644 --- a/src/plugins/console/server/lib/proxy_request.ts +++ b/src/plugins/console/server/lib/proxy_request.ts @@ -58,7 +58,7 @@ export const proxyRequest = ({ }); const finalUserHeaders = { ...headers }; - const hasHostHeader = Object.keys(finalUserHeaders).some(key => key.toLowerCase() === 'host'); + const hasHostHeader = Object.keys(finalUserHeaders).some((key) => key.toLowerCase() === 'host'); if (!hasHostHeader) { finalUserHeaders.host = hostname; } @@ -79,7 +79,7 @@ export const proxyRequest = ({ agent, }); - req.once('response', res => { + req.once('response', (res) => { resolved = true; resolve(res); }); diff --git a/src/plugins/console/server/lib/spec_definitions/js/filter.ts b/src/plugins/console/server/lib/spec_definitions/js/filter.ts index 27e02f7cf1837..b5e99e610b8ba 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/filter.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/filter.ts @@ -58,13 +58,6 @@ filters.limit = { value: 100, }; -filters.type = { - __template: { - value: 'TYPE', - }, - value: '{type}', -}; - filters.geo_bounding_box = { __template: { FIELD: { diff --git a/src/plugins/console/server/lib/spec_definitions/js/mappings.ts b/src/plugins/console/server/lib/spec_definitions/js/mappings.ts index 8491bc17a2ff6..fbc9a822e509c 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/mappings.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/mappings.ts @@ -177,7 +177,7 @@ export const mappings = (specService: SpecDefinitionsService) => { 'week_date_time', 'week_date_time_no_millis', ], - function(s) { + function (s) { return ['basic_' + s, 'strict_' + s]; } ), diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/update_by_query.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/update_by_query.json new file mode 100644 index 0000000000000..44819eda6e29e --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/update_by_query.json @@ -0,0 +1,17 @@ +{ + "update_by_query": { + "data_autocomplete_rules": { + "conflicts": "", + "query": { + "__scope_link": "GLOBAL.query" + }, + "script": { + "__template": { + "source": "", + "lang": "painless" + }, + "__scope_link": "GLOBAL.script" + } + } + } +} diff --git a/src/plugins/console/server/plugin.ts b/src/plugins/console/server/plugin.ts index 85b728ea83891..eedd1541e8898 100644 --- a/src/plugins/console/server/plugin.ts +++ b/src/plugins/console/server/plugin.ts @@ -45,10 +45,7 @@ export class ConsoleServerPlugin implements Plugin { }, })); - const config = await this.ctx.config - .create() - .pipe(first()) - .toPromise(); + const config = await this.ctx.config.create().pipe(first()).toPromise(); const { elasticsearch } = await this.ctx.config.legacy.globalConfig$.pipe(first()).toPromise(); diff --git a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts index 9446289ff03ea..272f63322ffaa 100644 --- a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts +++ b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts @@ -57,7 +57,7 @@ function toURL(base: string, path: string) { } function filterHeaders(originalHeaders: object, headersToKeep: string[]): object { - const normalizeHeader = function(header: any) { + const normalizeHeader = function (header: any) { if (!header) { return ''; } @@ -131,7 +131,7 @@ export const createHandler = ({ const { body, query } = request; const { path, method } = query; - if (!pathFilters.some(re => re.test(path))) { + if (!pathFilters.some((re) => re.test(path))) { return response.forbidden({ body: `Error connecting to '${path}':\n\nUnable to send requests to that path.`, headers: { diff --git a/src/plugins/console/server/routes/api/console/proxy/validation_config.ts b/src/plugins/console/server/routes/api/console/proxy/validation_config.ts index f2372e9ee80d0..c46fc5c808a42 100644 --- a/src/plugins/console/server/routes/api/console/proxy/validation_config.ts +++ b/src/plugins/console/server/routes/api/console/proxy/validation_config.ts @@ -22,9 +22,9 @@ export type Query = TypeOf; export type Body = TypeOf; const acceptedHttpVerb = schema.string({ - validate: method => { + validate: (method) => { return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE'].some( - verb => verb.toLowerCase() === method.toLowerCase() + (verb) => verb.toLowerCase() === method.toLowerCase() ) ? undefined : `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE']. Received '${method}'.`; @@ -32,7 +32,7 @@ const acceptedHttpVerb = schema.string({ }); const nonEmptyString = schema.string({ - validate: s => (s === '' ? 'Expected non-empty string' : undefined), + validate: (s) => (s === '' ? 'Expected non-empty string' : undefined), }); export const routeValidationConfig = { diff --git a/src/plugins/console/server/services/spec_definitions_service.ts b/src/plugins/console/server/services/spec_definitions_service.ts index 39a8d5094bd5c..ccd3b6b1c0a82 100644 --- a/src/plugins/console/server/services/spec_definitions_service.ts +++ b/src/plugins/console/server/services/spec_definitions_service.ts @@ -45,7 +45,7 @@ export class SpecDefinitionsService { copiedDescription = { ...this.endpoints[endpoint] }; } let urlParamsDef: any; - _.each(description.patterns || [], function(p) { + _.each(description.patterns || [], function (p) { if (p.indexOf('{indices}') >= 0) { urlParamsDef = urlParamsDef || {}; urlParamsDef.ignore_unavailable = '__flag__'; @@ -114,7 +114,7 @@ export class SpecDefinitionsService { const overrideFiles = glob.sync(join(dirname, 'overrides', '*.json')); return generatedFiles.reduce((acc, file) => { - const overrideFile = overrideFiles.find(f => basename(f) === basename(file)); + const overrideFile = overrideFiles.find((f) => basename(f) === basename(file)); const loadedSpec = JSON.parse(readFileSync(file, 'utf8')); if (overrideFile) { merge(loadedSpec, JSON.parse(readFileSync(overrideFile, 'utf8'))); @@ -135,16 +135,16 @@ export class SpecDefinitionsService { private loadJsonSpec() { const result = this.loadJSONSpecInDir(PATH_TO_OSS_JSON_SPEC); - this.extensionSpecFilePaths.forEach(extensionSpecFilePath => { + this.extensionSpecFilePaths.forEach((extensionSpecFilePath) => { merge(result, this.loadJSONSpecInDir(extensionSpecFilePath)); }); - Object.keys(result).forEach(endpoint => { + Object.keys(result).forEach((endpoint) => { this.addEndpointDescription(endpoint, result[endpoint]); }); } private loadJSSpec() { - jsSpecLoaders.forEach(addJsSpec => addJsSpec(this)); + jsSpecLoaders.forEach((addJsSpec) => addJsSpec(this)); } } diff --git a/src/plugins/dashboard/common/migrate_to_730_panels.test.ts b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts index 0867909225ddb..f7a03a54376ca 100644 --- a/src/plugins/dashboard/common/migrate_to_730_panels.test.ts +++ b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts @@ -134,7 +134,7 @@ test('6.0 migrates old panel data in the right order', () => { false, {} ) as SavedDashboardPanel730ToLatest[]; - const foo8Panel = newPanels.find(panel => panel.id === 'foo8'); + const foo8Panel = newPanels.find((panel) => panel.id === 'foo8'); expect(foo8Panel).toBeDefined(); expect((foo8Panel! as any).row).toBe(undefined); @@ -195,7 +195,7 @@ test('6.0 migrates old panel data in the right order without margins', () => { false, {} ) as SavedDashboardPanel730ToLatest[]; - const foo8Panel = newPanels.find(panel => panel.id === 'foo8'); + const foo8Panel = newPanels.find((panel) => panel.id === 'foo8'); expect(foo8Panel).toBeDefined(); expect((foo8Panel! as any).row).toBe(undefined); @@ -239,7 +239,7 @@ test('6.0 migrates old panel data in the right order with margins', () => { true, {} ) as SavedDashboardPanel730ToLatest[]; - const foo8Panel = newPanels.find(panel => panel.id === 'foo8'); + const foo8Panel = newPanels.find((panel) => panel.id === 'foo8'); expect(foo8Panel).toBeDefined(); expect((foo8Panel! as any).row).toBe(undefined); diff --git a/src/plugins/dashboard/common/migrate_to_730_panels.ts b/src/plugins/dashboard/common/migrate_to_730_panels.ts index b89345f0a872c..d870e3c87d6ab 100644 --- a/src/plugins/dashboard/common/migrate_to_730_panels.ts +++ b/src/plugins/dashboard/common/migrate_to_730_panels.ts @@ -150,7 +150,7 @@ function migrate610PanelToLatest( useMargins: boolean, uiState?: { [key: string]: { [key: string]: unknown } } ): RawSavedDashboardPanel730ToLatest { - (['w', 'x', 'h', 'y'] as Array).forEach(key => { + (['w', 'x', 'h', 'y'] as Array).forEach((key) => { if (panel.gridData[key] === undefined) { throw new Error( i18n.translate('dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', { @@ -285,7 +285,7 @@ export function migratePanelsTo730( useMargins: boolean, uiState?: { [key: string]: { [key: string]: unknown } } ): RawSavedDashboardPanel730ToLatest[] { - return panels.map(panel => { + return panels.map((panel) => { if (isPre61Panel(panel)) { return migratePre61PanelToLatest(panel, version, useMargins, uiState); } diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx index 17943333d25b0..e7534fa09aa3f 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx @@ -96,7 +96,7 @@ test('Clone adds a new embeddable', async () => { await action.execute({ embeddable }); expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount + 1); const newPanelId = Object.keys(container.getInput().panels).find( - key => !originalPanelKeySet.has(key) + (key) => !originalPanelKeySet.has(key) ); expect(newPanelId).toBeDefined(); const newPanel = container.getInput().panels[newPanelId!]; diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx index ff4e50ba8c327..96210358c05e4 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx @@ -97,10 +97,7 @@ export class ClonePanelAction implements ActionByType }); const cloneRegex = new RegExp(`\\(${clonedTag}\\)`, 'g'); const cloneNumberRegex = new RegExp(`\\(${clonedTag} [0-9]+\\)`, 'g'); - const baseTitle = rawTitle - .replace(cloneNumberRegex, '') - .replace(cloneRegex, '') - .trim(); + const baseTitle = rawTitle.replace(cloneNumberRegex, '').replace(cloneRegex, '').trim(); const similarSavedObjects = await this.core.savedObjects.client.find({ type: embeddableType, diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx index fe9a19030602e..57fe4acf08145 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx @@ -107,7 +107,7 @@ export class ReplacePanelFlyout extends React.Component { })} savedObjectMetaData={[...this.props.getEmbeddableFactories()] .filter( - embeddableFactory => + (embeddableFactory) => Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType ) .map(({ savedObjectMetaData }) => savedObjectMetaData as any)} diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts index 8e29d2734065e..543450916c505 100644 --- a/src/plugins/dashboard/public/application/application.ts +++ b/src/plugins/dashboard/public/application/application.ts @@ -134,7 +134,7 @@ function createLocalAngularModule() { function createLocalIconModule() { angular .module('app/dashboard/icon', ['react']) - .directive('icon', reactDirective => reactDirective(EuiIcon)); + .directive('icon', (reactDirective) => reactDirective(EuiIcon)); } function createLocalI18nModule() { diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index fa2f06bfcdcdd..a59d1e8c546d4 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -41,6 +41,7 @@ import { QueryState, SavedQuery, syncQueryStateWithUrl, + UI_SETTINGS, } from '../../../data/public'; import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public'; @@ -160,7 +161,7 @@ export class DashboardAppController { set: ({ filters }) => dashboardStateManager.setFilters(filters || []), get: () => ({ filters: dashboardStateManager.appState.filters }), state$: dashboardStateManager.appState$.pipe( - map(state => ({ + map((state) => ({ filters: state.filters, })) ), @@ -228,7 +229,7 @@ export class DashboardAppController { } let panelIndexPatterns: IndexPattern[] = []; - Object.values(container.getChildIds()).forEach(id => { + Object.values(container.getChildIds()).forEach((id) => { const embeddableInstance = container.getChild(id); if (isErrorEmbeddable(embeddableInstance)) return; const embeddableIndexPatterns = (embeddableInstance.getOutput() as any).indexPatterns; @@ -242,7 +243,7 @@ export class DashboardAppController { $scope.indexPatterns = panelIndexPatterns; }); } else { - indexPatterns.getDefault().then(defaultIndexPattern => { + indexPatterns.getDefault().then((defaultIndexPattern) => { $scope.$evalAsync(() => { $scope.indexPatterns = [defaultIndexPattern as IndexPattern]; }); @@ -421,7 +422,7 @@ export class DashboardAppController { dirty: !dash.id, }; - dashboardStateManager.registerChangeListener(status => { + dashboardStateManager.registerChangeListener((status) => { this.appStatus.dirty = status.dirty || !dash.id; updateState(); }); @@ -430,7 +431,8 @@ export class DashboardAppController { dashboardStateManager.getQuery() || { query: '', language: - localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'), + localStorage.get('kibana.userQueryLanguage') || + uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), }, queryFilter.getFilters() ); @@ -484,7 +486,7 @@ export class DashboardAppController { differences.filters = appStateDashboardInput.filters; } - Object.keys(_.omit(containerInput, 'filters')).forEach(key => { + Object.keys(_.omit(containerInput, 'filters')).forEach((key) => { const containerValue = (containerInput as { [key: string]: unknown })[key]; const appStateValue = ((appStateDashboardInput as unknown) as { [key: string]: unknown })[ key @@ -506,7 +508,7 @@ export class DashboardAppController { } }; - $scope.updateQueryAndFetch = function({ query, dateRange }) { + $scope.updateQueryAndFetch = function ({ query, dateRange }) { if (dateRange) { timefilter.setTime(dateRange); } @@ -554,7 +556,7 @@ export class DashboardAppController { () => { return dashboardStateManager.getSavedQueryId(); }, - newSavedQueryId => { + (newSavedQueryId) => { if (!newSavedQueryId) { $scope.savedQuery = undefined; return; @@ -579,7 +581,7 @@ export class DashboardAppController { $scope.$watch( () => dashboardCapabilities.saveQuery, - newCapability => { + (newCapability) => { $scope.showSaveQuery = newCapability as boolean; } ); @@ -712,7 +714,7 @@ export class DashboardAppController { }), } ) - .then(isConfirmed => { + .then((isConfirmed) => { if (isConfirmed) { revertChangesAndExitEditMode(); } @@ -735,7 +737,7 @@ export class DashboardAppController { */ function save(saveOptions: SavedObjectSaveOpts): Promise { return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions) - .then(function(id) { + .then(function (id) { if (id) { notifications.toasts.addSuccess({ title: i18n.translate('dashboard.dashboardWasSavedSuccessMessage', { @@ -758,7 +760,7 @@ export class DashboardAppController { } return { id }; }) - .catch(error => { + .catch((error) => { notifications.toasts.addDanger({ title: i18n.translate('dashboard.dashboardWasNotSavedDangerMessage', { defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`, @@ -904,7 +906,7 @@ export class DashboardAppController { } }; - navActions[TopNavIds.OPTIONS] = anchorElement => { + navActions[TopNavIds.OPTIONS] = (anchorElement) => { showOptionsPopover({ anchorElement, useMargins: dashboardStateManager.getUseMargins(), @@ -920,7 +922,7 @@ export class DashboardAppController { if (share) { // the share button is only availabale if "share" plugin contract enabled - navActions[TopNavIds.SHARE] = anchorElement => { + navActions[TopNavIds.SHARE] = (anchorElement) => { share.toggleShareContextMenu({ anchorElement, allowEmbed: true, @@ -950,7 +952,7 @@ export class DashboardAppController { }, }); - const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { + const visibleSubscription = chrome.getIsVisible$().subscribe((isVisible) => { $scope.$evalAsync(() => { $scope.isVisible = isVisible; showSearchBar = isVisible || showFilterBar(); diff --git a/src/plugins/dashboard/public/application/dashboard_state.test.ts b/src/plugins/dashboard/public/application/dashboard_state.test.ts index cfb7fc7e56e42..e32bb9b9ecabe 100644 --- a/src/plugins/dashboard/public/application/dashboard_state.test.ts +++ b/src/plugins/dashboard/public/application/dashboard_state.test.ts @@ -24,7 +24,7 @@ import { InputTimeRange, TimefilterContract, TimeRange } from 'src/plugins/data/ import { ViewMode } from 'src/plugins/embeddable/public'; import { createKbnUrlStateStorage } from 'src/plugins/kibana_utils/public'; -describe('DashboardState', function() { +describe('DashboardState', function () { let dashboardState: DashboardStateManager; const savedDashboard = getSavedDashboardMock(); @@ -48,8 +48,8 @@ describe('DashboardState', function() { }); } - describe('syncTimefilterWithDashboard', function() { - test('syncs quick time', function() { + describe('syncTimefilterWithDashboard', function () { + test('syncs quick time', function () { savedDashboard.timeRestore = true; savedDashboard.timeFrom = 'now/w'; savedDashboard.timeTo = 'now/w'; @@ -64,7 +64,7 @@ describe('DashboardState', function() { expect(mockTime.from).toBe('now/w'); }); - test('syncs relative time', function() { + test('syncs relative time', function () { savedDashboard.timeRestore = true; savedDashboard.timeFrom = 'now-13d'; savedDashboard.timeTo = 'now'; @@ -79,7 +79,7 @@ describe('DashboardState', function() { expect(mockTime.from).toBe('now-13d'); }); - test('syncs absolute time', function() { + test('syncs absolute time', function () { savedDashboard.timeRestore = true; savedDashboard.timeFrom = '2015-09-19 06:31:44.000'; savedDashboard.timeTo = '2015-09-29 06:31:44.000'; @@ -95,7 +95,7 @@ describe('DashboardState', function() { }); }); - describe('isDirty', function() { + describe('isDirty', function () { beforeAll(() => { initDashboardState(); }); diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index b03ea95069a3d..5fed38487dc54 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -139,8 +139,8 @@ export class DashboardStateManager { this.stateContainer = createStateContainer( initialState, { - set: state => (prop, value) => ({ ...state, [prop]: value }), - setOption: state => (option, value) => ({ + set: (state) => (prop, value) => ({ ...state, [prop]: value }), + setOption: (state) => (option, value) => ({ ...state, options: { ...state.options, @@ -162,7 +162,7 @@ export class DashboardStateManager { this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => { this.isDirty = this.checkIsDirty(); - this.changeListeners.forEach(listener => listener({ dirty: this.isDirty })); + this.changeListeners.forEach((listener) => listener({ dirty: this.isDirty })); }); // setup state syncing utils. state container will be synced with url into `this.STATE_STORAGE_KEY` query param @@ -218,7 +218,7 @@ export class DashboardStateManager { const savedDashboardPanelMap: { [key: string]: SavedDashboardPanel } = {}; const input = dashboardContainer.getInput(); - this.getPanels().forEach(savedDashboardPanel => { + this.getPanels().forEach((savedDashboardPanel) => { if (input.panels[savedDashboardPanel.panelIndex] !== undefined) { savedDashboardPanelMap[savedDashboardPanel.panelIndex] = savedDashboardPanel; } else { @@ -229,7 +229,7 @@ export class DashboardStateManager { const convertedPanelStateMap: { [key: string]: SavedDashboardPanel } = {}; - Object.values(input.panels).forEach(panelState => { + Object.values(input.panels).forEach((panelState) => { if (savedDashboardPanelMap[panelState.explicitInput.id] === undefined) { dirty = true; } @@ -271,7 +271,7 @@ export class DashboardStateManager { this.setQuery(input.query); } - this.changeListeners.forEach(listener => listener({ dirty })); + this.changeListeners.forEach((listener) => listener({ dirty })); } public getFullScreenMode() { diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index 6785a373c065b..3cebe2b847155 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -53,7 +53,7 @@ beforeEach(() => { options.embeddable = doStart(); }); -test('DashboardContainer initializes embeddables', async done => { +test('DashboardContainer initializes embeddables', async (done) => { const initialInput = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -64,7 +64,7 @@ test('DashboardContainer initializes embeddables', async done => { }); const container = new DashboardContainer(initialInput, options); - const subscription = container.getOutput$().subscribe(output => { + const subscription = container.getOutput$().subscribe((output) => { if (container.getOutput().embeddableLoaded['123']) { const embeddable = container.getChild('123'); expect(embeddable).toBeDefined(); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 7e25d80c9d619..5d4cc851cf455 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -186,7 +186,11 @@ export class DashboardContainer extends Container - + , dom diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index 948e22e86a302..9a2610a82b97d 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -80,6 +80,7 @@ function prepare(props?: Partial) { dashboardContainer = new DashboardContainer(initialInput, options); const defaultTestProps: DashboardGridProps = { container: dashboardContainer, + PanelComponent: () =>
, kibana: null as any, intl: null as any, }; @@ -167,7 +168,7 @@ test('DashboardGrid renders expanded panel', () => { ).toBeUndefined(); }); -test('DashboardGrid unmount unsubscribes', async done => { +test('DashboardGrid unmount unsubscribes', async (done) => { const { props, options } = prepare(); const component = mountWithIntl( diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index f8632011002d0..dcd07fe394c7d 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -30,7 +30,7 @@ import React from 'react'; import { Subscription } from 'rxjs'; import ReactGridLayout, { Layout } from 'react-grid-layout'; import { GridData } from '../../../../common'; -import { ViewMode, EmbeddableChildPanel } from '../../../embeddable_plugin'; +import { ViewMode, EmbeddableChildPanel, EmbeddableStart } from '../../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; import { DashboardPanelState } from '../types'; import { withKibana } from '../../../../../kibana_react/public'; @@ -115,6 +115,7 @@ const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid); export interface DashboardGridProps extends ReactIntl.InjectedIntlProps { kibana: DashboardReactContextValue; + PanelComponent: EmbeddableStart['EmbeddablePanel']; container: DashboardContainer; } @@ -199,7 +200,7 @@ class DashboardGridUi extends React.Component { } public buildLayoutFromPanels = (): GridData[] => { - return _.map(this.state.panels, panel => { + return _.map(this.state.panels, (panel) => { return panel.gridData; }); }; @@ -250,7 +251,7 @@ class DashboardGridUi extends React.Component { } }); - return _.map(panelsInOrder, panel => { + return _.map(panelsInOrder, (panel) => { const expandPanel = expandedPanelId !== undefined && expandedPanelId === panel.explicitInput.id; const hidePanel = expandedPanelId !== undefined && expandedPanelId !== panel.explicitInput.id; @@ -264,21 +265,14 @@ class DashboardGridUi extends React.Component { className={classes} key={panel.explicitInput.id} data-test-subj="dashboardPanel" - ref={reactGridItem => { + ref={(reactGridItem) => { this.gridItems[panel.explicitInput.id] = reactGridItem; }} >
); diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts index b95b7f394a27d..1b060c186db97 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts @@ -44,7 +44,7 @@ export function findTopLeftMostOpenSpace({ let maxY = -1; const currentPanelsArray = Object.values(currentPanels); - currentPanelsArray.forEach(panel => { + currentPanelsArray.forEach((panel) => { maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY); }); @@ -58,7 +58,7 @@ export function findTopLeftMostOpenSpace({ grid[y] = new Array(DASHBOARD_GRID_COLUMN_COUNT).fill(0); } - currentPanelsArray.forEach(panel => { + currentPanelsArray.forEach((panel) => { for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) { for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) { const row = grid[y]; @@ -108,16 +108,32 @@ interface IplacementDirection { fits: boolean; } +/** + * Compare grid data by an ending y coordinate. Grid data with a smaller ending y coordinate + * comes first. + * @param a + * @param b + */ +function comparePanels(a: GridData, b: GridData): number { + if (a.y + a.h < b.y + b.h) { + return -1; + } + if (a.y + a.h > b.y + b.h) { + return 1; + } + // a.y === b.y + if (a.x + a.w <= b.x + b.w) { + return -1; + } + return 1; +} + export function placePanelBeside({ width, height, currentPanels, placeBesideId, }: IPanelPlacementBesideArgs): Omit { - // const clonedPanels = _.cloneDeep(currentPanels); - if (!placeBesideId) { - throw new Error('Place beside method called without placeBesideId'); - } const panelToPlaceBeside = currentPanels[placeBesideId]; if (!panelToPlaceBeside) { throw new PanelNotFoundError(); @@ -130,10 +146,11 @@ export function placePanelBeside({ const possiblePlacementDirections: IplacementDirection[] = [ { grid: { x: beside.x + beside.w, y: beside.y, w: width, h: height }, fits: true }, // right - { grid: { x: beside.x - width, y: beside.y, w: width, h: height }, fits: true }, // left + { grid: { x: 0, y: beside.y + beside.h, w: width, h: height }, fits: true }, // left side of next row { grid: { x: beside.x, y: beside.y + beside.h, w: width, h: height }, fits: true }, // bottom ]; + // first, we check if there is place around the current panel for (const direction of possiblePlacementDirections) { if ( direction.grid.x >= 0 && @@ -156,13 +173,32 @@ export function placePanelBeside({ } } // if we get here that means there is no blank space around the panel we are placing beside. This means it's time to mess up the dashboard's groove. Fun! - const [, , bottomPlacement] = possiblePlacementDirections; - for (const currentPanelGrid of otherPanels) { - if (bottomPlacement.grid.y <= currentPanelGrid.y) { - const movedPanel = _.cloneDeep(currentPanels[currentPanelGrid.i]); - movedPanel.gridData.y = movedPanel.gridData.y + bottomPlacement.grid.h; - currentPanels[currentPanelGrid.i] = movedPanel; + /** + * 1. sort the panels in the grid + * 2. place the cloned panel to the bottom + * 3. reposition the panels after the cloned panel in the grid + */ + const grid = otherPanels.sort(comparePanels); + + let position = 0; + for (position; position < grid.length; position++) { + if (beside.i === grid[position].i) { + break; } } + const bottomPlacement = possiblePlacementDirections[2]; + // place to the bottom and move all other panels + let originalPositionInTheGrid = grid[position + 1].i; + const diff = + bottomPlacement.grid.y + + bottomPlacement.grid.h - + currentPanels[originalPositionInTheGrid].gridData.y; + + for (let j = position + 1; j < grid.length; j++) { + originalPositionInTheGrid = grid[j].i; + const movedPanel = _.cloneDeep(currentPanels[originalPositionInTheGrid]); + movedPanel.gridData.y = movedPanel.gridData.y + diff; + currentPanels[originalPositionInTheGrid] = movedPanel; + } return bottomPlacement.grid; } diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index b3fc462fd1c50..25e451dc7f793 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -87,6 +87,7 @@ function getProps( dashboardContainer = new DashboardContainer(input, options); const defaultTestProps: DashboardViewportProps = { container: dashboardContainer, + PanelComponent: () =>
, }; return { @@ -153,23 +154,17 @@ test('renders exit full screen button when in full screen mode', async () => { ); - expect( - (component - .find('.dshDashboardViewport') - .childAt(0) - .type() as any).name - ).toBe('ExitFullScreenButton'); + expect((component.find('.dshDashboardViewport').childAt(0).type() as any).name).toBe( + 'ExitFullScreenButton' + ); props.container.updateInput({ isFullScreenMode: false }); component.update(); await nextTick(); - expect( - (component - .find('.dshDashboardViewport') - .childAt(0) - .type() as any).name - ).not.toBe('ExitFullScreenButton'); + expect((component.find('.dshDashboardViewport').childAt(0).type() as any).name).not.toBe( + 'ExitFullScreenButton' + ); component.unmount(); }); @@ -186,28 +181,22 @@ test('renders exit full screen button when in full screen mode and empty screen' ); - expect( - (component - .find('.dshDashboardEmptyScreen') - .childAt(0) - .type() as any).name - ).toBe('ExitFullScreenButton'); + expect((component.find('.dshDashboardEmptyScreen').childAt(0).type() as any).name).toBe( + 'ExitFullScreenButton' + ); props.container.updateInput({ isFullScreenMode: false }); component.update(); await nextTick(); - expect( - (component - .find('.dshDashboardEmptyScreen') - .childAt(0) - .type() as any).name - ).not.toBe('ExitFullScreenButton'); + expect((component.find('.dshDashboardEmptyScreen').childAt(0).type() as any).name).not.toBe( + 'ExitFullScreenButton' + ); component.unmount(); }); -test('DashboardViewport unmount unsubscribes', async done => { +test('DashboardViewport unmount unsubscribes', async (done) => { const { props, options } = getProps(); const component = mount( diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index ae239bc27fdba..429837583b648 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { Subscription } from 'rxjs'; -import { PanelState } from '../../../embeddable_plugin'; +import { PanelState, EmbeddableStart } from '../../../embeddable_plugin'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; import { DashboardGrid } from '../grid'; import { context } from '../../../../../kibana_react/public'; @@ -27,6 +27,7 @@ import { context } from '../../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; renderEmpty?: () => React.ReactNode; + PanelComponent: EmbeddableStart['EmbeddablePanel']; } interface State { @@ -114,7 +115,7 @@ export class DashboardViewport extends React.Component )} - +
); } diff --git a/src/plugins/dashboard/public/application/legacy_app.js b/src/plugins/dashboard/public/application/legacy_app.js index 4e3cc15d93ece..2d99a2c6a2253 100644 --- a/src/plugins/dashboard/public/application/legacy_app.js +++ b/src/plugins/dashboard/public/application/legacy_app.js @@ -38,7 +38,7 @@ import { syncQueryStateWithUrl } from '../../../data/public'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); - app.directive('dashboardListing', function(reactDirective) { + app.directive('dashboardListing', function (reactDirective) { return reactDirective(DashboardListing, [ ['core', { watchDepth: 'reference' }], ['createItem', { watchDepth: 'reference' }], @@ -61,14 +61,14 @@ export function initDashboardApp(app, deps) { } app.factory('history', () => createHashHistory()); - app.factory('kbnUrlStateStorage', history => + app.factory('kbnUrlStateStorage', (history) => createKbnUrlStateStorage({ history, useHash: deps.uiSettings.get('state:storeInSessionStorage'), }) ); - app.config(function($routeProvider) { + app.config(function ($routeProvider) { const defaults = { reloadOnSearch: false, requireUICapability: 'dashboard.show', @@ -96,7 +96,7 @@ export function initDashboardApp(app, deps) { .when(DashboardConstants.LANDING_PAGE_PATH, { ...defaults, template: dashboardListingTemplate, - controller: function($scope, kbnUrlStateStorage, history) { + controller: function ($scope, kbnUrlStateStorage, history) { deps.core.chrome.docTitle.change( i18n.translate('dashboard.dashboardPageTitle', { defaultMessage: 'Dashboards' }) ); @@ -114,7 +114,7 @@ export function initDashboardApp(app, deps) { $scope.create = () => { history.push(DashboardConstants.CREATE_NEW_DASHBOARD_URL); }; - $scope.find = search => { + $scope.find = (search) => { return service.find(search, $scope.listingLimit); }; $scope.editItem = ({ id }) => { @@ -123,8 +123,8 @@ export function initDashboardApp(app, deps) { $scope.getViewUrl = ({ id }) => { return deps.addBasePath(`#${createDashboardEditUrl(id)}`); }; - $scope.delete = dashboards => { - return service.delete(dashboards.map(d => d.id)); + $scope.delete = (dashboards) => { + return service.delete(dashboards.map((d) => d.id)); }; $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); $scope.initialFilter = parse(history.location.search).filter || EMPTY_FILTER; @@ -143,7 +143,7 @@ export function initDashboardApp(app, deps) { }); }, resolve: { - dash: function($route, history) { + dash: function ($route, history) { return deps.data.indexPatterns.ensureDefaultIndexPattern(history).then(() => { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; @@ -154,10 +154,11 @@ export function initDashboardApp(app, deps) { search_fields: 'title', type: 'dashboard', }) - .then(results => { + .then((results) => { // The search isn't an exact match, lets see if we can find a single exact match to use const matchingDashboards = results.savedObjects.filter( - dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase() + (dashboard) => + dashboard.attributes.title.toLowerCase() === title.toLowerCase() ); if (matchingDashboards.length === 1) { history.replace(createDashboardEditUrl(matchingDashboards[0].id)); @@ -178,7 +179,7 @@ export function initDashboardApp(app, deps) { controller: createNewDashboardCtrl, requireUICapability: 'dashboard.createNew', resolve: { - dash: history => + dash: (history) => deps.data.indexPatterns .ensureDefaultIndexPattern(history) .then(() => deps.savedDashboards.get()) @@ -199,13 +200,13 @@ export function initDashboardApp(app, deps) { template: dashboardTemplate, controller: createNewDashboardCtrl, resolve: { - dash: function($route, history) { + dash: function ($route, history) { const id = $route.current.params.id; return deps.data.indexPatterns .ensureDefaultIndexPattern(history) .then(() => deps.savedDashboards.get(id)) - .then(savedDashboard => { + .then((savedDashboard) => { deps.chrome.recentlyAccessed.add( savedDashboard.getFullPath(), savedDashboard.title, @@ -213,7 +214,7 @@ export function initDashboardApp(app, deps) { ); return savedDashboard; }) - .catch(error => { + .catch((error) => { // Preserve BWC of v5.3.0 links for new, unsaved dashboards. // See https://github.com/elastic/kibana/issues/10951 for more context. if (error instanceof SavedObjectNotFound && id === 'create') { @@ -242,7 +243,7 @@ export function initDashboardApp(app, deps) { }) .otherwise({ template: '', - controller: function() { + controller: function () { deps.navigateToDefaultApp(); }, }); diff --git a/src/plugins/dashboard/public/application/lib/filter_utils.ts b/src/plugins/dashboard/public/application/lib/filter_utils.ts index 775bf90643a21..b6b935d6050ae 100644 --- a/src/plugins/dashboard/public/application/lib/filter_utils.ts +++ b/src/plugins/dashboard/public/application/lib/filter_utils.ts @@ -37,9 +37,7 @@ export class FilterUtils { */ public static convertTimeToUTCString(time?: string | Moment): undefined | string { if (moment(time).isValid()) { - return moment(time) - .utc() - .format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'); + return moment(time).utc().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'); } else { // If it's not a valid moment date, then it should be a string representing a relative time // like 'now' or 'now-15m'. @@ -66,7 +64,7 @@ export class FilterUtils { * @returns {Array.} */ public static cleanFiltersForComparison(filters: Filter[]) { - return _.map(filters, filter => { + return _.map(filters, (filter) => { const f: Partial = _.omit(filter, ['$$hashKey', '$state']); if (f.meta) { // f.meta.value is the value displayed in the filter bar. diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts index f4d97578adebf..6632eafc51f30 100644 --- a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts @@ -58,7 +58,7 @@ export function migrateAppState( | SavedDashboardPanel630 | SavedDashboardPanel640To720 | SavedDashboardPanel730ToLatest - >).some(panel => { + >).some((panel) => { if ((panel as { version?: string }).version === undefined) return true; const version = (panel as SavedDashboardPanel730ToLatest).version; diff --git a/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts index fc519327b41ee..e3b6725ce7449 100644 --- a/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts @@ -53,6 +53,6 @@ export function updateSavedDashboard( // save only unpinned filters const unpinnedFilters = savedDashboard .getFilters() - .filter(filter => !esFilters.isFilterPinned(filter)); + .filter((filter) => !esFilters.isFilterPinned(filter)); savedDashboard.searchSource.setField('filter', unpinnedFilters); } diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js index 0cdefff6a738e..dccac4e7c3c76 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js @@ -22,7 +22,7 @@ jest.mock( () => ({ ...require.requireActual('lodash'), // mock debounce to fire immediately with no internal timer - debounce: func => { + debounce: (func) => { function debounced(...args) { return func.apply(this, args); } @@ -37,7 +37,7 @@ import { shallow } from 'enzyme'; import { DashboardListing } from './dashboard_listing'; -const find = num => { +const find = (num) => { const hits = []; for (let i = 0; i < num; i++) { hits.push({ @@ -85,7 +85,7 @@ describe('after fetch', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -107,7 +107,7 @@ describe('after fetch', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -129,7 +129,7 @@ describe('after fetch', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -151,7 +151,7 @@ describe('after fetch', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -173,7 +173,7 @@ describe('after fetch', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index b4419adfe58da..0de3982039928 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -174,7 +174,7 @@ export class DashboardPlugin }, []); }; - const ExitFullScreenButton: React.FC = props => { + const ExitFullScreenButton: React.FC = (props) => { useHideChrome(); return ; }; @@ -228,7 +228,7 @@ export class DashboardPlugin const app: App = { id: DashboardConstants.DASHBOARDS_ID, title: 'Dashboard', - order: -1001, + order: 2500, euiIconType: 'dashboardApp', defaultPath: `#${DashboardConstants.LANDING_PAGE_PATH}`, updater$: this.appStateUpdater, @@ -290,7 +290,7 @@ export class DashboardPlugin kibanaLegacy.forwardApp( DashboardConstants.DASHBOARD_ID, DashboardConstants.DASHBOARDS_ID, - path => { + (path) => { const [, id, tail] = /dashboard\/?(.*?)($|\?.*)/.exec(path) || []; if (!id && !tail) { // unrecognized sub url @@ -307,7 +307,7 @@ export class DashboardPlugin kibanaLegacy.forwardApp( DashboardConstants.DASHBOARDS_ID, DashboardConstants.DASHBOARDS_ID, - path => { + (path) => { const [, tail] = /(\?.*)/.exec(path) || []; // carry over query if it exists return `#/list${tail || ''}`; diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.ts index 3e49b6636f562..3df9e64887725 100644 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.ts +++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.ts @@ -70,11 +70,11 @@ export function injectReferences( if (!Array.isArray(panels)) { return; } - panels.forEach(panel => { + panels.forEach((panel) => { if (!panel.panelRefName) { return; } - const reference = references.find(ref => ref.name === panel.panelRefName); + const reference = references.find((ref) => ref.name === panel.panelRefName); if (!reference) { // Throw an error since "panelRefName" means the reference exists within // "references" and in this scenario we have bad data. diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts index 08ba6a6ca921d..d6805b2d94119 100644 --- a/src/plugins/dashboard/public/url_generator.ts +++ b/src/plugins/dashboard/public/url_generator.ts @@ -83,7 +83,7 @@ export const createDashboardUrlGenerator = ( }> ): UrlGeneratorsDefinition => ({ id: DASHBOARD_APP_URL_GENERATOR, - createUrl: async state => { + createUrl: async (state) => { const startServices = await getStartServices(); const useHash = state.useHash ?? startServices.useHashedUrl; const appBasePath = startServices.appBasePath; @@ -103,7 +103,7 @@ export const createDashboardUrlGenerator = ( }; const cleanEmptyKeys = (stateObj: Record) => { - Object.keys(stateObj).forEach(key => { + Object.keys(stateObj).forEach((key) => { if (stateObj[key] === undefined) { delete stateObj[key]; } @@ -122,7 +122,7 @@ export const createDashboardUrlGenerator = ( STATE_STORAGE_KEY, cleanEmptyKeys({ query: state.query, - filters: filters?.filter(f => !esFilters.isFilterPinned(f)), + filters: filters?.filter((f) => !esFilters.isFilterPinned(f)), }), { useHash }, `${appBasePath}#/${hash}` @@ -132,7 +132,7 @@ export const createDashboardUrlGenerator = ( GLOBAL_STATE_STORAGE_KEY, cleanEmptyKeys({ time: state.timeRange, - filters: filters?.filter(f => esFilters.isFilterPinned(f)), + filters: filters?.filter((f) => esFilters.isFilterPinned(f)), refreshInterval: state.refreshInterval, }), { useHash }, diff --git a/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts index db2fbeb278802..75e169b79f320 100644 --- a/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts +++ b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts @@ -21,7 +21,7 @@ import { SavedObjectMigrationFn } from 'kibana/server'; import { get } from 'lodash'; import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common'; -export const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { +export const migrateMatchAllQuery: SavedObjectMigrationFn = (doc) => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); if (searchSourceJSON) { diff --git a/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts index 01a66445e4fc2..071568831d835 100644 --- a/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts +++ b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts @@ -61,7 +61,7 @@ export function moveFiltersToQuery( searchSource.filter = []; } - searchSource.filter.forEach(filter => { + searchSource.filter.forEach((filter) => { if (isQueryFilter(filter)) { searchSource730.query = { query: filter.query.query_string ? filter.query.query_string.query : '', diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 66a96e3e6e129..8ec72dc1f9a74 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -18,5 +18,33 @@ */ export const DEFAULT_QUERY_LANGUAGE = 'kuery'; -export const META_FIELDS_SETTING = 'metaFields'; -export const DOC_HIGHLIGHT_SETTING = 'doc_table:highlight'; + +export const UI_SETTINGS = { + META_FIELDS: 'metaFields', + DOC_HIGHLIGHT: 'doc_table:highlight', + QUERY_STRING_OPTIONS: 'query:queryString:options', + QUERY_ALLOW_LEADING_WILDCARDS: 'query:allowLeadingWildcards', + SEARCH_QUERY_LANGUAGE: 'search:queryLanguage', + SORT_OPTIONS: 'sort:options', + COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: 'courier:ignoreFilterIfFieldNotInIndex', + COURIER_SET_REQUEST_PREFERENCE: 'courier:setRequestPreference', + COURIER_CUSTOM_REQUEST_PREFERENCE: 'courier:customRequestPreference', + COURIER_MAX_CONCURRENT_SHARD_REQUESTS: 'courier:maxConcurrentShardRequests', + COURIER_BATCH_SEARCHES: 'courier:batchSearches', + SEARCH_INCLUDE_FROZEN: 'search:includeFrozen', + HISTOGRAM_BAR_TARGET: 'histogram:barTarget', + HISTOGRAM_MAX_BARS: 'histogram:maxBars', + HISTORY_LIMIT: 'history:limit', + SHORT_DOTS_ENABLE: 'shortDots:enable', + FORMAT_DEFAULT_TYPE_MAP: 'format:defaultTypeMap', + FORMAT_NUMBER_DEFAULT_PATTERN: 'format:number:defaultPattern', + FORMAT_PERCENT_DEFAULT_PATTERN: 'format:percent:defaultPattern', + FORMAT_BYTES_DEFAULT_PATTERN: 'format:bytes:defaultPattern', + FORMAT_CURRENCY_DEFAULT_PATTERN: 'format:currency:defaultPattern', + FORMAT_NUMBER_DEFAULT_LOCALE: 'format:number:defaultLocale', + TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: 'timepicker:refreshIntervalDefaults', + TIMEPICKER_QUICK_RANGES: 'timepicker:quickRanges', + INDEXPATTERN_PLACEHOLDER: 'indexPattern:placeholder', + FILTERS_PINNED_BY_DEFAULT: 'filters:pinnedByDefault', + FILTERS_EDITOR_SUGGEST_VALUES: 'filterEditor:suggestValues', +}; diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.ts b/src/plugins/data/common/es_query/es_query/build_es_query.ts index e4f5f1f9e216c..66d44f5e0747f 100644 --- a/src/plugins/data/common/es_query/es_query/build_es_query.ts +++ b/src/plugins/data/common/es_query/es_query/build_es_query.ts @@ -53,7 +53,7 @@ export function buildEsQuery( queries = Array.isArray(queries) ? queries : [queries]; filters = Array.isArray(filters) ? filters : [filters]; - const validQueries = queries.filter(query => has(query, 'query')); + const validQueries = queries.filter((query) => has(query, 'query')); const queriesByLanguage = groupBy(validQueries, 'language'); const kueryQuery = buildQueryFromKuery( indexPattern, diff --git a/src/plugins/data/common/es_query/es_query/from_filters.ts b/src/plugins/data/common/es_query/es_query/from_filters.ts index ed91d391fc1fd..d2d52efedd085 100644 --- a/src/plugins/data/common/es_query/es_query/from_filters.ts +++ b/src/plugins/data/common/es_query/es_query/from_filters.ts @@ -58,16 +58,18 @@ export const buildQueryFromFilters = ( indexPattern: IIndexPattern | undefined, ignoreFilterIfFieldNotInIndex: boolean = false ) => { - filters = filters.filter(filter => filter && !isFilterDisabled(filter)); + filters = filters.filter((filter) => filter && !isFilterDisabled(filter)); const filtersToESQueries = (negate: boolean) => { return filters .filter(filterNegate(negate)) - .filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern)) - .map(filter => { + .filter( + (filter) => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern) + ) + .map((filter) => { return migrateFilter(filter, indexPattern); }) - .map(filter => handleNestedFilter(filter, indexPattern)) + .map((filter) => handleNestedFilter(filter, indexPattern)) .map(translateToQuery) .map(cleanFilter); }; diff --git a/src/plugins/data/common/es_query/es_query/from_kuery.test.ts b/src/plugins/data/common/es_query/es_query/from_kuery.test.ts index 4574cd5ffd0cb..7ddd4dee743c5 100644 --- a/src/plugins/data/common/es_query/es_query/from_kuery.test.ts +++ b/src/plugins/data/common/es_query/es_query/from_kuery.test.ts @@ -46,7 +46,7 @@ describe('build query', () => { { query: 'machine.os:osx', language: 'kuery' }, ] as Query[]; - const expectedESQueries = queries.map(query => { + const expectedESQueries = queries.map((query) => { return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern); }); @@ -57,7 +57,7 @@ describe('build query', () => { test("should accept a specific date format for a kuery query into an ES query in the bool's filter clause", () => { const queries = [{ query: '@timestamp:"2018-04-03T19:04:17"', language: 'kuery' }] as Query[]; - const expectedESQueries = queries.map(query => { + const expectedESQueries = queries.map((query) => { return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern, { dateFormatTZ: 'America/Phoenix', }); @@ -72,7 +72,7 @@ describe('build query', () => { const queries = [ { query: '@timestamp:"2018-04-03T19:04:17Z"', language: 'kuery' }, ] as Query[]; - const expectedESQueries = queries.map(query => { + const expectedESQueries = queries.map((query) => { return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern); }); diff --git a/src/plugins/data/common/es_query/es_query/from_kuery.ts b/src/plugins/data/common/es_query/es_query/from_kuery.ts index f4ec0fe0b34c5..8af319704067b 100644 --- a/src/plugins/data/common/es_query/es_query/from_kuery.ts +++ b/src/plugins/data/common/es_query/es_query/from_kuery.ts @@ -27,7 +27,7 @@ export function buildQueryFromKuery( allowLeadingWildcards: boolean = false, dateFormatTZ?: string ) { - const queryASTs = queries.map(query => { + const queryASTs = queries.map((query) => { return fromKueryExpression(query.query, { allowLeadingWildcards }); }); diff --git a/src/plugins/data/common/es_query/es_query/from_lucene.test.ts b/src/plugins/data/common/es_query/es_query/from_lucene.test.ts index fc85404a5060c..53edeec37e0b3 100644 --- a/src/plugins/data/common/es_query/es_query/from_lucene.test.ts +++ b/src/plugins/data/common/es_query/es_query/from_lucene.test.ts @@ -41,7 +41,7 @@ describe('build query', () => { { query: 'foo:bar', language: 'lucene' }, { query: 'bar:baz', language: 'lucene' }, ] as unknown) as Query[]; - const expectedESQueries = queries.map(query => { + const expectedESQueries = queries.map((query) => { return decorateQuery(luceneStringToDsl(query.query), {}); }); @@ -64,7 +64,7 @@ describe('build query', () => { { query: 'bar:baz', language: 'lucene' }, ] as unknown) as Query[]; const dateFormatTZ = 'America/Phoenix'; - const expectedESQueries = queries.map(query => { + const expectedESQueries = queries.map((query) => { return decorateQuery(luceneStringToDsl(query.query), {}, dateFormatTZ); }); const result = buildQueryFromLucene(queries, {}, dateFormatTZ); diff --git a/src/plugins/data/common/es_query/es_query/from_lucene.ts b/src/plugins/data/common/es_query/es_query/from_lucene.ts index 8babb6df4fba5..18c2b24872e32 100644 --- a/src/plugins/data/common/es_query/es_query/from_lucene.ts +++ b/src/plugins/data/common/es_query/es_query/from_lucene.ts @@ -25,7 +25,7 @@ export function buildQueryFromLucene( queryStringOptions: Record, dateFormatTZ?: string ) { - const combinedQueries = (queries || []).map(query => { + const combinedQueries = (queries || []).map((query) => { const queryDsl = luceneStringToDsl(query.query); return decorateQuery(queryDsl, queryStringOptions, dateFormatTZ); diff --git a/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts index d146d81973d0d..5fa3c67dea400 100644 --- a/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts +++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts @@ -19,18 +19,19 @@ import { get } from 'lodash'; import { getEsQueryConfig } from './get_es_query_config'; import { IUiSettingsClient } from 'kibana/public'; +import { UI_SETTINGS } from '../../'; const config = ({ get(item: string) { return get(config, item); }, - 'query:allowLeadingWildcards': { + [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: { allowLeadingWildcards: true, }, - 'query:queryString:options': { + [UI_SETTINGS.QUERY_STRING_OPTIONS]: { queryStringOptions: {}, }, - 'courier:ignoreFilterIfFieldNotInIndex': { + [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: { ignoreFilterIfFieldNotInIndex: true, }, 'dateFormat:tz': { diff --git a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts index 0a82cf03bdb44..ff8fc5b11b26e 100644 --- a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts +++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts @@ -18,15 +18,18 @@ */ import { EsQueryConfig } from './build_es_query'; +import { UI_SETTINGS } from '../../'; interface KibanaConfig { get(key: string): T; } export function getEsQueryConfig(config: KibanaConfig) { - const allowLeadingWildcards = config.get('query:allowLeadingWildcards'); - const queryStringOptions = config.get('query:queryString:options'); - const ignoreFilterIfFieldNotInIndex = config.get('courier:ignoreFilterIfFieldNotInIndex'); + const allowLeadingWildcards = config.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS); + const queryStringOptions = config.get(UI_SETTINGS.QUERY_STRING_OPTIONS); + const ignoreFilterIfFieldNotInIndex = config.get( + UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX + ); const dateFormatTZ = config.get('dateFormat:tz'); return { diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts index 594b2641c39be..ad1ad34741d20 100644 --- a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts +++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts @@ -22,7 +22,7 @@ import { fields } from '../../index_patterns/mocks'; import { buildPhraseFilter, buildQueryFilter } from '../filters'; import { IFieldType, IIndexPattern } from '../../index_patterns'; -describe('handleNestedFilter', function() { +describe('handleNestedFilter', function () { const indexPattern: IIndexPattern = ({ id: 'logstash-*', fields, @@ -86,6 +86,6 @@ describe('handleNestedFilter', function() { }); function getField(name: string) { - return indexPattern.fields.find(field => field.name === name); + return indexPattern.fields.find((field) => field.name === name); } }); diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts index 27be7925fe00c..a0f4abb514b22 100644 --- a/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts +++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts @@ -28,7 +28,9 @@ export const handleNestedFilter = (filter: Filter, indexPattern?: IIndexPattern) return filter; } - const field = indexPattern.fields.find(indexPatternField => indexPatternField.name === fieldName); + const field = indexPattern.fields.find( + (indexPatternField) => indexPatternField.name === fieldName + ); if (!field || !field.subType || !field.subType.nested || !field.subType.nested.path) { return filter; } diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts index 698d7bb48e685..ae9d1c7921955 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts @@ -21,7 +21,7 @@ import { isEqual, clone } from 'lodash'; import { migrateFilter, DeprecatedMatchPhraseFilter } from './migrate_filter'; import { PhraseFilter, MatchAllFilter } from '../filters'; -describe('migrateFilter', function() { +describe('migrateFilter', function () { const oldMatchPhraseFilter = ({ query: { match: { @@ -45,13 +45,13 @@ describe('migrateFilter', function() { meta: {}, } as unknown) as PhraseFilter; - it('should migrate match filters of type phrase', function() { + it('should migrate match filters of type phrase', function () { const migratedFilter = migrateFilter(oldMatchPhraseFilter, undefined); expect(migratedFilter).toEqual(newMatchPhraseFilter); }); - it('should not modify the original filter', function() { + it('should not modify the original filter', function () { const oldMatchPhraseFilterCopy = clone(oldMatchPhraseFilter, true); migrateFilter(oldMatchPhraseFilter, undefined); @@ -59,7 +59,7 @@ describe('migrateFilter', function() { expect(isEqual(oldMatchPhraseFilter, oldMatchPhraseFilterCopy)).toBe(true); }); - it('should return the original filter if no migration is necessary', function() { + it('should return the original filter if no migration is necessary', function () { const originalFilter = { match_all: {}, } as MatchAllFilter; diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.ts index a0529d585879e..498763be538de 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.ts @@ -45,7 +45,7 @@ export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) { const params: Record = get(filter, ['query', 'match', fieldName]); let query = params.query; if (indexPattern) { - const field = indexPattern.fields.find(f => f.name === fieldName); + const field = indexPattern.fields.find((f) => f.name === fieldName); if (field) { query = getConvertedValueForField(field, params.query); diff --git a/src/plugins/data/common/es_query/filters/build_filter.test.ts b/src/plugins/data/common/es_query/filters/build_filter.test.ts index 22b44035d6ca8..73a4a4c788863 100644 --- a/src/plugins/data/common/es_query/filters/build_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/build_filter.test.ts @@ -18,7 +18,7 @@ */ import { buildFilter, FilterStateStore, FILTERS } from '.'; -import { stubIndexPattern, stubFields } from '../../../public/stubs'; +import { stubIndexPattern, stubFields } from '../../../common/stubs'; describe('buildFilter', () => { it('should build phrase filters', () => { diff --git a/src/plugins/data/common/es_query/filters/exists_filter.test.ts b/src/plugins/data/common/es_query/filters/exists_filter.test.ts index af52192dd85e4..065301986726d 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/exists_filter.test.ts @@ -21,14 +21,14 @@ import { buildExistsFilter, getExistsFilterField } from './exists_filter'; import { IIndexPattern } from '../../index_patterns'; import { fields } from '../../index_patterns/fields/fields.mocks.ts'; -describe('exists filter', function() { +describe('exists filter', function () { const indexPattern: IIndexPattern = ({ fields, } as unknown) as IIndexPattern; - describe('getExistsFilterField', function() { + describe('getExistsFilterField', function () { it('should return the name of the field an exists query is targeting', () => { - const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const field = indexPattern.fields.find((patternField) => patternField.name === 'extension'); const filter = buildExistsFilter(field!, indexPattern); const result = getExistsFilterField(filter); expect(result).toBe('extension'); diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts index 63c3a59044c1f..5d605c58a10d3 100644 --- a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts @@ -19,8 +19,8 @@ import { getGeoBoundingBoxFilterField } from './geo_bounding_box_filter'; -describe('geo_bounding_box filter', function() { - describe('getGeoBoundingBoxFilterField', function() { +describe('geo_bounding_box filter', function () { + describe('getGeoBoundingBoxFilterField', function () { it('should return the name of the field a geo_bounding_box query is targeting', () => { const filter = { geo_bounding_box: { diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts index 619903954ff55..e41e3f2978344 100644 --- a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts +++ b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts @@ -37,6 +37,6 @@ export const isGeoBoundingBoxFilter = (filter: any): filter is GeoBoundingBoxFil export const getGeoBoundingBoxFilterField = (filter: GeoBoundingBoxFilter) => { return ( filter.geo_bounding_box && - Object.keys(filter.geo_bounding_box).find(key => key !== 'ignore_unmapped') + Object.keys(filter.geo_bounding_box).find((key) => key !== 'ignore_unmapped') ); }; diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts b/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts index ba8e43b0cea85..f309e72e24e5c 100644 --- a/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts @@ -19,8 +19,8 @@ import { getGeoPolygonFilterField } from './geo_polygon_filter'; -describe('geo_polygon filter', function() { - describe('getGeoPolygonFilterField', function() { +describe('geo_polygon filter', function () { + describe('getGeoPolygonFilterField', function () { it('should return the name of the field a geo_polygon query is targeting', () => { const filter = { geo_polygon: { diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts b/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts index 03367feb83ee4..2b00f2e3cb99c 100644 --- a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts +++ b/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts @@ -35,6 +35,6 @@ export const isGeoPolygonFilter = (filter: any): filter is GeoPolygonFilter => export const getGeoPolygonFilterField = (filter: GeoPolygonFilter) => { return ( - filter.geo_polygon && Object.keys(filter.geo_polygon).find(key => key !== 'ignore_unmapped') + filter.geo_polygon && Object.keys(filter.geo_polygon).find((key) => key !== 'ignore_unmapped') ); }; diff --git a/src/plugins/data/common/es_query/filters/get_display_value.ts b/src/plugins/data/common/es_query/filters/get_display_value.ts index 03167f3080419..10b4dab3f46ef 100644 --- a/src/plugins/data/common/es_query/filters/get_display_value.ts +++ b/src/plugins/data/common/es_query/filters/get_display_value.ts @@ -25,6 +25,7 @@ import { Filter } from '../filters'; function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { if (!indexPattern || !key) return; + let format = get(indexPattern, ['fields', 'byName', key, 'format']); if (!format && (indexPattern.fields as any).getByName) { // TODO: Why is indexPatterns sometimes a map and sometimes an array? @@ -43,9 +44,8 @@ function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { } export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string { - const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); - if (typeof filter.meta.value === 'function') { + const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); const valueFormatter: any = getValueFormatter(indexPattern, filter.meta.key); return filter.meta.value(valueFormatter); } else { diff --git a/src/plugins/data/common/es_query/filters/get_filter_field.test.ts b/src/plugins/data/common/es_query/filters/get_filter_field.test.ts index 2fc8ffef9713b..4329a45f84ef9 100644 --- a/src/plugins/data/common/es_query/filters/get_filter_field.test.ts +++ b/src/plugins/data/common/es_query/filters/get_filter_field.test.ts @@ -23,14 +23,14 @@ import { getFilterField } from './get_filter_field'; import { IIndexPattern } from '../../index_patterns'; import { fields } from '../../index_patterns/fields/fields.mocks.ts'; -describe('getFilterField', function() { +describe('getFilterField', function () { const indexPattern: IIndexPattern = ({ id: 'logstash-*', fields, } as unknown) as IIndexPattern; it('should return the field name from known filter types that target a specific field', () => { - const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const field = indexPattern.fields.find((patternField) => patternField.name === 'extension'); const filter = buildPhraseFilter(field!, 'jpg', indexPattern); const result = getFilterField(filter); expect(result).toBe('extension'); diff --git a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts b/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts index 2f31fafcb74e4..672c0a6db4dd0 100644 --- a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { stubIndexPattern, phraseFilter } from 'src/plugins/data/public/stubs'; +import { stubIndexPattern, phraseFilter } from 'src/plugins/data/common/stubs'; import { getIndexPatternFromFilter } from './get_index_pattern_from_filter'; describe('getIndexPatternFromFilter', () => { diff --git a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.ts b/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.ts index 43d4bdaf03bc1..93b91757e1dac 100644 --- a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.ts +++ b/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.ts @@ -24,5 +24,5 @@ export function getIndexPatternFromFilter( filter: Filter, indexPatterns: IIndexPattern[] ): IIndexPattern | undefined { - return indexPatterns.find(indexPattern => indexPattern.id === filter.meta.index); + return indexPatterns.find((indexPattern) => indexPattern.id === filter.meta.index); } diff --git a/src/plugins/data/common/es_query/filters/missing_filter.test.ts b/src/plugins/data/common/es_query/filters/missing_filter.test.ts index 240d8fb26f3e0..28d2d3776d74c 100644 --- a/src/plugins/data/common/es_query/filters/missing_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/missing_filter.test.ts @@ -19,8 +19,8 @@ import { getMissingFilterField } from './missing_filter'; -describe('missing filter', function() { - describe('getMissingFilterField', function() { +describe('missing filter', function () { + describe('getMissingFilterField', function () { it('should return the name of the field an missing query is targeting', () => { const filter = { missing: { diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts index 9f90097e55475..a3948777e6d65 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts @@ -100,13 +100,13 @@ describe('buildInlineScriptForPhraseFilter', () => { }); }); -describe('getPhraseFilterField', function() { +describe('getPhraseFilterField', function () { const indexPattern: IIndexPattern = ({ fields, } as unknown) as IIndexPattern; it('should return the name of the field a phrase query is targeting', () => { - const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const field = indexPattern.fields.find((patternField) => patternField.name === 'extension'); const filter = buildPhraseFilter(field!, 'jpg', indexPattern); const result = getPhraseFilterField(filter); expect(result).toBe('extension'); diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.test.ts b/src/plugins/data/common/es_query/filters/phrases_filter.test.ts index 3a121eb9da034..7fbab263ac040 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/phrases_filter.test.ts @@ -21,14 +21,14 @@ import { buildPhrasesFilter, getPhrasesFilterField } from './phrases_filter'; import { IIndexPattern } from '../../index_patterns'; import { fields } from '../../index_patterns/fields/fields.mocks.ts'; -describe('phrases filter', function() { +describe('phrases filter', function () { const indexPattern: IIndexPattern = ({ fields, } as unknown) as IIndexPattern; - describe('getPhrasesFilterField', function() { + describe('getPhrasesFilterField', function () { it('should return the name of the field a phrases query is targeting', () => { - const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const field = indexPattern.fields.find((patternField) => patternField.name === 'extension'); const filter = buildPhrasesFilter(field!, ['jpg', 'png'], indexPattern); const result = getPhrasesFilterField(filter); expect(result).toBe('extension'); diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts index 45d59c97941b3..8accca5c29a45 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -173,13 +173,13 @@ describe('Range filter builder', () => { }); }); -describe('getRangeFilterField', function() { +describe('getRangeFilterField', function () { const indexPattern: IIndexPattern = ({ fields, } as unknown) as IIndexPattern; test('should return the name of the field a range query is targeting', () => { - const field = indexPattern.fields.find(patternField => patternField.name === 'bytes'); + const field = indexPattern.fields.find((patternField) => patternField.name === 'bytes'); const filter = buildRangeFilter(field!, {}, indexPattern); const result = getRangeFilterField(filter); expect(result).toBe('bytes'); diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts index b300539f4280a..c318a0f0c2c3d 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -112,7 +112,7 @@ export const buildRangeFilter = ( filter.meta.formattedValue = formattedValue; } - params = mapValues(params, value => (field.type === 'number' ? parseFloat(value) : value)); + params = mapValues(params, (value) => (field.type === 'number' ? parseFloat(value) : value)); if ('gte' in params && 'gt' in params) throw new Error('gte and gt are mutually exclusive'); if ('lte' in params && 'lt' in params) throw new Error('lte and lt are mutually exclusive'); diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.ts b/src/plugins/data/common/es_query/kuery/ast/ast.ts index 01ce77fa8f578..c6f44ebc437d3 100644 --- a/src/plugins/data/common/es_query/kuery/ast/ast.ts +++ b/src/plugins/data/common/es_query/kuery/ast/ast.ts @@ -24,7 +24,7 @@ import { IIndexPattern } from '../../../index_patterns/types'; // @ts-ignore import { parse as parseKuery } from './_generated_/kuery'; -import { JsonObject } from '../../../../../kibana_utils/public'; +import { JsonObject } from '../../../../../kibana_utils/common'; const fromExpression = ( expression: string | DslQuery, diff --git a/src/plugins/data/common/es_query/kuery/functions/and.test.ts b/src/plugins/data/common/es_query/kuery/functions/and.test.ts index 133e691b27dba..6f27a6dcb30fa 100644 --- a/src/plugins/data/common/es_query/kuery/functions/and.test.ts +++ b/src/plugins/data/common/es_query/kuery/functions/and.test.ts @@ -61,7 +61,7 @@ describe('kuery functions', () => { expect(Object.keys(result.bool).length).toBe(1); expect(result.bool.filter).toEqual( - [childNode1, childNode2].map(childNode => + [childNode1, childNode2].map((childNode) => ast.toElasticsearchQuery(childNode, indexPattern) ) ); diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts index f382e5668bb9d..753fd4e0a5060 100644 --- a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts +++ b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts @@ -24,7 +24,7 @@ import { LiteralTypeBuildNode } from '../node_types/types'; export function buildNodeParams(fieldName: string, points: LatLon[]) { const fieldNameArg = nodeTypes.literal.buildNode(fieldName); - const args = points.map(point => { + const args = points.map((point) => { const latLon = `${point.lat}, ${point.lon}`; return nodeTypes.literal.buildNode(latLon); }); diff --git a/src/plugins/data/common/es_query/kuery/functions/or.test.ts b/src/plugins/data/common/es_query/kuery/functions/or.test.ts index a6590546e5fc5..5ef8610362ca9 100644 --- a/src/plugins/data/common/es_query/kuery/functions/or.test.ts +++ b/src/plugins/data/common/es_query/kuery/functions/or.test.ts @@ -60,7 +60,7 @@ describe('kuery functions', () => { expect(Object.keys(result).length).toBe(1); expect(result.bool).toHaveProperty('should'); expect(result.bool.should).toEqual( - [childNode1, childNode2].map(childNode => + [childNode1, childNode2].map((childNode) => ast.toElasticsearchQuery(childNode, indexPattern) ) ); diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts index 0e314ec778af8..cdd050b2647c1 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts +++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts @@ -26,13 +26,13 @@ export function getFields(node: KueryNode, indexPattern?: IIndexPattern) { if (!indexPattern) return []; if (node.type === 'literal') { const fieldName = literal.toElasticsearchQuery(node as LiteralTypeBuildNode); - const field = indexPattern.fields.find(fld => fld.name === fieldName); + const field = indexPattern.fields.find((fld) => fld.name === fieldName); if (!field) { return []; } return [field]; } else if (node.type === 'wildcard') { - const fields = indexPattern.fields.filter(fld => wildcard.test(node, fld.name)); + const fields = indexPattern.fields.filter((fld) => wildcard.test(node, fld.name)); return fields; } } diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts index 200c5b51e4d27..abfdb2c363821 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts +++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts @@ -24,7 +24,7 @@ import { IIndexPattern } from '../../../../index_patterns'; // @ts-ignore import { getFullFieldNameNode } from './get_full_field_name_node'; -describe('getFullFieldNameNode', function() { +describe('getFullFieldNameNode', function () { let indexPattern: IIndexPattern; beforeEach(() => { diff --git a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts index 4ada139a10a0f..6aac1a3b3486d 100644 --- a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts +++ b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts @@ -55,7 +55,7 @@ export class KQLSyntaxError extends Error { constructor(error: KQLSyntaxErrorData, expression: any) { let message = error.message; if (error.expected) { - const translatedExpectations = error.expected.map(expected => { + const translatedExpectations = error.expected.map((expected) => { return grammarRuleTranslations[expected.description] || expected.description; }); diff --git a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts index 398cb1a164415..df1fdd1e0d514 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts @@ -21,7 +21,7 @@ import _ from 'lodash'; import * as ast from '../ast'; import { nodeTypes } from '../node_types'; import { NamedArgTypeBuildNode } from './types'; -import { JsonObject } from '../../../../../kibana_utils/public'; +import { JsonObject } from '../../../../../kibana_utils/common'; export function buildNode(name: string, value: any): NamedArgTypeBuildNode { const argumentNode = diff --git a/src/plugins/data/common/es_query/kuery/node_types/types.ts b/src/plugins/data/common/es_query/kuery/node_types/types.ts index 937b5c6e7ef9c..6d3019e75d483 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/types.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/types.ts @@ -22,7 +22,7 @@ */ import { IIndexPattern } from '../../../index_patterns'; -import { JsonValue } from '../../../../../kibana_utils/public'; +import { JsonValue } from '../../../../../kibana_utils/common'; import { KueryNode } from '..'; export type FunctionName = diff --git a/src/plugins/data/common/es_query/kuery/node_types/wildcard.ts b/src/plugins/data/common/es_query/kuery/node_types/wildcard.ts index 87fcfa1ca2f64..160212c58f0fb 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/wildcard.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/wildcard.ts @@ -46,10 +46,7 @@ export function buildNode(value: string): WildcardTypeBuildNode | KueryNode { export function test(node: any, str: string): boolean { const { value } = node; - const regex = value - .split(wildcardSymbol) - .map(escapeRegExp) - .join('[\\s\\S]*'); + const regex = value.split(wildcardSymbol).map(escapeRegExp).join('[\\s\\S]*'); const regexp = new RegExp(`^${regex}$`); return regexp.test(str); } @@ -61,10 +58,7 @@ export function toElasticsearchQuery(node: any): string { export function toQueryStringQuery(node: any): string { const { value } = node; - return value - .split(wildcardSymbol) - .map(escapeQueryString) - .join('*'); + return value.split(wildcardSymbol).map(escapeQueryString).join('*'); } export function hasLeadingWildcard(node: any): boolean { diff --git a/src/plugins/data/common/field_formats/content_types/text_content_type.ts b/src/plugins/data/common/field_formats/content_types/text_content_type.ts index dc450086edc62..4a90ba6c0b203 100644 --- a/src/plugins/data/common/field_formats/content_types/text_content_type.ts +++ b/src/plugins/data/common/field_formats/content_types/text_content_type.ts @@ -27,7 +27,7 @@ export const setup = ( format: IFieldFormat, convert: TextContextTypeConvert = asPrettyString ): TextContextTypeConvert => { - const recurse: TextContextTypeConvert = value => { + const recurse: TextContextTypeConvert = (value) => { if (!value || !isFunction(value.map)) { return convert.call(format, value); } diff --git a/src/plugins/data/common/field_formats/converters/boolean.test.ts b/src/plugins/data/common/field_formats/converters/boolean.test.ts index 3650df6517611..cef7936aaaf13 100644 --- a/src/plugins/data/common/field_formats/converters/boolean.test.ts +++ b/src/plugins/data/common/field_formats/converters/boolean.test.ts @@ -63,7 +63,7 @@ describe('Boolean Format', () => { input: ' True ', // should handle trailing and mixed case expected: 'true', }, - ].forEach(data => { + ].forEach((data) => { test(`convert ${data.input} to boolean`, () => { expect(boolean.convert(data.input)).toBe(data.expected); }); diff --git a/src/plugins/data/common/field_formats/converters/boolean.ts b/src/plugins/data/common/field_formats/converters/boolean.ts index a824f9e73fb1e..f8304a396f83c 100644 --- a/src/plugins/data/common/field_formats/converters/boolean.ts +++ b/src/plugins/data/common/field_formats/converters/boolean.ts @@ -30,7 +30,7 @@ export class BoolFormat extends FieldFormat { }); static fieldType = [KBN_FIELD_TYPES.BOOLEAN, KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING]; - textConvert: TextContextTypeConvert = value => { + textConvert: TextContextTypeConvert = (value) => { if (typeof value === 'string') { value = value.trim().toLowerCase(); } diff --git a/src/plugins/data/common/field_formats/converters/bytes.test.ts b/src/plugins/data/common/field_formats/converters/bytes.test.ts index 8dad9fc206e72..e0c26170c2907 100644 --- a/src/plugins/data/common/field_formats/converters/bytes.test.ts +++ b/src/plugins/data/common/field_formats/converters/bytes.test.ts @@ -18,11 +18,12 @@ */ import { BytesFormat } from './bytes'; +import { UI_SETTINGS } from '../../constants'; describe('BytesFormat', () => { const config: Record = {}; - config['format:bytes:defaultPattern'] = '0,0.[000]b'; + config[UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN] = '0,0.[000]b'; const getConfig = (key: string) => config[key]; diff --git a/src/plugins/data/common/field_formats/converters/color.ts b/src/plugins/data/common/field_formats/converters/color.ts index babf3dbd8c451..ca659916f7671 100644 --- a/src/plugins/data/common/field_formats/converters/color.ts +++ b/src/plugins/data/common/field_formats/converters/color.ts @@ -60,7 +60,7 @@ export class ColorFormat extends FieldFormat { } } - htmlConvert: HtmlContextTypeConvert = val => { + htmlConvert: HtmlContextTypeConvert = (val) => { const color = this.findColorRuleForVal(val) as typeof DEFAULT_CONVERTER_COLOR; if (!color) return escape(asPrettyString(val)); diff --git a/src/plugins/data/common/field_formats/converters/date_nanos.test.ts b/src/plugins/data/common/field_formats/converters/date_nanos.test.ts index ea755eb5d5bdf..267f023e9b69d 100644 --- a/src/plugins/data/common/field_formats/converters/date_nanos.test.ts +++ b/src/plugins/data/common/field_formats/converters/date_nanos.test.ts @@ -67,7 +67,7 @@ describe('Date Nanos Format', () => { pattern: 'SSSSSSSSS', expected: '201900001', }, - ].forEach(fixture => { + ].forEach((fixture) => { const fracPattern = analysePatternForFract(fixture.pattern); const momentDate = moment(fixture.input).utc(); const value = formatWithNanos(momentDate, fixture.input, fracPattern); diff --git a/src/plugins/data/common/field_formats/converters/date_nanos.ts b/src/plugins/data/common/field_formats/converters/date_nanos.ts index 93f3a62842749..3fa2b1c276cd7 100644 --- a/src/plugins/data/common/field_formats/converters/date_nanos.ts +++ b/src/plugins/data/common/field_formats/converters/date_nanos.ts @@ -88,7 +88,7 @@ export class DateNanosFormat extends FieldFormat { }; } - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { // don't give away our ref to converter so // we can hot-swap when config changes const pattern = this.param('pattern'); diff --git a/src/plugins/data/common/field_formats/converters/duration.ts b/src/plugins/data/common/field_formats/converters/duration.ts index 3b3b6f87f36f9..53c2aba98120e 100644 --- a/src/plugins/data/common/field_formats/converters/duration.ts +++ b/src/plugins/data/common/field_formats/converters/duration.ts @@ -186,7 +186,7 @@ export class DurationFormat extends FieldFormat { }; } - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { const inputFormat = this.param('inputFormat'); const outputFormat = this.param('outputFormat') as keyof Duration; const outputPrecision = this.param('outputPrecision'); diff --git a/src/plugins/data/common/field_formats/converters/ip.ts b/src/plugins/data/common/field_formats/converters/ip.ts index 8e65d9c12d6c8..da86cdbd6b051 100644 --- a/src/plugins/data/common/field_formats/converters/ip.ts +++ b/src/plugins/data/common/field_formats/converters/ip.ts @@ -29,7 +29,7 @@ export class IpFormat extends FieldFormat { }); static fieldType = KBN_FIELD_TYPES.IP; - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { if (val === undefined || val === null) return '-'; if (!isFinite(val)) return val; diff --git a/src/plugins/data/common/field_formats/converters/number.test.ts b/src/plugins/data/common/field_formats/converters/number.test.ts index fe36d5b12e873..31c5ea41bf5af 100644 --- a/src/plugins/data/common/field_formats/converters/number.test.ts +++ b/src/plugins/data/common/field_formats/converters/number.test.ts @@ -18,11 +18,12 @@ */ import { NumberFormat } from './number'; +import { UI_SETTINGS } from '../../constants'; describe('NumberFormat', () => { const config: Record = {}; - config['format:number:defaultPattern'] = '0,0.[000]'; + config[UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN] = '0,0.[000]'; const getConfig = (key: string) => config[key]; diff --git a/src/plugins/data/common/field_formats/converters/numeral.ts b/src/plugins/data/common/field_formats/converters/numeral.ts index d8e46a480294f..1d844bca3f89a 100644 --- a/src/plugins/data/common/field_formats/converters/numeral.ts +++ b/src/plugins/data/common/field_formats/converters/numeral.ts @@ -24,6 +24,7 @@ import numeralLanguages from '@elastic/numeral/languages'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert } from '../types'; +import { UI_SETTINGS } from '../../constants'; const numeralInst = numeral(); @@ -51,7 +52,8 @@ export abstract class NumeralFormat extends FieldFormat { if (isNaN(val)) return ''; const previousLocale = numeral.language(); - const defaultLocale = (this.getConfig && this.getConfig('format:number:defaultLocale')) || 'en'; + const defaultLocale = + (this.getConfig && this.getConfig(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE)) || 'en'; numeral.language(defaultLocale); const formatted = numeralInst.set(val).format(this.param('pattern')); @@ -61,7 +63,7 @@ export abstract class NumeralFormat extends FieldFormat { return formatted; } - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { return this.getConvertedValue(val); }; } diff --git a/src/plugins/data/common/field_formats/converters/percent.test.ts b/src/plugins/data/common/field_formats/converters/percent.test.ts index 8b26564814af3..754234bdeb78b 100644 --- a/src/plugins/data/common/field_formats/converters/percent.test.ts +++ b/src/plugins/data/common/field_formats/converters/percent.test.ts @@ -18,11 +18,12 @@ */ import { PercentFormat } from './percent'; +import { UI_SETTINGS } from '../../constants'; describe('PercentFormat', () => { const config: Record = {}; - config['format:percent:defaultPattern'] = '0,0.[000]%'; + config[UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN] = '0,0.[000]%'; const getConfig = (key: string) => config[key]; diff --git a/src/plugins/data/common/field_formats/converters/percent.ts b/src/plugins/data/common/field_formats/converters/percent.ts index 8cbc2d36d0ef1..ecf9c7d19108d 100644 --- a/src/plugins/data/common/field_formats/converters/percent.ts +++ b/src/plugins/data/common/field_formats/converters/percent.ts @@ -20,6 +20,7 @@ import { i18n } from '@kbn/i18n'; import { NumeralFormat } from './numeral'; import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; +import { UI_SETTINGS } from '../../constants'; export class PercentFormat extends NumeralFormat { static id = FIELD_FORMAT_IDS.PERCENT; @@ -32,11 +33,11 @@ export class PercentFormat extends NumeralFormat { allowsNumericalAggregations = true; getParamDefaults = () => ({ - pattern: this.getConfig!('format:percent:defaultPattern'), + pattern: this.getConfig!(UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN), fractional: true, }); - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { const formatted = super.getConvertedValue(val); if (this.param('fractional')) { diff --git a/src/plugins/data/common/field_formats/converters/relative_date.ts b/src/plugins/data/common/field_formats/converters/relative_date.ts index 7c7f20ee9c411..e3bdf25da9076 100644 --- a/src/plugins/data/common/field_formats/converters/relative_date.ts +++ b/src/plugins/data/common/field_formats/converters/relative_date.ts @@ -30,7 +30,7 @@ export class RelativeDateFormat extends FieldFormat { }); static fieldType = KBN_FIELD_TYPES.DATE; - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { if (val === null || val === undefined) { return '-'; } diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts index 7f13d5526cc15..f00261e00971a 100644 --- a/src/plugins/data/common/field_formats/converters/source.ts +++ b/src/plugins/data/common/field_formats/converters/source.ts @@ -22,6 +22,7 @@ import { shortenDottedString } from '../../utils'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; +import { UI_SETTINGS } from '../../'; /** * Remove all of the whitespace between html tags @@ -56,7 +57,7 @@ export class SourceFormat extends FieldFormat { static title = '_source'; static fieldType = KBN_FIELD_TYPES._SOURCE; - textConvert: TextContextTypeConvert = value => JSON.stringify(value); + textConvert: TextContextTypeConvert = (value) => JSON.stringify(value); htmlConvert: HtmlContextTypeConvert = (value, options = {}) => { const { field, hit } = options; @@ -71,9 +72,9 @@ export class SourceFormat extends FieldFormat { const formatted = field.indexPattern.formatHit(hit); const highlightPairs: any[] = []; const sourcePairs: any[] = []; - const isShortDots = this.getConfig!('shortDots:enable'); + const isShortDots = this.getConfig!(UI_SETTINGS.SHORT_DOTS_ENABLE); - keys(formatted).forEach(key => { + keys(formatted).forEach((key) => { const pairs = highlights[key] ? highlightPairs : sourcePairs; const newField = isShortDots ? shortenDottedString(key) : key; const val = formatted[key]; diff --git a/src/plugins/data/common/field_formats/converters/static_lookup.ts b/src/plugins/data/common/field_formats/converters/static_lookup.ts index c835afd3db5ec..394bea5be4f25 100644 --- a/src/plugins/data/common/field_formats/converters/static_lookup.ts +++ b/src/plugins/data/common/field_formats/converters/static_lookup.ts @@ -51,7 +51,7 @@ export class StaticLookupFormat extends FieldFormat { }; } - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { const lookupEntries = this.param('lookupEntries'); const unknownKeyValue = this.param('unknownKeyValue'); diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts index 22df9f1f88bfb..a543a46f965cc 100644 --- a/src/plugins/data/common/field_formats/converters/string.ts +++ b/src/plugins/data/common/field_formats/converters/string.ts @@ -105,12 +105,12 @@ export class StringFormat extends FieldFormat { } private toTitleCase(val: string) { - return val.replace(/\w\S*/g, txt => { + return val.replace(/\w\S*/g, (txt) => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); } - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { switch (this.param('transform')) { case 'lower': return String(val).toLowerCase(); diff --git a/src/plugins/data/common/field_formats/converters/truncate.ts b/src/plugins/data/common/field_formats/converters/truncate.ts index 70b2a3dff18f2..a6c4a1133a2ed 100644 --- a/src/plugins/data/common/field_formats/converters/truncate.ts +++ b/src/plugins/data/common/field_formats/converters/truncate.ts @@ -32,7 +32,7 @@ export class TruncateFormat extends FieldFormat { }); static fieldType = KBN_FIELD_TYPES.STRING; - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { const length = this.param('fieldLength'); if (length > 0) { return trunc(val, { diff --git a/src/plugins/data/common/field_formats/converters/url.ts b/src/plugins/data/common/field_formats/converters/url.ts index 60d7efdba34ff..a0a498b6cab34 100644 --- a/src/plugins/data/common/field_formats/converters/url.ts +++ b/src/plugins/data/common/field_formats/converters/url.ts @@ -111,7 +111,7 @@ export class UrlFormat extends FieldFormat { // trim all the odd bits, the variable names const parts = template.split(templateMatchRE).map((part, i) => (i % 2 ? part.trim() : part)); - return function(locals: Record): string { + return function (locals: Record): string { // replace all the odd bits with their local var let output = ''; let i = -1; @@ -139,7 +139,7 @@ export class UrlFormat extends FieldFormat { return `${imageLabel}`; } - textConvert: TextContextTypeConvert = value => this.formatLabel(value); + textConvert: TextContextTypeConvert = (value) => this.formatLabel(value); htmlConvert: HtmlContextTypeConvert = (rawValue, options = {}) => { const { field, hit } = options; @@ -161,7 +161,7 @@ export class UrlFormat extends FieldFormat { return this.generateImgHtml(url, imageLabel); default: - const inWhitelist = whitelistUrlSchemes.some(scheme => url.indexOf(scheme) === 0); + const inWhitelist = whitelistUrlSchemes.some((scheme) => url.indexOf(scheme) === 0); if (!inWhitelist && !parsedUrl) { return url; } diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts index 2eb9a3e593d1a..9325485bce75d 100644 --- a/src/plugins/data/common/field_formats/field_formats_registry.ts +++ b/src/plugins/data/common/field_formats/field_formats_registry.ts @@ -33,6 +33,7 @@ import { baseFormatters } from './constants/base_formatters'; import { FieldFormat } from './field_format'; import { SerializedFieldFormat } from '../../../expressions/common/types'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../types'; +import { UI_SETTINGS } from '../'; export class FieldFormatsRegistry { protected fieldFormats: Map = new Map(); @@ -49,7 +50,7 @@ export class FieldFormatsRegistry { metaParamsOptions: Record = {}, defaultFieldConverters: FieldFormatInstanceType[] = baseFormatters ) { - const defaultTypeMap = getConfig('format:defaultTypeMap'); + const defaultTypeMap = getConfig(UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP); this.register(defaultFieldConverters); this.parseDefaultTypeMap(defaultTypeMap); this.getConfig = getConfig; @@ -129,7 +130,7 @@ export class FieldFormatsRegistry { return undefined; } - return esTypes.find(type => this.defaultMap[type] && this.defaultMap[type].es); + return esTypes.find((type) => this.defaultMap[type] && this.defaultMap[type].es); }; /** @@ -231,7 +232,7 @@ export class FieldFormatsRegistry { parseDefaultTypeMap(value: any) { this.defaultMap = value; - forOwn(this, fn => { + forOwn(this, (fn) => { if (isFunction(fn) && fn.cache) { // clear all memoize caches // @ts-ignore @@ -241,7 +242,7 @@ export class FieldFormatsRegistry { } register(fieldFormats: FieldFormatInstanceType[]) { - fieldFormats.forEach(fieldFormat => this.fieldFormats.set(fieldFormat.id, fieldFormat)); + fieldFormats.forEach((fieldFormat) => this.fieldFormats.set(fieldFormat.id, fieldFormat)); } /** diff --git a/src/plugins/data/common/field_formats/utils/highlight/highlight_html.test.ts b/src/plugins/data/common/field_formats/utils/highlight/highlight_html.test.ts index 6ada630851c1d..8df25a2f34269 100644 --- a/src/plugins/data/common/field_formats/utils/highlight/highlight_html.test.ts +++ b/src/plugins/data/common/field_formats/utils/highlight/highlight_html.test.ts @@ -21,7 +21,7 @@ import { highlightTags } from './highlight_tags'; import { htmlTags } from './html_tags'; import { getHighlightHtml } from './highlight_html'; -describe('getHighlightHtml', function() { +describe('getHighlightHtml', function () { const text = '' + 'Bacon ipsum dolor amet pork loin pork cow pig beef chuck ground round shankle sirloin landjaeger kevin ' + @@ -29,20 +29,20 @@ describe('getHighlightHtml', function() { 'sirloin, t-bone ham shoulder jerky turducken bresaola. Chicken cow beef picanha. Picanha hamburger alcatra ' + 'cupim. Salami capicola boudin pork belly shank picanha.'; - test('should not modify text if highlight is empty', function() { + test('should not modify text if highlight is empty', function () { expect(getHighlightHtml(text, undefined)).toBe(text); expect(getHighlightHtml(text, null)).toBe(text); expect(getHighlightHtml(text, [])).toBe(text); }); - test('should preserve escaped text', function() { + test('should preserve escaped text', function () { const highlights = ['']; const result = getHighlightHtml('<foo>', highlights); expect(result.indexOf('')).toBe(-1); expect(result.indexOf('<foo>')).toBeGreaterThan(-1); }); - test('should highlight a single result', function() { + test('should highlight a single result', function () { const highlights = [ highlightTags.pre + 'hamburger' + @@ -56,7 +56,7 @@ describe('getHighlightHtml', function() { ); }); - test('should highlight multiple results', function() { + test('should highlight multiple results', function () { const highlights = [ 'kevin venison sausage ribeye tongue. ' + highlightTags.pre + @@ -76,7 +76,7 @@ describe('getHighlightHtml', function() { ); }); - test('should highlight multiple hits in a result', function() { + test('should highlight multiple hits in a result', function () { const highlights = [ 'Bacon ipsum dolor amet ' + highlightTags.pre + @@ -114,7 +114,7 @@ describe('getHighlightHtml', function() { ); }); - test('should accept an object and return a string containing its properties', function() { + test('should accept an object and return a string containing its properties', function () { const obj = { foo: 1, bar: 2 }; const result = getHighlightHtml(obj, null); expect(result.indexOf('' + obj)).toBe(-1); diff --git a/src/plugins/data/common/field_formats/utils/highlight/highlight_html.ts b/src/plugins/data/common/field_formats/utils/highlight/highlight_html.ts index c7c092941728e..23c72e94b120a 100644 --- a/src/plugins/data/common/field_formats/utils/highlight/highlight_html.ts +++ b/src/plugins/data/common/field_formats/utils/highlight/highlight_html.ts @@ -24,7 +24,7 @@ import { htmlTags } from './html_tags'; export function getHighlightHtml(fieldValue: any, highlights: any) { let highlightHtml = typeof fieldValue === 'object' ? JSON.stringify(fieldValue) : fieldValue; - _.each(highlights, function(highlight) { + _.each(highlights, function (highlight) { const escapedHighlight = _.escape(highlight); // Strip out the highlight tags to compare against the field text diff --git a/src/plugins/data/public/index_patterns/field.stub.ts b/src/plugins/data/common/index_patterns/field.stub.ts similarity index 96% rename from src/plugins/data/public/index_patterns/field.stub.ts rename to src/plugins/data/common/index_patterns/field.stub.ts index 2e94f4b45f400..cbb3d2fa2ce68 100644 --- a/src/plugins/data/public/index_patterns/field.stub.ts +++ b/src/plugins/data/common/index_patterns/field.stub.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IFieldType } from '../../../../plugins/data/public'; +import { IFieldType } from '.'; export const stubFields: IFieldType[] = [ { diff --git a/src/plugins/data/common/index_patterns/fields/fields.mocks.ts.ts b/src/plugins/data/common/index_patterns/fields/fields.mocks.ts.ts index c27ff42b1e9d2..ed778d0422054 100644 --- a/src/plugins/data/common/index_patterns/fields/fields.mocks.ts.ts +++ b/src/plugins/data/common/index_patterns/fields/fields.mocks.ts.ts @@ -318,4 +318,4 @@ export const fields: IFieldType[] = [ }, ]; -export const getField = (name: string) => fields.find(field => field.name === name) as IFieldType; +export const getField = (name: string) => fields.find((field) => field.name === name) as IFieldType; diff --git a/src/plugins/data/public/index_patterns/index_pattern.stub.ts b/src/plugins/data/common/index_patterns/index_pattern.stub.ts similarity index 96% rename from src/plugins/data/public/index_patterns/index_pattern.stub.ts rename to src/plugins/data/common/index_patterns/index_pattern.stub.ts index 4f8108575aa15..e7384e09494aa 100644 --- a/src/plugins/data/public/index_patterns/index_pattern.stub.ts +++ b/src/plugins/data/common/index_patterns/index_pattern.stub.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IIndexPattern } from '../../common'; +import { IIndexPattern } from '.'; import { stubFields } from './field.stub'; export const stubIndexPattern: IIndexPattern = { diff --git a/src/plugins/data/common/index_patterns/utils.test.ts b/src/plugins/data/common/index_patterns/utils.test.ts index e2707d469a317..43d6fadb336ff 100644 --- a/src/plugins/data/common/index_patterns/utils.test.ts +++ b/src/plugins/data/common/index_patterns/utils.test.ts @@ -30,20 +30,20 @@ const mockField = { describe('isFilterable', () => { describe('types', () => { it('should return true for filterable types', () => { - ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => { + ['string', 'number', 'date', 'ip', 'boolean'].forEach((type) => { expect(isFilterable({ ...mockField, type })).toBe(true); }); }); it('should return false for filterable types if the field is not searchable', () => { - ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => { + ['string', 'number', 'date', 'ip', 'boolean'].forEach((type) => { expect(isFilterable({ ...mockField, type, searchable: false })).toBe(false); }); }); it('should return false for un-filterable types', () => { ['geo_point', 'geo_shape', 'attachment', 'murmur3', '_source', 'unknown', 'conflict'].forEach( - type => { + (type) => { expect(isFilterable({ ...mockField, type })).toBe(false); } ); diff --git a/src/plugins/data/common/kbn_field_types/kbn_field_types.ts b/src/plugins/data/common/kbn_field_types/kbn_field_types.ts index 91d3b7404e5b0..ce05dc796bbab 100644 --- a/src/plugins/data/common/kbn_field_types/kbn_field_types.ts +++ b/src/plugins/data/common/kbn_field_types/kbn_field_types.ts @@ -31,7 +31,7 @@ const registeredKbnTypes = createKbnFieldTypes(); * @return {KbnFieldType} */ export const getKbnFieldType = (typeName: string): KbnFieldType | undefined => - registeredKbnTypes.find(t => t.name === typeName); + registeredKbnTypes.find((t) => t.name === typeName); /** * Get the esTypes known by all kbnFieldTypes @@ -39,7 +39,7 @@ export const getKbnFieldType = (typeName: string): KbnFieldType | undefined => * @return {Array} */ export const getKbnTypeNames = (): string[] => - registeredKbnTypes.filter(type => type.name).map(type => type.name); + registeredKbnTypes.filter((type) => type.name).map((type) => type.name); /** * Get the KbnFieldType name for an esType string @@ -48,7 +48,7 @@ export const getKbnTypeNames = (): string[] => * @return {string} */ export const castEsToKbnFieldTypeName = (esType: ES_FIELD_TYPES | string): KBN_FIELD_TYPES => { - const type = registeredKbnTypes.find(t => t.esTypes.includes(esType as ES_FIELD_TYPES)); + const type = registeredKbnTypes.find((t) => t.esTypes.includes(esType as ES_FIELD_TYPES)); return type && type.name ? (type.name as KBN_FIELD_TYPES) : KBN_FIELD_TYPES.UNKNOWN; }; @@ -59,4 +59,4 @@ export const castEsToKbnFieldTypeName = (esType: ES_FIELD_TYPES | string): KBN_F * @return {Array} */ export const getFilterableKbnTypeNames = (): string[] => - registeredKbnTypes.filter(type => type.filterable).map(type => type.name); + registeredKbnTypes.filter((type) => type.filterable).map((type) => type.name); diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts index 0c3947ade8221..1e5391332e6b0 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts @@ -228,5 +228,21 @@ describe('filter manager utilities', () => { expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeFalsy(); }); + + test('should compare index with index true', () => { + const f1 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + const f2 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + + f2.meta.index = 'wassup'; + f2.meta.index = 'dog'; + + expect(compareFilters([f1], [f2], { index: true })).toBeFalsy(); + }); }); }); diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.ts b/src/plugins/data/common/query/filter_manager/compare_filters.ts index 3be52a9a60977..65df6e26a25b3 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.ts @@ -21,6 +21,7 @@ import { defaults, isEqual, omit, map } from 'lodash'; import { FilterMeta, Filter } from '../../es_query'; export interface FilterCompareOptions { + index?: boolean; disabled?: boolean; negate?: boolean; state?: boolean; @@ -31,6 +32,7 @@ export interface FilterCompareOptions { * Include disabled, negate and store when comparing filters */ export const COMPARE_ALL_OPTIONS: FilterCompareOptions = { + index: true, disabled: true, negate: true, state: true, @@ -44,6 +46,7 @@ const mapFilter = ( ) => { const cleaned: FilterMeta = omit(filter, excludedAttributes); + if (comparators.index) cleaned.index = filter.meta?.index; if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); if (comparators.alias) cleaned.alias = filter.meta?.alias; @@ -81,6 +84,7 @@ export const compareFilters = ( const excludedAttributes: string[] = ['$$hashKey', 'meta']; comparators = defaults(comparatorOptions || {}, { + index: false, state: false, negate: false, disabled: false, diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.ts b/src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.ts index 9df17b4c24a98..fe8ea6e346ba7 100644 --- a/src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.ts +++ b/src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.ts @@ -69,7 +69,7 @@ export function leastCommonInterval(a: string, b: string): string { } // Otherwise find the biggest non-calendar unit that divides evenly - const lcmUnit = unitsDesc.find(unit => { + const lcmUnit = unitsDesc.find((unit) => { const unitInfo = unitsMap[unit]; return !!(unitInfo.type !== 'calendar' && lcmMs % unitInfo.base === 0); }); diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.test.ts b/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.test.ts index c18ff4c7acead..b2e51faada2ac 100644 --- a/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.test.ts +++ b/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.test.ts @@ -45,7 +45,7 @@ describe('parseEsInterval', () => { const intervals = ['4w', '12M', '10y']; expect.assertions(intervals.length); - intervals.forEach(interval => { + intervals.forEach((interval) => { try { parseEsInterval(interval); } catch (error) { @@ -58,7 +58,7 @@ describe('parseEsInterval', () => { const intervals = ['1', 'h', '0m', '0.5h']; expect.assertions(intervals.length); - intervals.forEach(interval => { + intervals.forEach((interval) => { try { parseEsInterval(interval); } catch (error) { diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.ts b/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.ts index f53da19ef476f..7121b776b800e 100644 --- a/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.ts +++ b/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.ts @@ -48,9 +48,7 @@ export type ParsedInterval = ReturnType; * */ export function parseEsInterval(interval: string) { - const matches = String(interval) - .trim() - .match(ES_INTERVAL_STRING_REGEX); + const matches = String(interval).trim().match(ES_INTERVAL_STRING_REGEX); if (!matches) { throw new InvalidEsIntervalFormatError(interval); diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts b/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts index 857c8594720ee..e9d708d7061d2 100644 --- a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts +++ b/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts @@ -25,9 +25,7 @@ import dateMath from '@elastic/datemath'; const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + dateMath.units.join('|') + ')$'); export function parseInterval(interval: string): moment.Duration | null { - const matches = String(interval) - .trim() - .match(INTERVAL_STRING_RE); + const matches = String(interval).trim().match(INTERVAL_STRING_RE); if (!matches) return null; @@ -46,7 +44,7 @@ export function parseInterval(interval: string): moment.Duration | null { // a duration corresponding to 0.5 hours, we return a duration corresponding to 12 hours. const selectedUnit = find( dateMath.units, - u => Math.abs(duration.as(u)) >= 1 + (u) => Math.abs(duration.as(u)) >= 1 ) as unitOfTime.Base; // however if we do this fhe other way around it will also fail diff --git a/src/plugins/data/common/stubs.ts b/src/plugins/data/common/stubs.ts new file mode 100644 index 0000000000000..aea2b71eec46b --- /dev/null +++ b/src/plugins/data/common/stubs.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { stubIndexPattern, stubIndexPatternWithFields } from './index_patterns/index_pattern.stub'; +export { stubFields } from './index_patterns/field.stub'; +export * from './es_query/filters/stubs'; diff --git a/src/plugins/data/common/utils/abort_utils.test.ts b/src/plugins/data/common/utils/abort_utils.test.ts index d2a25f2c2dd52..cd78d95b2ab00 100644 --- a/src/plugins/data/common/utils/abort_utils.test.ts +++ b/src/plugins/data/common/utils/abort_utils.test.ts @@ -21,7 +21,7 @@ import { AbortError, toPromise, getCombinedSignal } from './abort_utils'; jest.useFakeTimers(); -const flushPromises = () => new Promise(resolve => setImmediate(resolve)); +const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); describe('AbortUtils', () => { describe('AbortError', () => { diff --git a/src/plugins/data/common/utils/abort_utils.ts b/src/plugins/data/common/utils/abort_utils.ts index 9aec787170840..aef9802cecce9 100644 --- a/src/plugins/data/common/utils/abort_utils.ts +++ b/src/plugins/data/common/utils/abort_utils.ts @@ -61,7 +61,7 @@ export function toPromise(signal: AbortSignal, shouldReject: boolean = false) { * @param signals */ export function getCombinedSignal(signals: AbortSignal[]) { - const promises = signals.map(signal => toPromise(signal)); + const promises = signals.map((signal) => toPromise(signal)); const controller = new AbortController(); Promise.race(promises).then(() => controller.abort()); return controller.signal; diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts new file mode 100644 index 0000000000000..09cb2cb2afeca --- /dev/null +++ b/src/plugins/data/config.ts @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + autocomplete: schema.object({ + querySuggestions: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), + valueSuggestions: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), + }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/data/public/actions/apply_filter_action.ts b/src/plugins/data/public/actions/apply_filter_action.ts index ebaac6b745bec..7e8ed5ec8fb22 100644 --- a/src/plugins/data/public/actions/apply_filter_action.ts +++ b/src/plugins/data/public/actions/apply_filter_action.ts @@ -62,12 +62,12 @@ export function createFilterAction( if (selectedFilters.length > 1) { const indexPatterns = await Promise.all( - filters.map(filter => { + filters.map((filter) => { return getIndexPatterns().get(filter.meta.index!); }) ); - const filterSelectionPromise: Promise = new Promise(resolve => { + const filterSelectionPromise: Promise = new Promise((resolve) => { const overlay = getOverlays().openModal( toMountPoint( applyFiltersPopover( diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts index 2b426813a98a4..2fdd746535519 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts @@ -42,16 +42,16 @@ const getOtherBucketFilterTerms = ( } // get only rows where cell value matches current row for all the fields before columnIndex - const rows = table.rows.filter(row => { + const rows = table.rows.filter((row) => { return table.columns.every((column, i) => { return row[column.id] === table.rows[rowIndex][column.id] || i >= columnIndex; }); }); - const terms: any[] = rows.map(row => row[table.columns[columnIndex].id]); + const terms: any[] = rows.map((row) => row[table.columns[columnIndex].id]); return [ ...new Set( - terms.filter(term => { + terms.filter((term) => { const notOther = term !== '__other__'; const notMissing = term !== '__missing__'; return notOther && notMissing; @@ -119,12 +119,12 @@ export const createFiltersFromValueClickAction = async ({ await Promise.all( data - .filter(point => point) - .map(async val => { + .filter((point) => point) + .map(async (val) => { const { table, column, row } = val; const filter: Filter[] = (await createFilter(table, column, row)) || []; if (filter) { - filter.forEach(f => { + filter.forEach((f) => { if (negate) { f = esFilters.toggleFilterNegated(f); } diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts index 17c1b1b1e1769..5d4f1f5f1d6db 100644 --- a/src/plugins/data/public/actions/value_click_action.ts +++ b/src/plugins/data/public/actions/value_click_action.ts @@ -68,12 +68,12 @@ export function valueClickAction( if (filters.length > 1) { const indexPatterns = await Promise.all( - filters.map(filter => { + filters.map((filter) => { return getIndexPatterns().get(filter.meta.index!); }) ); - const filterSelectionPromise: Promise = new Promise(resolve => { + const filterSelectionPromise: Promise = new Promise((resolve) => { const overlay = getOverlays().openModal( toMountPoint( applyFiltersPopover( diff --git a/src/plugins/data/public/autocomplete/autocomplete_service.ts b/src/plugins/data/public/autocomplete/autocomplete_service.ts index bc557f31f7466..2136a405baad6 100644 --- a/src/plugins/data/public/autocomplete/autocomplete_service.ts +++ b/src/plugins/data/public/autocomplete/autocomplete_service.ts @@ -17,24 +17,35 @@ * under the License. */ -import { CoreSetup } from 'src/core/public'; +import { CoreSetup, PluginInitializerContext } from 'src/core/public'; import { QuerySuggestionGetFn } from './providers/query_suggestion_provider'; import { + getEmptyValueSuggestions, setupValueSuggestionProvider, ValueSuggestionsGetFn, } from './providers/value_suggestion_provider'; +import { ConfigSchema } from '../../config'; + export class AutocompleteService { + autocompleteConfig: ConfigSchema['autocomplete']; + + constructor(initializerContext: PluginInitializerContext) { + const { autocomplete } = initializerContext.config.get(); + + this.autocompleteConfig = autocomplete; + } + private readonly querySuggestionProviders: Map = new Map(); private getValueSuggestions?: ValueSuggestionsGetFn; private addQuerySuggestionProvider = (language: string, provider: QuerySuggestionGetFn): void => { - if (language && provider) { + if (language && provider && this.autocompleteConfig.querySuggestions.enabled) { this.querySuggestionProviders.set(language, provider); } }; - private getQuerySuggestions: QuerySuggestionGetFn = args => { + private getQuerySuggestions: QuerySuggestionGetFn = (args) => { const { language } = args; const provider = this.querySuggestionProviders.get(language); @@ -47,7 +58,9 @@ export class AutocompleteService { /** @public **/ public setup(core: CoreSetup) { - this.getValueSuggestions = setupValueSuggestionProvider(core); + this.getValueSuggestions = this.autocompleteConfig.valueSuggestions.enabled + ? setupValueSuggestionProvider(core) + : getEmptyValueSuggestions; return { addQuerySuggestionProvider: this.addQuerySuggestionProvider, diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index 5df88000edbd5..a6a45a26f06b3 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -19,7 +19,7 @@ import { memoize } from 'lodash'; import { CoreSetup } from 'src/core/public'; -import { IIndexPattern, IFieldType } from '../../../common'; +import { IIndexPattern, IFieldType, UI_SETTINGS } from '../../../common'; function resolver(title: string, field: IFieldType, query: string, boolFilter: any) { // Only cache results for a minute @@ -38,6 +38,8 @@ interface ValueSuggestionsGetFnArgs { signal?: AbortSignal; } +export const getEmptyValueSuggestions = (() => Promise.resolve([])) as ValueSuggestionsGetFn; + export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsGetFn => { const requestSuggestions = memoize( (index: string, field: IFieldType, query: string, boolFilter: any = [], signal?: AbortSignal) => @@ -56,7 +58,9 @@ export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsG boolFilter, signal, }: ValueSuggestionsGetFnArgs): Promise => { - const shouldSuggestValues = core!.uiSettings.get('filterEditor:suggestValues'); + const shouldSuggestValues = core!.uiSettings.get( + UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES + ); const { title } = indexPattern; if (field.type === 'boolean') { diff --git a/src/plugins/data/public/field_formats/converters/date.ts b/src/plugins/data/public/field_formats/converters/date.ts index 3e1efdc69dec8..78ef8b293e8b9 100644 --- a/src/plugins/data/public/field_formats/converters/date.ts +++ b/src/plugins/data/public/field_formats/converters/date.ts @@ -45,7 +45,7 @@ export class DateFormat extends FieldFormat { }; } - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { // don't give away our ref to converter so // we can hot-swap when config changes const pattern = this.param('pattern'); diff --git a/src/plugins/data/public/field_formats/field_formats_service.ts b/src/plugins/data/public/field_formats/field_formats_service.ts index 22c7e90c06130..3ddc8d0b68a5b 100644 --- a/src/plugins/data/public/field_formats/field_formats_service.ts +++ b/src/plugins/data/public/field_formats/field_formats_service.ts @@ -18,7 +18,7 @@ */ import { CoreSetup } from 'src/core/public'; -import { FieldFormatsRegistry } from '../../common'; +import { FieldFormatsRegistry, UI_SETTINGS } from '../../common'; import { deserializeFieldFormat } from './utils/deserialize'; import { FormatFactory } from '../../common/field_formats/utils'; import { baseFormattersPublic } from './constants'; @@ -28,7 +28,7 @@ export class FieldFormatsService { public setup(core: CoreSetup) { core.uiSettings.getUpdate$().subscribe(({ key, newValue }) => { - if (key === 'format:defaultTypeMap') { + if (key === UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP) { this.fieldFormatsRegistry.parseDefaultTypeMap(newValue); } }); diff --git a/src/plugins/data/public/field_formats/utils/deserialize.ts b/src/plugins/data/public/field_formats/utils/deserialize.ts index 840e023a11589..d9c713c8b1eb4 100644 --- a/src/plugins/data/public/field_formats/utils/deserialize.ts +++ b/src/plugins/data/public/field_formats/utils/deserialize.ts @@ -60,7 +60,7 @@ const getFieldFormat = ( return new DefaultFieldFormat(); }; -export const deserializeFieldFormat: FormatFactory = function( +export const deserializeFieldFormat: FormatFactory = function ( this: DataPublicPluginStart['fieldFormats'], mapping?: SerializedFieldFormat ) { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 4a5b3fd5714db..5540039323756 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -17,9 +17,8 @@ * under the License. */ -import './index.scss'; - import { PluginInitializerContext } from '../../../core/public'; +import { ConfigSchema } from '../config'; /* * Filters: @@ -266,6 +265,7 @@ export { ES_FIELD_TYPES, KBN_FIELD_TYPES, IndexPatternAttributes, + UI_SETTINGS, } from '../common'; /* @@ -450,7 +450,7 @@ export { import { DataPublicPlugin } from './plugin'; -export function plugin(initializerContext: PluginInitializerContext) { +export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); } diff --git a/src/plugins/data/public/index_patterns/fields/__snapshots__/field.test.ts.snap b/src/plugins/data/public/index_patterns/fields/__snapshots__/field.test.ts.snap new file mode 100644 index 0000000000000..4593349a408a7 --- /dev/null +++ b/src/plugins/data/public/index_patterns/fields/__snapshots__/field.test.ts.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Field exports the property to JSON 1`] = ` +Object { + "aggregatable": true, + "conflictDescriptions": Object { + "a": Array [ + "b", + "c", + ], + "d": Array [ + "e", + ], + }, + "count": 1, + "esTypes": Array [ + "type", + ], + "lang": "lang", + "name": "name", + "readFromDocValues": false, + "script": "script", + "scripted": true, + "searchable": true, + "subType": Object { + "multi": Object { + "parent": "parent", + }, + "nested": Object { + "path": "path", + }, + }, + "type": "type", +} +`; diff --git a/src/plugins/data/public/index_patterns/fields/field.test.ts b/src/plugins/data/public/index_patterns/fields/field.test.ts new file mode 100644 index 0000000000000..18252b159d98d --- /dev/null +++ b/src/plugins/data/public/index_patterns/fields/field.test.ts @@ -0,0 +1,223 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Field } from './field'; +import { IndexPattern } from '..'; +import { notificationServiceMock } from '../../../../../core/public/mocks'; +import { FieldFormatsStart } from '../../field_formats'; +import { KBN_FIELD_TYPES } from '../../../common'; + +describe('Field', function () { + function flatten(obj: Record) { + return JSON.parse(JSON.stringify(obj)); + } + + function getField(values = {}) { + return new Field( + fieldValues.indexPattern as IndexPattern, + { ...fieldValues, ...values }, + false, + { + fieldFormats: {} as FieldFormatsStart, + toastNotifications: notificationServiceMock.createStartContract().toasts, + } + ); + } + + const fieldValues = { + name: 'name', + type: 'type', + script: 'script', + lang: 'lang', + count: 1, + esTypes: ['type'], + aggregatable: true, + filterable: true, + searchable: true, + sortable: true, + readFromDocValues: false, + visualizable: true, + scripted: true, + subType: { multi: { parent: 'parent' }, nested: { path: 'path' } }, + displayName: 'displayName', + indexPattern: ({ + fieldFormatMap: { name: {}, _source: {}, _score: {}, _id: {} }, + } as unknown) as IndexPattern, + format: { name: 'formatName' }, + $$spec: {}, + conflictDescriptions: { a: ['b', 'c'], d: ['e'] }, + } as Field; + + it('the correct properties are writable', () => { + const field = getField(); + + expect(field.count).toEqual(1); + field.count = 2; + expect(field.count).toEqual(2); + + expect(field.script).toEqual(fieldValues.script); + field.script = '1'; + expect(field.script).toEqual('1'); + + expect(field.lang).toEqual(fieldValues.lang); + field.lang = 'painless'; + expect(field.lang).toEqual('painless'); + + expect(field.conflictDescriptions).toEqual(fieldValues.conflictDescriptions); + field.conflictDescriptions = {}; + expect(field.conflictDescriptions).toEqual({}); + }); + + it('the correct properties are not writable', () => { + const field = getField(); + + expect(field.name).toEqual(fieldValues.name); + field.name = 'newName'; + expect(field.name).toEqual(fieldValues.name); + + expect(field.type).toEqual(fieldValues.type); + field.type = 'newType'; + expect(field.type).toEqual(fieldValues.type); + + expect(field.esTypes).toEqual(fieldValues.esTypes); + field.esTypes = ['newType']; + expect(field.esTypes).toEqual(fieldValues.esTypes); + + expect(field.scripted).toEqual(fieldValues.scripted); + field.scripted = false; + expect(field.scripted).toEqual(fieldValues.scripted); + + expect(field.searchable).toEqual(fieldValues.searchable); + field.searchable = false; + expect(field.searchable).toEqual(fieldValues.searchable); + + expect(field.aggregatable).toEqual(fieldValues.aggregatable); + field.aggregatable = false; + expect(field.aggregatable).toEqual(fieldValues.aggregatable); + + expect(field.readFromDocValues).toEqual(fieldValues.readFromDocValues); + field.readFromDocValues = true; + expect(field.readFromDocValues).toEqual(fieldValues.readFromDocValues); + + expect(field.subType).toEqual(fieldValues.subType); + field.subType = {}; + expect(field.subType).toEqual(fieldValues.subType); + + // not writable, not serialized + expect(() => { + field.indexPattern = {} as IndexPattern; + }).toThrow(); + + // computed fields + expect(() => { + field.format = { name: 'newFormatName' }; + }).toThrow(); + + expect(() => { + field.sortable = false; + }).toThrow(); + + expect(() => { + field.filterable = false; + }).toThrow(); + + expect(() => { + field.visualizable = false; + }).toThrow(); + + expect(() => { + field.displayName = 'newDisplayName'; + }).toThrow(); + + expect(() => { + field.$$spec = { a: 'b' }; + }).toThrow(); + }); + + it('sets type field when _source field', () => { + const field = getField({ name: '_source' }); + expect(field.type).toEqual('_source'); + }); + + it('calculates searchable', () => { + const field = getField({ searchable: true, scripted: false }); + expect(field.searchable).toEqual(true); + + const fieldB = getField({ searchable: false, scripted: true }); + expect(fieldB.searchable).toEqual(true); + + const fieldC = getField({ searchable: false, scripted: false }); + expect(fieldC.searchable).toEqual(false); + }); + + it('calculates aggregatable', () => { + const field = getField({ aggregatable: true, scripted: false }); + expect(field.aggregatable).toEqual(true); + + const fieldB = getField({ aggregatable: false, scripted: true }); + expect(fieldB.aggregatable).toEqual(true); + + const fieldC = getField({ aggregatable: false, scripted: false }); + expect(fieldC.aggregatable).toEqual(false); + }); + + it('calculates readFromDocValues', () => { + const field = getField({ readFromDocValues: true, scripted: false }); + expect(field.readFromDocValues).toEqual(true); + + const fieldB = getField({ readFromDocValues: false, scripted: false }); + expect(fieldB.readFromDocValues).toEqual(false); + + const fieldC = getField({ readFromDocValues: true, scripted: true }); + expect(fieldC.readFromDocValues).toEqual(false); + }); + + it('calculates sortable', () => { + const field = getField({ name: '_score' }); + expect(field.sortable).toEqual(true); + + const fieldB = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); + expect(fieldB.sortable).toEqual(true); + + const fieldC = getField({ indexed: false }); + expect(fieldC.sortable).toEqual(false); + }); + + it('calculates filterable', () => { + const field = getField({ name: '_id' }); + expect(field.filterable).toEqual(true); + + const fieldB = getField({ scripted: true }); + expect(fieldB.filterable).toEqual(true); + + const fieldC = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); + expect(fieldC.filterable).toEqual(true); + + const fieldD = getField({ scripted: false, indexed: false }); + expect(fieldD.filterable).toEqual(false); + }); + + it('exports the property to JSON', () => { + const field = new Field({ fieldFormatMap: { name: {} } } as IndexPattern, fieldValues, false, { + fieldFormats: {} as FieldFormatsStart, + toastNotifications: notificationServiceMock.createStartContract().toasts, + }); + expect(flatten(field)).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index 12db09bbb846f..625df17d62e0d 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -56,6 +56,7 @@ export class Field implements IFieldType { subType?: IFieldSubType; displayName?: string; indexPattern?: IndexPattern; + readFromDocValues?: boolean; format: any; $$spec: FieldSpec; conflictDescriptions?: Record; diff --git a/src/plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts index 0631e00a1fb62..1aef0b1ccadaa 100644 --- a/src/plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -68,7 +68,7 @@ export const getIndexPatternFieldListCreator = ({ this.indexPattern = indexPattern; this.shortDotsEnable = shortDotsEnable; - specs.map(field => this.add(field)); + specs.map((field) => this.add(field)); } getByName = (name: Field['name']) => this.byName.get(name); @@ -96,7 +96,7 @@ export const getIndexPatternFieldListCreator = ({ fieldFormats, toastNotifications, }); - const index = this.findIndex(f => f.name === newField.name); + const index = this.findIndex((f) => f.name === newField.name); this.splice(index, 1, newField); this.setByName(newField); this.removeByGroup(newField); diff --git a/src/plugins/data/public/index_patterns/fields/obj_define.js b/src/plugins/data/public/index_patterns/fields/obj_define.js index bb32f93d6011a..9c9e5c8f3d55f 100644 --- a/src/plugins/data/public/index_patterns/fields/obj_define.js +++ b/src/plugins/data/public/index_patterns/fields/obj_define.js @@ -27,7 +27,7 @@ export function ObjDefine(defaults, prototype) { this.prototype = prototype || Object.prototype; } -ObjDefine.REDEFINE_SUPPORTED = (function() { +ObjDefine.REDEFINE_SUPPORTED = (function () { const a = Object.create(Object.prototype, { prop: { configurable: true, @@ -49,7 +49,7 @@ ObjDefine.REDEFINE_SUPPORTED = (function() { * @param {any} v - value * @return {object} - property descriptor */ -ObjDefine.prototype.writ = function(name, val) { +ObjDefine.prototype.writ = function (name, val) { this._define(name, val, true, true); }; @@ -59,7 +59,7 @@ ObjDefine.prototype.writ = function(name, val) { * @param {any} v - value * @return {object} - property descriptor */ -ObjDefine.prototype.fact = function(name, val) { +ObjDefine.prototype.fact = function (name, val) { this._define(name, val, true); }; @@ -69,7 +69,7 @@ ObjDefine.prototype.fact = function(name, val) { * @param {any} v - value * @return {object} - property descriptor */ -ObjDefine.prototype.comp = function(name, val) { +ObjDefine.prototype.comp = function (name, val) { this._define(name, val); }; @@ -84,7 +84,7 @@ ObjDefine.prototype.comp = function(name, val) { * * @return {object} - created object */ -ObjDefine.prototype.create = function() { +ObjDefine.prototype.create = function () { const self = this; self.obj = Object.create(this.prototype, self.descs); @@ -94,10 +94,10 @@ ObjDefine.prototype.create = function() { // to include or trim manually. This is currently only in use in PhantomJS // due to https://github.com/ariya/phantomjs/issues/11856 // TODO: remove this: https://github.com/elastic/kibana/issues/27136 - self.obj.toJSON = function() { + self.obj.toJSON = function () { return _.transform( self.obj, - function(json, val, key) { + function (json, val, key) { const desc = self.descs[key]; if (desc && desc.enumerable && val == null) return; json[key] = val; @@ -114,12 +114,12 @@ ObjDefine.prototype.create = function() { * Private APIS */ -ObjDefine.prototype._define = function(name, val, exported, changeable) { +ObjDefine.prototype._define = function (name, val, exported, changeable) { val = val != null ? val : this.defaults[name]; this.descs[name] = this._describe(name, val, !!exported, !!changeable); }; -ObjDefine.prototype._describe = function(name, val, exported, changeable) { +ObjDefine.prototype._describe = function (name, val, exported, changeable) { const self = this; const exists = val != null; @@ -128,7 +128,7 @@ ObjDefine.prototype._describe = function(name, val, exported, changeable) { enumerable: exists, configurable: true, get: _.constant(val), - set: function(update) { + set: function (update) { if (!changeable) return false; // change the descriptor, since the value now exists. diff --git a/src/plugins/data/public/index_patterns/fields/obj_define.test.js b/src/plugins/data/public/index_patterns/fields/obj_define.test.js index 7bcd97ec06813..ec9a022253621 100644 --- a/src/plugins/data/public/index_patterns/fields/obj_define.test.js +++ b/src/plugins/data/public/index_patterns/fields/obj_define.test.js @@ -20,13 +20,13 @@ import expect from '@kbn/expect'; import { ObjDefine } from './obj_define'; -describe('ObjDefine Utility', function() { +describe('ObjDefine Utility', function () { function flatten(obj) { return JSON.parse(JSON.stringify(obj)); } - describe('#writ', function() { - it('creates writeable properties', function() { + describe('#writ', function () { + it('creates writeable properties', function () { const def = new ObjDefine(); def.writ('name', 'foo'); @@ -37,13 +37,13 @@ describe('ObjDefine Utility', function() { expect(obj).to.have.property('name', 'bar'); }); - it('exports the property to JSON', function() { + it('exports the property to JSON', function () { const def = new ObjDefine(); def.writ('name', 'foo'); expect(flatten(def.create())).to.have.property('name', 'foo'); }); - it("does not export property to JSON it it's undefined or null", function() { + it("does not export property to JSON it it's undefined or null", function () { const def = new ObjDefine(); def.writ('name'); expect(flatten(def.create())).to.not.have.property('name'); @@ -52,7 +52,7 @@ describe('ObjDefine Utility', function() { expect(flatten(def.create())).to.not.have.property('name'); }); - it('switched to exporting if a value is written', function() { + it('switched to exporting if a value is written', function () { const def = new ObjDefine(); def.writ('name'); @@ -66,7 +66,7 @@ describe('ObjDefine Utility', function() { expect(flatten(obj)).to.have.property('name', 'foo'); }); - it('setting a writ value to null prevents it from exporting', function() { + it('setting a writ value to null prevents it from exporting', function () { const def = new ObjDefine(); def.writ('name', 'foo'); @@ -78,8 +78,8 @@ describe('ObjDefine Utility', function() { }); }); - describe('#fact', function() { - it('creates an immutable field', function() { + describe('#fact', function () { + it('creates an immutable field', function () { const def = new ObjDefine(); const val = 'foo'; const notval = 'bar'; @@ -90,37 +90,37 @@ describe('ObjDefine Utility', function() { expect(obj).to.have.property('name', val); }); - it('exports the fact to JSON', function() { + it('exports the fact to JSON', function () { const def = new ObjDefine(); def.fact('name', 'foo'); expect(flatten(def.create())).to.have.property('name', 'foo'); }); }); - describe('#comp', function() { - it('creates an immutable field', function() { + describe('#comp', function () { + it('creates an immutable field', function () { const def = new ObjDefine(); const val = 'foo'; const notval = 'bar'; def.comp('name', val); const obj = def.create(); - expect(function() { + expect(function () { 'use strict'; // eslint-disable-line strict obj.name = notval; }).to.throwException(); }); - it('does not export the computed value to JSON', function() { + it('does not export the computed value to JSON', function () { const def = new ObjDefine(); def.comp('name', 'foo'); expect(flatten(def.create())).to.not.have.property('name'); }); }); - describe('#create', function() { - it('creates object that inherits from the prototype', function() { + describe('#create', function () { + it('creates object that inherits from the prototype', function () { function SomeClass() {} const def = new ObjDefine(null, SomeClass.prototype); @@ -129,7 +129,7 @@ describe('ObjDefine Utility', function() { expect(obj).to.be.a(SomeClass); }); - it('uses the defaults for property values', function() { + it('uses the defaults for property values', function () { const def = new ObjDefine({ name: 'bar' }); def.fact('name'); @@ -138,7 +138,7 @@ describe('ObjDefine Utility', function() { expect(obj).to.have.property('name', 'bar'); }); - it('ignores default values that are not defined properties', function() { + it('ignores default values that are not defined properties', function () { const def = new ObjDefine({ name: 'foo', name2: 'bar' }); const obj = def.create(); diff --git a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx index 9115e523f5302..2088bd8c925df 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx +++ b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx @@ -91,9 +91,9 @@ export const createEnsureDefaultIndexPattern = (core: CoreStart) => { if (redirectTarget === '/home') { core.application.navigateToApp('home'); } else { - window.location.href = core.http.basePath.prepend( - `/app/kibana#/management/kibana/indexPatterns?bannerMessage=${bannerMessage}` - ); + core.application.navigateToApp('management', { + path: `/kibana/indexPatterns?bannerMessage=${bannerMessage}`, + }); } // return never-resolving promise to stop resolving and wait for the url change diff --git a/src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts index 18c6578e3142d..c194687b7c3bf 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts @@ -30,7 +30,7 @@ function flattenHit(indexPattern: IndexPattern, hit: Record, deep: const fields = indexPattern.fields.getByName; (function flatten(obj, keyPrefix = '') { keyPrefix = keyPrefix ? keyPrefix + '.' : ''; - _.forOwn(obj, function(val, key) { + _.forOwn(obj, function (val, key) { key = keyPrefix + key; if (deep) { @@ -38,7 +38,7 @@ function flattenHit(indexPattern: IndexPattern, hit: Record, deep: const isNestedField = field && field.type === 'nested'; const isArrayOfObjects = Array.isArray(val) && _.isPlainObject(_.first(val)); if (isArrayOfObjects && !isNestedField) { - _.each(val, v => flatten(v, key)); + _.each(val, (v) => flatten(v, key)); return; } } else if (flat[key] !== void 0) { @@ -68,15 +68,15 @@ function flattenHit(indexPattern: IndexPattern, hit: Record, deep: } function decorateFlattenedWrapper(hit: Record, metaFields: Record) { - return function(flattened: Record) { + return function (flattened: Record) { // assign the meta fields - _.each(metaFields, function(meta) { + _.each(metaFields, function (meta) { if (meta === '_source') return; flattened[meta] = hit[meta]; }); // unwrap computed fields - _.forOwn(hit.fields, function(val, key: any) { + _.forOwn(hit.fields, function (val, key: any) { if (key[0] === '_' && !_.contains(metaFields, key)) return; flattened[key] = Array.isArray(val) && val.length === 1 ? val[0] : val; }); diff --git a/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts index 9b18fb98f3e02..a0597ed4b9026 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts @@ -64,7 +64,7 @@ export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any const cache: Record = {}; formattedCache.set(hit, cache); - _.forOwn(indexPattern.flattenHit(hit), function(val: any, fieldName?: string) { + _.forOwn(indexPattern.flattenHit(hit), function (val: any, fieldName?: string) { // sync the formatted and partial cache if (!fieldName) { return; @@ -77,7 +77,7 @@ export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any return cache; } - formatHit.formatField = function(hit: Record, fieldName: string) { + formatHit.formatField = function (hit: Record, fieldName: string) { let partials = partialFormattedCache.get(hit); if (partials && partials[fieldName] != null) { return partials[fieldName]; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index 305aa8575e4d7..e4058007e0a57 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -55,7 +55,7 @@ let mockFieldsFetcherResponse: any[] = []; jest.mock('./_fields_fetcher', () => ({ createFieldsFetcher: jest.fn().mockImplementation(() => ({ fetch: jest.fn().mockImplementation(() => { - return new Promise(resolve => resolve(mockFieldsFetcherResponse)); + return new Promise((resolve) => resolve(mockFieldsFetcherResponse)); }), every: jest.fn(), })), @@ -160,7 +160,7 @@ describe('IndexPattern', () => { }); describe('fields', () => { - test('should have expected properties on fields', function() { + test('should have expected properties on fields', function () { expect(indexPattern.fields[0]).toHaveProperty('displayName'); expect(indexPattern.fields[0]).toHaveProperty('filterable'); expect(indexPattern.fields[0]).toHaveProperty('format'); @@ -191,7 +191,7 @@ describe('IndexPattern', () => { test('should request date fields as docvalue_fields', () => { const { docvalueFields } = indexPattern.getComputedFields(); - const docValueFieldNames = docvalueFields.map(field => field.field); + const docValueFieldNames = docvalueFields.map((field) => field.field); expect(Object.keys(docValueFieldNames).length).toBe(3); expect(docValueFieldNames).toContain('@timestamp'); @@ -201,7 +201,7 @@ describe('IndexPattern', () => { test('should request date field doc values in date_time format', () => { const { docvalueFields } = indexPattern.getComputedFields(); - const timestampField = docvalueFields.find(field => field.field === '@timestamp'); + const timestampField = docvalueFields.find((field) => field.field === '@timestamp'); expect(timestampField).toHaveProperty('format', 'date_time'); }); @@ -237,7 +237,7 @@ describe('IndexPattern', () => { const newFields = indexPattern.getNonScriptedFields(); expect(newFields).toHaveLength(2); - expect(newFields.map(f => f.name)).toEqual(['foo', 'bar']); + expect(newFields.map((f) => f.name)).toEqual(['foo', 'bar']); }); test('should preserve the scripted fields', async () => { @@ -249,7 +249,7 @@ describe('IndexPattern', () => { // called to append scripted fields to the response from mapper.getFieldsForIndexPattern // sinon.assert.calledOnce(indexPattern.getScriptedFields); - expect(indexPattern.getScriptedFields().map(f => f.name)).toEqual( + expect(indexPattern.getScriptedFields().map((f) => f.name)).toEqual( mockLogStashFields() .filter((f: Field) => f.scripted) .map((f: Field) => f.name) @@ -313,7 +313,7 @@ describe('IndexPattern', () => { describe('popularizeField', () => { test('should increment the popularity count by default', () => { // const saveSpy = sinon.stub(indexPattern, 'save'); - indexPattern.fields.forEach(async field => { + indexPattern.fields.forEach(async (field) => { const oldCount = field.count || 0; await indexPattern.popularizeField(field.name); @@ -325,7 +325,7 @@ describe('IndexPattern', () => { test('should increment the popularity count', () => { // const saveSpy = sinon.stub(indexPattern, 'save'); - indexPattern.fields.forEach(async field => { + indexPattern.fields.forEach(async (field) => { const oldCount = field.count || 0; const incrementAmount = 4; @@ -337,7 +337,7 @@ describe('IndexPattern', () => { }); test('should decrement the popularity count', () => { - indexPattern.fields.forEach(async field => { + indexPattern.fields.forEach(async (field) => { const oldCount = field.count || 0; const incrementAmount = 4; const decrementAmount = -2; @@ -350,7 +350,7 @@ describe('IndexPattern', () => { }); test('should not go below 0', () => { - indexPattern.fields.forEach(async field => { + indexPattern.fields.forEach(async (field) => { const decrementAmount = -Number.MAX_VALUE; await indexPattern.popularizeField(field.name, decrementAmount); diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 3780557db50d3..3d54009d0fdca 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -33,7 +33,7 @@ import { KBN_FIELD_TYPES, IIndexPattern, IFieldType, - META_FIELDS_SETTING, + UI_SETTINGS, } from '../../../common'; import { findByTitle } from '../utils'; import { IndexPatternMissingIndices } from '../lib'; @@ -85,7 +85,7 @@ export class IndexPattern implements IIndexPattern { return _.isEmpty(serialized) ? undefined : JSON.stringify(serialized); }, _deserialize: (map = '{}') => { - return _.mapValues(JSON.parse(map), mapping => { + return _.mapValues(JSON.parse(map), (mapping) => { return this.deserializeFieldFormatMap(mapping); }); }, @@ -108,8 +108,8 @@ export class IndexPattern implements IIndexPattern { // which cause problems when being consumed from angular this.getConfig = getConfig; - this.shortDotsEnable = this.getConfig('shortDots:enable'); - this.metaFields = this.getConfig(META_FIELDS_SETTING); + this.shortDotsEnable = this.getConfig(UI_SETTINGS.SHORT_DOTS_ENABLE); + this.metaFields = this.getConfig(UI_SETTINGS.META_FIELDS); this.createFieldList = getIndexPatternFieldListCreator({ fieldFormats: getFieldFormats(), @@ -117,8 +117,12 @@ export class IndexPattern implements IIndexPattern { }); this.fields = this.createFieldList(this, [], this.shortDotsEnable); - this.fieldsFetcher = createFieldsFetcher(this, apiClient, this.getConfig(META_FIELDS_SETTING)); - this.flattenHit = flattenHitWrapper(this, this.getConfig(META_FIELDS_SETTING)); + this.fieldsFetcher = createFieldsFetcher( + this, + apiClient, + this.getConfig(UI_SETTINGS.META_FIELDS) + ); + this.flattenHit = flattenHitWrapper(this, this.getConfig(UI_SETTINGS.META_FIELDS)); this.formatHit = formatHitProvider( this, getFieldFormats().getDefaultInstance(KBN_FIELD_TYPES.STRING) @@ -149,7 +153,7 @@ export class IndexPattern implements IIndexPattern { return true; } - return this.fields.every(field => { + return this.fields.every((field) => { // See https://github.com/elastic/kibana/pull/8421 const hasFieldCaps = 'aggregatable' in field && 'searchable' in field; @@ -174,7 +178,7 @@ export class IndexPattern implements IIndexPattern { private updateFromElasticSearch(response: any, forceFieldRefresh: boolean = false) { if (!response.found) { - throw new SavedObjectNotFound(type, this.id, '#/management/kibana/indexPatterns'); + throw new SavedObjectNotFound(type, this.id, 'kibana#/management/kibana/indexPatterns'); } _.forOwn(this.mapping, (fieldMapping: FieldMappingSpec, name: string | undefined) => { @@ -220,7 +224,7 @@ export class IndexPattern implements IIndexPattern { } ); - each(this.getScriptedFields(), function(field) { + each(this.getScriptedFields(), function (field) { scriptFields[field.name] = { script: { source: field.script, @@ -424,7 +428,7 @@ export class IndexPattern implements IIndexPattern { const body = this.prepBody(); // What keys changed since they last pulled the index pattern const originalChangedKeys = Object.keys(body).filter( - key => body[key] !== this.originalBody[key] + (key) => body[key] !== this.originalBody[key] ); return this.savedObjectsClient .update(type, this.id, body, { version: this.version }) @@ -432,7 +436,7 @@ export class IndexPattern implements IIndexPattern { this.id = resp.id; this.version = resp._version; }) - .catch(err => { + .catch((err) => { if ( _.get(err, 'res.status') === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS @@ -452,7 +456,7 @@ export class IndexPattern implements IIndexPattern { // and ensure we ignore the key if the server response // is the same as the original response (since that is expected // if we made a change in that key) - const serverChangedKeys = Object.keys(updatedBody).filter(key => { + const serverChangedKeys = Object.keys(updatedBody).filter((key) => { return updatedBody[key] !== body[key] && this.originalBody[key] !== updatedBody[key]; }); @@ -479,7 +483,7 @@ export class IndexPattern implements IIndexPattern { } // Set the updated response on this object - serverChangedKeys.forEach(key => { + serverChangedKeys.forEach((key) => { this[key] = samePattern[key]; }); this.version = samePattern.version; @@ -505,7 +509,7 @@ export class IndexPattern implements IIndexPattern { refreshFields() { return this._fetchFields() .then(() => this.save()) - .catch(err => { + .catch((err) => { // https://github.com/elastic/kibana/issues/9224 // This call will attempt to remap fields from the matching // ES index which may not actually exist. In that scenario, diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index 515f2e7cf95ee..32b31d4f2758d 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -100,7 +100,7 @@ export class IndexPatternsService { if (!this.savedObjectsCache) { return []; } - return this.savedObjectsCache.map(obj => obj?.id); + return this.savedObjectsCache.map((obj) => obj?.id); }; getTitles = async (refresh: boolean = false): Promise => { @@ -110,7 +110,7 @@ export class IndexPatternsService { if (!this.savedObjectsCache) { return []; } - return this.savedObjectsCache.map(obj => obj?.attributes?.title); + return this.savedObjectsCache.map((obj) => obj?.attributes?.title); }; getFields = async (fields: IndexPatternCachedFieldType[], refresh: boolean = false) => { diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts index 2d3e357e96819..51f4fc7ce94b9 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts @@ -19,7 +19,7 @@ import { setup } from 'test_utils/http_test_setup'; -export const { http } = setup(injectedMetadata => { +export const { http } = setup((injectedMetadata) => { injectedMetadata.getBasePath.mockReturnValue('/hola/daro/'); }); diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts index 0dca025b2bf38..37ee80c2c29e4 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts @@ -29,7 +29,7 @@ describe('IndexPatternsApiClient', () => { indexPatternsApiClient = new IndexPatternsApiClient(http); }); - test('uses the right URI to fetch fields for time patterns', async function() { + test('uses the right URI to fetch fields for time patterns', async function () { const expectedPath = '/api/index_patterns/_fields_for_time_pattern'; await indexPatternsApiClient.getFieldsForTimePattern(); @@ -37,7 +37,7 @@ describe('IndexPatternsApiClient', () => { expect(fetchSpy).toHaveBeenCalledWith(expectedPath, expect.any(Object)); }); - test('uses the right URI to fetch fields for wildcard', async function() { + test('uses the right URI to fetch fields for wildcard', async function () { const expectedPath = '/api/index_patterns/_fields_for_wildcard'; await indexPatternsApiClient.getFieldsForWildcard(); @@ -45,7 +45,7 @@ describe('IndexPatternsApiClient', () => { expect(fetchSpy).toHaveBeenCalledWith(expectedPath, expect.any(Object)); }); - test('uses the right URI to fetch fields for wildcard given a type', async function() { + test('uses the right URI to fetch fields for wildcard given a type', async function () { const expectedPath = '/api/index_patterns/rollup/_fields_for_wildcard'; await indexPatternsApiClient.getFieldsForWildcard({ type: 'rollup' }); diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index 0007d1780c25b..cd189ccf0135b 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -54,13 +54,7 @@ export class IndexPatternsApiClient { } _getUrl(path: string[]) { - return ( - API_BASE_URL + - path - .filter(Boolean) - .map(encodeURIComponent) - .join('/') - ); + return API_BASE_URL + path.filter(Boolean).map(encodeURIComponent).join('/'); } getFieldsForTimePattern(options: GetFieldsOptions = {}) { diff --git a/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts index 74e420ffeb5c0..87a702b3a012d 100644 --- a/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts +++ b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts @@ -29,7 +29,7 @@ describe('Index Pattern Utils', () => { }); it('should not allow illegal characters', () => { - ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { + ILLEGAL_CHARACTERS_VISIBLE.forEach((char) => { const errors = validateIndexPattern(`pattern${char}`); expect(errors[ILLEGAL_CHARACTERS_KEY]).toEqual([char]); }); diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 08796216db1e0..06b5cbdfdfdfb 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -17,6 +17,8 @@ * under the License. */ +import './index.scss'; + import { PluginInitializerContext, CoreSetup, @@ -24,6 +26,7 @@ import { Plugin, PackageInfo, } from 'src/core/public'; +import { ConfigSchema } from '../config'; import { Storage, IStorageWrapper, createStartServicesGetter } from '../../kibana_utils/public'; import { DataPublicPluginSetup, @@ -83,17 +86,18 @@ declare module '../../ui_actions/public' { } export class DataPublicPlugin implements Plugin { - private readonly autocomplete = new AutocompleteService(); + private readonly autocomplete: AutocompleteService; private readonly searchService: SearchService; private readonly fieldFormatsService: FieldFormatsService; private readonly queryService: QueryService; private readonly storage: IStorageWrapper; private readonly packageInfo: PackageInfo; - constructor(initializerContext: PluginInitializerContext) { + constructor(initializerContext: PluginInitializerContext) { this.searchService = new SearchService(); this.queryService = new QueryService(); this.fieldFormatsService = new FieldFormatsService(); + this.autocomplete = new AutocompleteService(initializerContext); this.storage = new Storage(window.localStorage); this.packageInfo = initializerContext.env.packageInfo; } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index fd40153e12c06..dcdb528ac8b7d 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -53,6 +53,7 @@ import { SimpleSavedObject } from 'src/core/public'; import { Subscription } from 'rxjs'; import { Toast } from 'kibana/public'; import { ToastsStart } from 'kibana/public'; +import { TypeOf } from '@kbn/config-schema'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { Unit } from '@elastic/datemath'; @@ -991,6 +992,8 @@ export class IndexPatternField implements IFieldType { // (undocumented) name: string; // (undocumented) + readFromDocValues?: boolean; + // (undocumented) script?: string; // (undocumented) scripted?: boolean; @@ -1317,7 +1320,8 @@ export type PhrasesFilter = Filter & { // // @public (undocumented) export class Plugin implements Plugin_2 { - constructor(initializerContext: PluginInitializerContext_2); + // Warning: (ae-forgotten-export) The symbol "ConfigSchema" needs to be exported by the entry point index.d.ts + constructor(initializerContext: PluginInitializerContext_2); // Warning: (ae-forgotten-export) The symbol "DataSetupDependencies" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1334,7 +1338,7 @@ export class Plugin implements Plugin_2): Plugin; // Warning: (ae-missing-release-tag) "Query" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1364,7 +1368,7 @@ export interface QueryState { // Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const QueryStringInput: React.FC>; +export const QueryStringInput: React.FC>; // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; @@ -1576,8 +1580,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts @@ -1788,6 +1792,39 @@ export interface TimeRange { // @public export type TSearchStrategyProvider = (context: ISearchContext) => ISearchStrategy; +// Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const UI_SETTINGS: { + META_FIELDS: string; + DOC_HIGHLIGHT: string; + QUERY_STRING_OPTIONS: string; + QUERY_ALLOW_LEADING_WILDCARDS: string; + SEARCH_QUERY_LANGUAGE: string; + SORT_OPTIONS: string; + COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; + COURIER_SET_REQUEST_PREFERENCE: string; + COURIER_CUSTOM_REQUEST_PREFERENCE: string; + COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; + COURIER_BATCH_SEARCHES: string; + SEARCH_INCLUDE_FROZEN: string; + HISTOGRAM_BAR_TARGET: string; + HISTOGRAM_MAX_BARS: string; + HISTORY_LIMIT: string; + SHORT_DOTS_ENABLE: string; + FORMAT_DEFAULT_TYPE_MAP: string; + FORMAT_NUMBER_DEFAULT_PATTERN: string; + FORMAT_PERCENT_DEFAULT_PATTERN: string; + FORMAT_BYTES_DEFAULT_PATTERN: string; + FORMAT_CURRENCY_DEFAULT_PATTERN: string; + FORMAT_NUMBER_DEFAULT_LOCALE: string; + TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; + TIMEPICKER_QUICK_RANGES: string; + INDEXPATTERN_PLACEHOLDER: string; + FILTERS_PINNED_BY_DEFAULT: string; + FILTERS_EDITOR_SUGGEST_VALUES: string; +}; + // Warnings were encountered during analysis: // @@ -1796,38 +1833,38 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts index 3b4ca08cbbf14..878142906f54b 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts @@ -24,14 +24,14 @@ import { Subscription } from 'rxjs'; import { FilterManager } from './filter_manager'; import { getFilter } from './test_helpers/get_stub_filter'; import { getFiltersArray } from './test_helpers/get_filters_array'; -import { Filter, FilterStateStore } from '../../../common'; +import { Filter, FilterStateStore, UI_SETTINGS } from '../../../common'; import { coreMock } from '../../../../../core/public/mocks'; const setupMock = coreMock.createSetup(); const uiSettingsMock = (pinnedByDefault: boolean) => (key: string) => { switch (key) { - case 'filters:pinnedByDefault': + case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT: return pinnedByDefault; default: throw new Error(`Unexpected uiSettings key in FilterManager mock: ${key}`); @@ -154,7 +154,7 @@ describe('filter_manager', () => { expect(updateListener.callCount).toBe(2); }); - test('changing a disabled filter should fire only update event', async function() { + test('changing a disabled filter should fire only update event', async function () { const updateStub = jest.fn(); const fetchStub = jest.fn(); const f1 = getFilter(FilterStateStore.GLOBAL_STATE, true, false, 'age', 34); @@ -178,7 +178,7 @@ describe('filter_manager', () => { expect(updateStub).toBeCalledTimes(1); }); - test('should merge multiple conflicting app filters', async function() { + test('should merge multiple conflicting app filters', async function () { filterManager.addFilters(readyFilters, true); const appFilter1 = _.cloneDeep(readyFilters[1]); appFilter1.meta.negate = true; @@ -198,13 +198,13 @@ describe('filter_manager', () => { const res = filterManager.getFilters(); expect(res).toHaveLength(3); expect( - res.filter(function(filter) { + res.filter(function (filter) { return filter.$state && filter.$state.store === FilterStateStore.GLOBAL_STATE; }).length ).toBe(3); }); - test('should set app filters and merge them with duplicate global filters', async function() { + test('should set app filters and merge them with duplicate global filters', async function () { const [filter, ...otherFilters] = readyFilters; filterManager.addFilters(otherFilters, true); const appFilter1 = _.cloneDeep(readyFilters[1]); @@ -219,7 +219,7 @@ describe('filter_manager', () => { expect(newAppFilters).toHaveLength(1); }); - test('should set global filters and remove any duplicated app filters', async function() { + test('should set global filters and remove any duplicated app filters', async function () { filterManager.addFilters(readyFilters, false); const globalFilter1 = _.cloneDeep(readyFilters[1]); const globalFilter2 = _.cloneDeep(readyFilters[2]); @@ -273,7 +273,7 @@ describe('filter_manager', () => { }); describe('add filters', () => { - test('app state should accept a single filter', async function() { + test('app state should accept a single filter', async function () { updateSubscription = filterManager.getUpdates$().subscribe(updateListener); const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); filterManager.addFilters(f1); @@ -398,7 +398,7 @@ describe('filter_manager', () => { } }); - test('should return app and global filters', async function() { + test('should return app and global filters', async function () { const filters = getFiltersArray(); filterManager.addFilters(filters[0], false); filterManager.addFilters(filters[1], true); @@ -421,7 +421,7 @@ describe('filter_manager', () => { expect(res).toHaveLength(3); }); - test('should skip appStateStub filters that match globalStateStub filters', async function() { + test('should skip appStateStub filters that match globalStateStub filters', async function () { filterManager.addFilters(readyFilters, true); const appFilter = _.cloneDeep(readyFilters[1]); filterManager.addFilters(appFilter, false); @@ -429,12 +429,12 @@ describe('filter_manager', () => { // global filters should be listed first const res = filterManager.getFilters(); expect(res).toHaveLength(3); - _.each(res, function(filter) { + _.each(res, function (filter) { expect(filter.$state && filter.$state.store).toBe(FilterStateStore.GLOBAL_STATE); }); }); - test('should allow overwriting a positive filter by a negated one', async function() { + test('should allow overwriting a positive filter by a negated one', async function () { // Add negate: false version of the filter const filter = _.cloneDeep(readyFilters[0]); filter.meta.negate = false; @@ -453,7 +453,7 @@ describe('filter_manager', () => { expect(filterManager.getFilters()[0]).toEqual(negatedFilter); }); - test('should allow overwriting a negated filter by a positive one', async function() { + test('should allow overwriting a negated filter by a positive one', async function () { // Add negate: true version of the same filter const negatedFilter = _.cloneDeep(readyFilters[0]); negatedFilter.meta.negate = true; @@ -473,7 +473,7 @@ describe('filter_manager', () => { expect(filterManager.getFilters()[0]).toEqual(filter); }); - test('should fire the update and fetch events', async function() { + test('should fire the update and fetch events', async function () { const updateStub = jest.fn(); const fetchStub = jest.fn(); @@ -493,8 +493,8 @@ describe('filter_manager', () => { }); }); - describe('filter reconciliation', function() { - test('should de-dupe app filters being added', async function() { + describe('filter reconciliation', function () { + test('should de-dupe app filters being added', async function () { const newFilter = _.cloneDeep(readyFilters[1]); filterManager.addFilters(readyFilters, false); expect(filterManager.getFilters()).toHaveLength(3); @@ -503,7 +503,7 @@ describe('filter_manager', () => { expect(filterManager.getFilters()).toHaveLength(3); }); - test('should de-dupe global filters being added', async function() { + test('should de-dupe global filters being added', async function () { const newFilter = _.cloneDeep(readyFilters[1]); filterManager.addFilters(readyFilters, true); expect(filterManager.getFilters()).toHaveLength(3); @@ -530,7 +530,7 @@ describe('filter_manager', () => { expect(filterManager.getFilters()).toHaveLength(1); }); - test('should mutate global filters on appStateStub filter changes', async function() { + test('should mutate global filters on appStateStub filter changes', async function () { const idx = 1; filterManager.addFilters(readyFilters, true); @@ -542,14 +542,14 @@ describe('filter_manager', () => { filterManager.addFilters(appFilter); const res = filterManager.getFilters(); expect(res).toHaveLength(3); - _.each(res, function(filter, i) { + _.each(res, function (filter, i) { expect(filter.$state && filter.$state.store).toBe('globalState'); // make sure global filter actually mutated expect(filter.meta.negate).toBe(i === idx); }); }); - test('should merge conflicting app filters', async function() { + test('should merge conflicting app filters', async function () { filterManager.addFilters(readyFilters, true); const appFilter = _.cloneDeep(readyFilters[1]); appFilter.meta.negate = true; @@ -562,15 +562,15 @@ describe('filter_manager', () => { const res = filterManager.getFilters(); expect(res).toHaveLength(3); expect( - res.filter(function(filter) { + res.filter(function (filter) { return filter.$state && filter.$state.store === FilterStateStore.GLOBAL_STATE; }).length ).toBe(3); }); - test('should enable disabled filters - global state', async function() { + test('should enable disabled filters - global state', async function () { // test adding to globalStateStub - const disabledFilters = _.map(readyFilters, function(filter) { + const disabledFilters = _.map(readyFilters, function (filter) { const f = _.cloneDeep(filter); f.meta.disabled = true; return f; @@ -581,15 +581,15 @@ describe('filter_manager', () => { const res = filterManager.getFilters(); expect(res).toHaveLength(3); expect( - res.filter(function(filter) { + res.filter(function (filter) { return filter.meta.disabled === false; }).length ).toBe(3); }); - test('should enable disabled filters - app state', async function() { + test('should enable disabled filters - app state', async function () { // test adding to appStateStub - const disabledFilters = _.map(readyFilters, function(filter) { + const disabledFilters = _.map(readyFilters, function (filter) { const f = _.cloneDeep(filter); f.meta.disabled = true; return f; @@ -600,7 +600,7 @@ describe('filter_manager', () => { const res = filterManager.getFilters(); expect(res).toHaveLength(3); expect( - res.filter(function(filter) { + res.filter(function (filter) { return filter.meta.disabled === false; }).length ).toBe(3); @@ -652,21 +652,21 @@ describe('filter_manager', () => { expect(filterManager.getFilters()).toHaveLength(2); }); - test('should remove the filter from appStateStub', async function() { + test('should remove the filter from appStateStub', async function () { filterManager.addFilters(readyFilters, false); expect(filterManager.getAppFilters()).toHaveLength(3); filterManager.removeFilter(readyFilters[0]); expect(filterManager.getAppFilters()).toHaveLength(2); }); - test('should remove the filter from globalStateStub', async function() { + test('should remove the filter from globalStateStub', async function () { filterManager.addFilters(readyFilters, true); expect(filterManager.getGlobalFilters()).toHaveLength(3); filterManager.removeFilter(readyFilters[0]); expect(filterManager.getGlobalFilters()).toHaveLength(2); }); - test('should fire the update and fetch events', async function() { + test('should fire the update and fetch events', async function () { const updateStub = jest.fn(); const fetchStub = jest.fn(); @@ -687,7 +687,7 @@ describe('filter_manager', () => { expect(updateStub).toBeCalledTimes(1); }); - test('should remove matching filters', async function() { + test('should remove matching filters', async function () { filterManager.addFilters([readyFilters[0], readyFilters[1]], true); filterManager.addFilters([readyFilters[2]], false); @@ -697,7 +697,7 @@ describe('filter_manager', () => { expect(filterManager.getGlobalFilters()).toHaveLength(1); }); - test('should remove matching filters by comparison', async function() { + test('should remove matching filters by comparison', async function () { filterManager.addFilters([readyFilters[0], readyFilters[1]], true); filterManager.addFilters([readyFilters[2]], false); @@ -711,7 +711,7 @@ describe('filter_manager', () => { expect(filterManager.getGlobalFilters()).toHaveLength(1); }); - test('should do nothing with a non-matching filter', async function() { + test('should do nothing with a non-matching filter', async function () { filterManager.addFilters([readyFilters[0], readyFilters[1]], true); filterManager.addFilters([readyFilters[2]], false); @@ -723,7 +723,7 @@ describe('filter_manager', () => { expect(filterManager.getGlobalFilters()).toHaveLength(2); }); - test('should remove all the filters from both states', async function() { + test('should remove all the filters from both states', async function () { filterManager.addFilters([readyFilters[0], readyFilters[1]], true); filterManager.addFilters([readyFilters[2]], false); expect(filterManager.getAppFilters()).toHaveLength(1); @@ -736,7 +736,7 @@ describe('filter_manager', () => { }); describe('invert', () => { - test('should fire the update and fetch events', async function() { + test('should fire the update and fetch events', async function () { filterManager.addFilters(readyFilters); expect(filterManager.getFilters()).toHaveLength(3); diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts index e206286bce147..60a49a4bd50f4 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts @@ -34,6 +34,7 @@ import { isFilterPinned, compareFilters, COMPARE_ALL_OPTIONS, + UI_SETTINGS, } from '../../../common'; export class FilterManager { @@ -53,8 +54,8 @@ export class FilterManager { // existing globalFilters should be mutated by appFilters // ignore original appFilters which are already inside globalFilters const cleanedAppFilters: Filter[] = []; - _.each(appFilters, function(filter, i) { - const match = _.find(globalFilters, function(globalFilter) { + _.each(appFilters, function (filter, i) { + const match = _.find(globalFilters, function (globalFilter) { return compareFilters(globalFilter, filter); }); @@ -129,7 +130,7 @@ export class FilterManager { public addFilters( filters: Filter[] | Filter, - pinFilterStatus: boolean = this.uiSettings.get('filters:pinnedByDefault') + pinFilterStatus: boolean = this.uiSettings.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT) ) { if (!Array.isArray(filters)) { filters = [filters]; @@ -157,7 +158,7 @@ export class FilterManager { public setFilters( newFilters: Filter[], - pinFilterStatus: boolean = this.uiSettings.get('filters:pinnedByDefault') + pinFilterStatus: boolean = this.uiSettings.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT) ) { const store = pinFilterStatus ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE; @@ -203,7 +204,7 @@ export class FilterManager { } public removeFilter(filter: Filter) { - const filterIndex = _.findIndex(this.filters, item => { + const filterIndex = _.findIndex(this.filters, (item) => { return _.isEqual(item.meta, filter.meta) && _.isEqual(item.query, filter.query); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts index 4220df7b1a49b..432a763bfd48c 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -39,7 +39,7 @@ function getExistingFilter( value: any ): Filter | undefined { // TODO: On array fields, negating does not negate the combination, rather all terms - return _.find(appFilters, function(filter) { + return _.find(appFilters, function (filter) { if (!filter) return; if (fieldName === '_exists_' && isExistsFilter(filter)) { @@ -95,7 +95,7 @@ export function generateFilters( const negate = operation === '-'; let filter; - _.each(values, function(value) { + _.each(values, function (value) { const existing = getExistingFilter(appFilters, fieldName, value); if (existing) { diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts index 8322e79dd67d1..780db36426dc1 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts @@ -97,7 +97,7 @@ describe('filter manager utilities', () => { expect(result).toEqual({ key: 'test', value: 'example' }); }); - test('should throw an error if no functions match', async done => { + test('should throw an error if no functions match', async (done) => { const filter = buildEmptyFilter(true); mapping.throws(filter); diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts index f75970d4dec18..35d2f2b7b294e 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts @@ -77,7 +77,7 @@ describe('filter manager utilities', () => { expect(after.meta).toHaveProperty('negate', false); }); - test('should finish with a catch', async done => { + test('should finish with a catch', async (done) => { const before: any = { meta: { index: 'logstash-*' } }; try { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts index c90c056f4eb35..b5715e33a4677 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts @@ -22,7 +22,7 @@ import { Filter, FILTERS } from '../../../../../common'; export const mapDefault = (filter: Filter) => { const metaProperty = /(^\$|meta)/; - const key = find(keys(filter), item => !item.match(metaProperty)); + const key = find(keys(filter), (item) => !item.match(metaProperty)); if (key) { const type = FILTERS.CUSTOM; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts index 83c39e449d3ea..0e7b12b6f5f58 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts @@ -44,7 +44,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', 'exists'); }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const filter = buildEmptyFilter(true); try { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts index 97f275b05a520..035dadfd7432a 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts @@ -79,7 +79,7 @@ describe('filter manager utilities', () => { } }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts index f32c459baee5d..c66e65f00efbd 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts @@ -41,7 +41,7 @@ const getFormattedValueFn = (params: any) => { }; const getParams = (filter: GeoBoundingBoxFilter) => { - const key = Object.keys(filter.geo_bounding_box).filter(k => k !== 'ignore_unmapped')[0]; + const key = Object.keys(filter.geo_bounding_box).filter((k) => k !== 'ignore_unmapped')[0]; const params = filter.geo_bounding_box[key]; return { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts index 4af881aa58542..8da98b3a329d6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts @@ -70,7 +70,7 @@ describe('filter manager utilities', () => { } }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const wrongFilter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts index df5379289dd28..55f744558942a 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts @@ -36,7 +36,7 @@ const getFormattedValueFn = (points: string[]) => { }; function getParams(filter: GeoPolygonFilter) { - const key = Object.keys(filter.geo_polygon).filter(k => k !== 'ignore_unmapped')[0]; + const key = Object.keys(filter.geo_polygon).filter((k) => k !== 'ignore_unmapped')[0]; const params = filter.geo_polygon[key]; return { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts index b22583ff8bb24..3d50b87bb32f8 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts @@ -38,7 +38,7 @@ describe('filter_manager/lib', () => { }); describe('when given a filter that is not match_all', () => { - test('filter is rejected', async done => { + test('filter is rejected', async (done) => { delete filter.match_all; try { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts index 67e5987818fb5..d851e96ad6ac6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts @@ -33,7 +33,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', 'missing'); }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const filter = buildEmptyFilter(true); try { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts index 5dd10ce30111f..8f4a8d1bb35a1 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts @@ -38,7 +38,7 @@ describe('filter manager utilities', () => { } }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts index 0589215955562..9b75d5a769d3e 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts @@ -30,7 +30,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', 'foo:bar'); }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const filter = buildEmptyFilter(true); try { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts index c8868b412707b..a6f587ddc7d32 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts @@ -37,7 +37,7 @@ describe('filter manager utilities', () => { } }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts index aee1bf257be01..5e58b2c14c262 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts @@ -65,7 +65,7 @@ describe('mapSpatialFilter()', () => { expect(result).toHaveProperty('type', FILTERS.SPATIAL_FILTER); }); - test('should return undefined for none matching', async done => { + test('should return undefined for none matching', async (done) => { const filter = { meta: { key: 'location', diff --git a/src/plugins/data/public/query/lib/from_user.test.ts b/src/plugins/data/public/query/lib/from_user.test.ts index b74a1a07dc356..208558bb138c6 100644 --- a/src/plugins/data/public/query/lib/from_user.test.ts +++ b/src/plugins/data/public/query/lib/from_user.test.ts @@ -19,23 +19,23 @@ import { fromUser } from '../'; -describe('user input helpers', function() { - describe('user input parser', function() { - it('should return the input if passed an object', function() { +describe('user input helpers', function () { + describe('user input parser', function () { + it('should return the input if passed an object', function () { expect(fromUser({ foo: 'bar' })).toEqual({ foo: 'bar' }); }); - it('unless the object is empty, then convert it to an empty string', function() { + it('unless the object is empty, then convert it to an empty string', function () { expect(fromUser({})).toEqual(''); }); - it('should pass through input strings that not start with {', function() { + it('should pass through input strings that not start with {', function () { expect(fromUser('foo')).toEqual('foo'); expect(fromUser('400')).toEqual('400'); expect(fromUser('true')).toEqual('true'); }); - it('should parse valid JSON and return the object instead of a string', function() { + it('should parse valid JSON and return the object instead of a string', function () { expect(fromUser('{}')).toEqual({}); // invalid json remains a string diff --git a/src/plugins/data/public/query/lib/get_query_log.ts b/src/plugins/data/public/query/lib/get_query_log.ts index a71eb7580cf07..b7827d2c8de02 100644 --- a/src/plugins/data/public/query/lib/get_query_log.ts +++ b/src/plugins/data/public/query/lib/get_query_log.ts @@ -20,6 +20,7 @@ import { IUiSettingsClient } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { PersistedLog } from '../persisted_log'; +import { UI_SETTINGS } from '../../../common'; export function getQueryLog( uiSettings: IUiSettingsClient, @@ -30,7 +31,7 @@ export function getQueryLog( return new PersistedLog( `typeahead:${appName}-${language}`, { - maxLength: uiSettings.get('history:limit'), + maxLength: uiSettings.get(UI_SETTINGS.HISTORY_LIMIT), filterDuplicates: true, }, storage diff --git a/src/plugins/data/public/query/lib/match_pairs.ts b/src/plugins/data/public/query/lib/match_pairs.ts index d5cfb4f99c9d5..ce8b5f17644e7 100644 --- a/src/plugins/data/public/query/lib/match_pairs.ts +++ b/src/plugins/data/public/query/lib/match_pairs.ts @@ -31,8 +31,8 @@ */ const pairs = ['()', '[]', '{}', `''`, '""']; -const openers = pairs.map(pair => pair[0]); -const closers = pairs.map(pair => pair[1]); +const openers = pairs.map((pair) => pair[0]); +const closers = pairs.map((pair) => pair[1]); interface MatchPairsOptions { value: string; diff --git a/src/plugins/data/public/query/persisted_log/persisted_log.ts b/src/plugins/data/public/query/persisted_log/persisted_log.ts index 553b0bf5ef7e0..f61315b85b195 100644 --- a/src/plugins/data/public/query/persisted_log/persisted_log.ts +++ b/src/plugins/data/public/query/persisted_log/persisted_log.ts @@ -64,7 +64,7 @@ export class PersistedLog { // remove any matching items from the stack if option is set if (this.filterDuplicates) { - _.remove(this.items, item => { + _.remove(this.items, (item) => { return this.isDuplicate(item, val); }); } diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts index a86a5b4ed401e..584eb01f44457 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -89,7 +89,7 @@ describe('saved query service', () => { mockSavedObjectsClient.delete.mockReset(); }); - describe('saveQuery', function() { + describe('saveQuery', function () { it('should create a saved object for the given attributes', async () => { mockSavedObjectsClient.create.mockReturnValue({ id: 'foo', @@ -165,7 +165,7 @@ describe('saved query service', () => { expect(error).not.toBe(null); }); }); - describe('findSavedQueries', function() { + describe('findSavedQueries', function () { it('should find and return saved queries without search text or pagination parameters', async () => { mockSavedObjectsClient.find.mockReturnValue({ savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], @@ -276,7 +276,7 @@ describe('saved query service', () => { }); }); - describe('getSavedQuery', function() { + describe('getSavedQuery', function () { it('should retrieve a saved query by id', async () => { mockSavedObjectsClient.get.mockReturnValue({ id: 'foo', attributes: savedQueryAttributes }); @@ -291,14 +291,14 @@ describe('saved query service', () => { }); }); - describe('deleteSavedQuery', function() { + describe('deleteSavedQuery', function () { it('should delete the saved query for the given ID', async () => { await deleteSavedQuery('foo'); expect(mockSavedObjectsClient.delete).toHaveBeenCalledWith('query', 'foo'); }); }); - describe('getAllSavedQueries', function() { + describe('getAllSavedQueries', function () { it('should return all the saved queries', async () => { mockSavedObjectsClient.find.mockReturnValue({ savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], @@ -324,7 +324,7 @@ describe('saved query service', () => { }); }); - describe('getSavedQueryCount', function() { + describe('getSavedQueryCount', function () { it('should return the total number of saved queries', async () => { mockSavedObjectsClient.find.mockReturnValue({ total: 1, diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts index 06e4c1c8be6d5..4e394445b75ae 100644 --- a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts @@ -20,7 +20,7 @@ import { Subscription } from 'rxjs'; import { FilterManager } from '../filter_manager'; import { getFilter } from '../filter_manager/test_helpers/get_stub_filter'; -import { Filter, FilterStateStore } from '../../../common'; +import { Filter, FilterStateStore, UI_SETTINGS } from '../../../common'; import { coreMock } from '../../../../../core/public/mocks'; import { BaseStateContainer, createStateContainer, Storage } from '../../../../kibana_utils/public'; import { QueryService, QueryStart } from '../query_service'; @@ -46,11 +46,11 @@ const startMock = coreMock.createStart(); setupMock.uiSettings.get.mockImplementation((key: string) => { switch (key) { - case 'filters:pinnedByDefault': + case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT: return true; case 'timepicker:timeDefaults': return { from: 'now-15m', to: 'now' }; - case 'timepicker:refreshIntervalDefaults': + case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: return { pause: false, value: 0 }; default: throw new Error(`sync_query test: not mocked uiSetting: ${key}`); diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts index 3256c1cbd65a1..e74497a5053b4 100644 --- a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts @@ -125,7 +125,7 @@ export const connectToQueryState = ( .pipe( filter(({ changes, state }) => { if (updateInProgress) return false; - return syncKeys.some(syncKey => changes[syncKey]); + return syncKeys.some((syncKey) => changes[syncKey]); }), map(({ changes }) => { const newState: QueryState = {}; @@ -150,10 +150,10 @@ export const connectToQueryState = ( return newState; }) ) - .subscribe(newState => { + .subscribe((newState) => { stateContainer.set({ ...stateContainer.get(), ...newState }); }), - stateContainer.state$.subscribe(state => { + stateContainer.state$.subscribe((state) => { updateInProgress = true; // cloneDeep is required because services are mutating passed objects @@ -204,6 +204,6 @@ export const connectToQueryState = ( ]; return () => { - subs.forEach(s => s.unsubscribe()); + subs.forEach((s) => s.unsubscribe()); }; }; diff --git a/src/plugins/data/public/query/state_sync/create_global_query_observable.ts b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts index dd075f9be7d94..87032925294c6 100644 --- a/src/plugins/data/public/query/state_sync/create_global_query_observable.ts +++ b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts @@ -32,7 +32,7 @@ export function createQueryStateObservable({ timefilter: TimefilterSetup; filterManager: FilterManager; }): Observable<{ changes: QueryStateChange; state: QueryState }> { - return new Observable(subscriber => { + return new Observable((subscriber) => { const state = createStateContainer({ time: timefilter.getTime(), refreshInterval: timefilter.getRefreshInterval(), @@ -53,8 +53,8 @@ export function createQueryStateObservable({ currentChange.filters = true; const { filters } = state.get(); - const globalOld = filters?.filter(f => isFilterPinned(f)); - const appOld = filters?.filter(f => !isFilterPinned(f)); + const globalOld = filters?.filter((f) => isFilterPinned(f)); + const appOld = filters?.filter((f) => !isFilterPinned(f)); const globalNew = filterManager.getGlobalFilters(); const appNew = filterManager.getAppFilters(); @@ -73,7 +73,7 @@ export function createQueryStateObservable({ }), state.state$ .pipe( - map(newState => ({ state: newState, changes: currentChange })), + map((newState) => ({ state: newState, changes: currentChange })), tap(() => { currentChange = {}; }) @@ -81,7 +81,7 @@ export function createQueryStateObservable({ .subscribe(subscriber), ]; return () => { - subs.forEach(s => s.unsubscribe()); + subs.forEach((s) => s.unsubscribe()); }; }); } diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts index 50dc35ea955ee..7727153537257 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts @@ -21,7 +21,7 @@ import { Subscription } from 'rxjs'; import { createBrowserHistory, History } from 'history'; import { FilterManager } from '../filter_manager'; import { getFilter } from '../filter_manager/test_helpers/get_stub_filter'; -import { Filter, FilterStateStore } from '../../../common'; +import { Filter, FilterStateStore, UI_SETTINGS } from '../../../common'; import { coreMock } from '../../../../../core/public/mocks'; import { createKbnUrlStateStorage, @@ -39,11 +39,11 @@ const startMock = coreMock.createStart(); setupMock.uiSettings.get.mockImplementation((key: string) => { switch (key) { - case 'filters:pinnedByDefault': + case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT: return true; case 'timepicker:timeDefaults': return { from: 'now-15m', to: 'now' }; - case 'timepicker:refreshIntervalDefaults': + case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: return { pause: false, value: 0 }; default: throw new Error(`sync_query test: not mocked uiSetting: ${key}`); diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts index 77e5b0ab02dc1..4d3da7b9313a3 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts @@ -84,7 +84,7 @@ export const syncQueryStateWithUrl = ( stateStorage: kbnUrlStateStorage, stateContainer: { ...globalQueryStateContainer, - set: state => { + set: (state) => { if (state) { // syncState utils requires to handle incoming "null" value globalQueryStateContainer.set(state); diff --git a/src/plugins/data/public/query/timefilter/get_time.ts b/src/plugins/data/public/query/timefilter/get_time.ts index 9cdd25d3213ce..3706972ce4a2e 100644 --- a/src/plugins/data/public/query/timefilter/get_time.ts +++ b/src/plugins/data/public/query/timefilter/get_time.ts @@ -54,7 +54,9 @@ function createTimeRangeFilter( if (!indexPattern) { return; } - const field = indexPattern.fields.find(f => f.name === (fieldName || indexPattern.timeFieldName)); + const field = indexPattern.fields.find( + (f) => f.name === (fieldName || indexPattern.timeFieldName) + ); if (!field) { return; } diff --git a/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts index 850c87635be9c..3a9402209be20 100644 --- a/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts +++ b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts @@ -22,7 +22,7 @@ import _ from 'lodash'; import { RefreshInterval } from '../../../../common'; import { InputTimeRange } from '../types'; -const valueOf = function(o: any) { +const valueOf = function (o: any) { if (o) return o.valueOf(); }; diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.ts b/src/plugins/data/public/query/timefilter/timefilter_service.ts index 413163ed059ad..df2fbc8e5a8f3 100644 --- a/src/plugins/data/public/query/timefilter/timefilter_service.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.ts @@ -20,6 +20,7 @@ import { IUiSettingsClient } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { TimeHistory, Timefilter, TimeHistoryContract, TimefilterContract } from './index'; +import { UI_SETTINGS } from '../../../common'; /** * Filter Service @@ -35,7 +36,7 @@ export class TimefilterService { public setup({ uiSettings, storage }: TimeFilterServiceDependencies): TimefilterSetup { const timefilterConfig = { timeDefaults: uiSettings.get('timepicker:timeDefaults'), - refreshIntervalDefaults: uiSettings.get('timepicker:refreshIntervalDefaults'), + refreshIntervalDefaults: uiSettings.get(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS), }; const history = new TimeHistory(storage); const timefilter = new Timefilter(timefilterConfig, history); diff --git a/src/plugins/data/public/search/aggs/agg_config.test.ts b/src/plugins/data/public/search/aggs/agg_config.test.ts index b5df90313230c..6a0dad07b69bb 100644 --- a/src/plugins/data/public/search/aggs/agg_config.test.ts +++ b/src/plugins/data/public/search/aggs/agg_config.test.ts @@ -375,7 +375,7 @@ describe('AggConfig', () => { fieldFormats.getDefaultInstance = (() => ({ getConverterFor: (t?: string) => t || identity, })) as any; - indexPattern.fields.getByName = name => + indexPattern.fields.getByName = (name) => ({ format: { getConverterFor: (t?: string) => t || identity, @@ -500,7 +500,7 @@ describe('AggConfig', () => { // Overwrite the `ranges` param in the `range` agg with a mock toExpressionAst function const range: MetricAggType = typesRegistry.get('range'); range.expressionName = 'aggRange'; - const rangesParam = range.params.find(p => p.name === 'ranges'); + const rangesParam = range.params.find((p) => p.name === 'ranges'); rangesParam!.toExpressionAst = (val: any) => ({ type: 'function', function: 'aggRanges', @@ -623,7 +623,7 @@ describe('AggConfig', () => { fieldFormats.getDefaultInstance = (() => ({ getConverterFor: (t?: string) => t || identity, })) as any; - indexPattern.fields.getByName = name => + indexPattern.fields.getByName = (name) => ({ format: { getConverterFor: (t?: string) => t || identity, @@ -649,10 +649,7 @@ describe('AggConfig', () => { }, }; expect(aggConfig.fieldFormatter().toString()).toBe( - aggConfig - .getField() - .format.getConverterFor() - .toString() + aggConfig.getField().format.getConverterFor().toString() ); }); diff --git a/src/plugins/data/public/search/aggs/agg_config.ts b/src/plugins/data/public/search/aggs/agg_config.ts index 86a2c3e0e82e4..ee4116eefc0e2 100644 --- a/src/plugins/data/public/search/aggs/agg_config.ts +++ b/src/plugins/data/public/search/aggs/agg_config.ts @@ -75,12 +75,12 @@ export class AggConfig { static ensureIds(list: any[]) { const have: IAggConfig[] = []; const haveNot: AggConfigOptions[] = []; - list.forEach(function(obj) { + list.forEach(function (obj) { (obj.id ? have : haveNot).push(obj); }); let nextId = AggConfig.nextId(have); - haveNot.forEach(function(obj) { + haveNot.forEach(function (obj) { obj.id = String(nextId++); }); @@ -95,7 +95,7 @@ export class AggConfig { static nextId(list: IAggConfig[]) { return ( 1 + - list.reduce(function(max, obj) { + list.reduce(function (max, obj) { return Math.max(max, +obj.id || 0); }, 0) ); @@ -154,7 +154,7 @@ export class AggConfig { from = from || this.params || {}; const to = (this.params = {} as any); - this.getAggParams().forEach(aggParam => { + this.getAggParams().forEach((aggParam) => { let val = from[aggParam.name]; if (val == null) { @@ -323,7 +323,7 @@ export class AggConfig { // Go through each of the params and convert to an array of expression args. const params = Object.entries(rest.params).reduce((acc, [key, value]) => { - const deserializedParam = this.getAggParams().find(p => p.name === key); + const deserializedParam = this.getAggParams().find((p) => p.name === key); if (deserializedParam && deserializedParam.toExpressionAst) { // If the param provides `toExpressionAst`, we call it with the value @@ -454,7 +454,7 @@ export class AggConfig { if (this.__typeDecorations) { _.forOwn( this.__typeDecorations, - function(prop, name: string | undefined) { + function (prop, name: string | undefined) { // @ts-ignore delete this[name]; }, diff --git a/src/plugins/data/public/search/aggs/agg_configs.test.ts b/src/plugins/data/public/search/aggs/agg_configs.test.ts index 653bf6a266df6..6e6fb3350d901 100644 --- a/src/plugins/data/public/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/public/search/aggs/agg_configs.test.ts @@ -172,7 +172,7 @@ describe('AggConfigs', () => { const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const sorted = ac.getRequestAggs(); - const aggs = indexBy(ac.aggs, agg => agg.type.name); + const aggs = indexBy(ac.aggs, (agg) => agg.type.name); expect(sorted.shift()).toBe(aggs.terms); expect(sorted.shift()).toBe(aggs.histogram); @@ -195,7 +195,7 @@ describe('AggConfigs', () => { const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const sorted = ac.getResponseAggs(); - const aggs = indexBy(ac.aggs, agg => agg.type.name); + const aggs = indexBy(ac.aggs, (agg) => agg.type.name); expect(sorted.shift()).toBe(aggs.terms); expect(sorted.shift()).toBe(aggs.date_histogram); @@ -212,7 +212,7 @@ describe('AggConfigs', () => { const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const sorted = ac.getResponseAggs(); - const aggs = indexBy(ac.aggs, agg => agg.type.name); + const aggs = indexBy(ac.aggs, (agg) => agg.type.name); expect(sorted.shift()).toBe(aggs.terms); expect(sorted.shift()).toBe(aggs.date_histogram); @@ -226,7 +226,7 @@ describe('AggConfigs', () => { describe('#toDsl', () => { beforeEach(() => { indexPattern = stubIndexPattern as IndexPattern; - indexPattern.fields.getByName = name => (name as unknown) as IndexPatternField; + indexPattern.fields.getByName = (name) => (name as unknown) as IndexPatternField; }); it('uses the sorted aggs', () => { @@ -250,7 +250,7 @@ describe('AggConfigs', () => { fieldFormats, }); - const aggInfos = ac.aggs.map(aggConfig => { + const aggInfos = ac.aggs.map((aggConfig) => { const football = {}; aggConfig.toDsl = jest.fn().mockImplementation(() => football); @@ -328,7 +328,7 @@ describe('AggConfigs', () => { expect(typeof dsl[histo.id]).toBe('object'); expect(dsl[histo.id]).toHaveProperty('aggs'); - metrics.forEach(metric => { + metrics.forEach((metric) => { expect(dsl[histo.id].aggs).toHaveProperty(metric.id); expect(dsl[histo.id].aggs[metric.id]).not.toHaveProperty('aggs'); }); diff --git a/src/plugins/data/public/search/aggs/agg_configs.ts b/src/plugins/data/public/search/aggs/agg_configs.ts index d2151a2c5ed4d..6cc03be292d7b 100644 --- a/src/plugins/data/public/search/aggs/agg_configs.ts +++ b/src/plugins/data/public/search/aggs/agg_configs.ts @@ -95,7 +95,7 @@ export class AggConfigs { this.timeRange = timeRange; const updateAggTimeRange = (agg: AggConfig) => { - _.each(agg.params, param => { + _.each(agg.params, (param) => { if (param instanceof AggConfig) { updateAggTimeRange(param); } @@ -176,10 +176,10 @@ export class AggConfigs { if (hierarchical) { // collect all metrics, and filter out the ones that we won't be copying nestedMetrics = this.aggs - .filter(function(agg) { + .filter(function (agg) { return agg.type.type === 'metrics' && agg.type.name !== 'count'; }) - .map(agg => { + .map((agg) => { return { config: agg, dsl: agg.toDsl(this), @@ -239,15 +239,15 @@ export class AggConfigs { } byId(id: string) { - return this.aggs.find(agg => agg.id === id); + return this.aggs.find((agg) => agg.id === id); } byName(name: string) { - return this.aggs.filter(agg => agg.type?.name === name); + return this.aggs.filter((agg) => agg.type?.name === name); } byType(type: string) { - return this.aggs.filter(agg => agg.type?.type === type); + return this.aggs.filter((agg) => agg.type?.type === type); } byTypeName(type: string) { @@ -255,13 +255,13 @@ export class AggConfigs { } bySchemaName(schema: string) { - return this.aggs.filter(agg => agg.schema === schema); + return this.aggs.filter((agg) => agg.schema === schema); } getRequestAggs(): AggConfig[] { // collect all the aggregations const aggregations = this.aggs - .filter(agg => agg.enabled && agg.type) + .filter((agg) => agg.enabled && agg.type) .reduce((requestValuesAggs, agg: AggConfig) => { const aggs = agg.getRequestAggs(); return aggs ? requestValuesAggs.concat(aggs) : requestValuesAggs; @@ -288,7 +288,7 @@ export class AggConfigs { * @return {array[AggConfig]} */ getResponseAggs(): AggConfig[] { - return this.getRequestAggs().reduce(function(responseValuesAggs, agg: AggConfig) { + return this.getRequestAggs().reduce(function (responseValuesAggs, agg: AggConfig) { const aggs = agg.getResponseAggs(); return aggs ? responseValuesAggs.concat(aggs) : responseValuesAggs; }, [] as AggConfig[]); @@ -303,7 +303,7 @@ export class AggConfigs { */ getResponseAggById(id: string): AggConfig | undefined { id = String(id); - const reqAgg = _.find(this.getRequestAggs(), function(agg: AggConfig) { + const reqAgg = _.find(this.getRequestAggs(), function (agg: AggConfig) { return id.substr(0, String(agg.id).length) === agg.id; }); if (!reqAgg) return; diff --git a/src/plugins/data/public/search/aggs/agg_params.test.ts b/src/plugins/data/public/search/aggs/agg_params.test.ts index e116bdca157ff..997027fe88e82 100644 --- a/src/plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/plugins/data/public/search/aggs/agg_params.test.ts @@ -68,7 +68,7 @@ describe('AggParams class', () => { expect(aggParams[0] instanceof OptionedParamType).toBeTruthy(); }); - it('Always converts the params to a BaseParamType', function() { + it('Always converts the params to a BaseParamType', function () { const params = [ { name: 'height', @@ -88,7 +88,7 @@ describe('AggParams class', () => { expect(aggParams).toHaveLength(params.length); - aggParams.forEach(aggParam => expect(aggParam instanceof BaseParamType).toBeTruthy()); + aggParams.forEach((aggParam) => expect(aggParam instanceof BaseParamType).toBeTruthy()); }); }); }); diff --git a/src/plugins/data/public/search/aggs/agg_params.ts b/src/plugins/data/public/search/aggs/agg_params.ts index e7b2f72bae656..4f7bec30fa27d 100644 --- a/src/plugins/data/public/search/aggs/agg_params.ts +++ b/src/plugins/data/public/search/aggs/agg_params.ts @@ -83,7 +83,7 @@ export const writeParams = < }; locals = locals || {}; - params.forEach(param => { + params.forEach((param) => { if (param.write) { param.write(aggConfig, output, aggs, locals); } else { diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/public/search/aggs/agg_type.test.ts index 369ae0ce0b3a5..18783bbd9a760 100644 --- a/src/plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/plugins/data/public/search/aggs/agg_type.test.ts @@ -159,7 +159,7 @@ describe('AggType Class', () => { }); }); - describe('getFormat', function() { + describe('getFormat', function () { let aggConfig: IAggConfig; let field: any; diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts index abda6b5fc5980..fba3d35f002af 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -65,7 +65,7 @@ const getAggResultBuckets = ( for (let aggId = 0; aggId < responseAggs.length; aggId++) { const aggById = responseAggs[aggId]; const aggKey = keys(responseAgg)[aggId]; - const aggConfig = find(aggConfigs.aggs, agg => agg.id === aggKey); + const aggConfig = find(aggConfigs.aggs, (agg) => agg.id === aggKey); if (aggConfig) { const aggResultBucket = find(aggById.buckets, (bucket, bucketObjKey) => { const bucketKey = aggConfig @@ -102,9 +102,9 @@ const getAggConfigResultMissingBuckets = (responseAggs: any, aggId: string) => { if (matchingBucket) resultBuckets.push(matchingBucket); return resultBuckets; } - each(responseAggs, agg => { + each(responseAggs, (agg) => { if (agg.buckets) { - each(agg.buckets, bucket => { + each(agg.buckets, (bucket) => { resultBuckets = [...resultBuckets, ...getAggConfigResultMissingBuckets(bucket, aggId)]; }); } @@ -137,8 +137,8 @@ export const buildOtherBucketAgg = ( aggWithOtherBucket: IBucketAggConfig, response: any ) => { - const bucketAggs = aggConfigs.aggs.filter(agg => agg.type.type === AggGroupNames.Buckets); - const index = bucketAggs.findIndex(agg => agg.id === aggWithOtherBucket.id); + const bucketAggs = aggConfigs.aggs.filter((agg) => agg.type.type === AggGroupNames.Buckets); + const index = bucketAggs.findIndex((agg) => agg.id === aggWithOtherBucket.id); const aggs = aggConfigs.toDsl(); const indexPattern = aggWithOtherBucket.params.field.indexPattern; @@ -215,7 +215,7 @@ export const buildOtherBucketAgg = ( } // create not filters for all the buckets - each(agg.buckets, bucket => { + each(agg.buckets, (bucket) => { if (bucket.key === '__missing__') return; const filter = currentAgg.createFilter(bucket.key); filter.meta.negate = true; @@ -289,7 +289,7 @@ export const updateMissingBucket = ( ) => { const updatedResponse = cloneDeep(response); const aggResultBuckets = getAggConfigResultMissingBuckets(updatedResponse.aggregations, agg.id); - aggResultBuckets.forEach(bucket => { + aggResultBuckets.forEach((bucket) => { bucket.key = '__missing__'; }); return updatedResponse; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index bb73c8a39df19..2f03b8ec67112 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -121,7 +121,7 @@ describe('AggConfig Filters', () => { }); test('extends the filter edge to 1ms before the next bucket for all interval options', () => { - intervalOptions.forEach(option => { + intervalOptions.forEach((option) => { let duration; if (option.val !== 'custom' && moment(1, option.val).isValid()) { // @ts-ignore @@ -137,12 +137,7 @@ describe('AggConfig Filters', () => { const params = filter.range[field.name]; expect(params.gte).toBe(bucketStart.toISOString()); - expect(params.lt).toBe( - bucketStart - .clone() - .add(interval) - .toISOString() - ); + expect(params.lt).toBe(bucketStart.clone().add(interval).toISOString()); }); }); }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index 219bb5440c8da..8a5596f669cb7 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -31,7 +31,7 @@ import { dateHistogramInterval, TimeRange } from '../../../../common'; import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; -import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; +import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common'; import { TimefilterContract } from '../../../query'; import { QuerySetup } from '../../../query/query_service'; import { GetInternalStartServicesFn } from '../../../types'; @@ -125,8 +125,8 @@ export const getDateHistogramBucketAgg = ({ const { timefilter } = query.timefilter; buckets = new TimeBuckets({ - 'histogram:maxBars': uiSettings.get('histogram:maxBars'), - 'histogram:barTarget': uiSettings.get('histogram:barTarget'), + 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), dateFormat: uiSettings.get('dateFormat'), 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), }); @@ -225,7 +225,7 @@ export const getDateHistogramBucketAgg = ({ const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale && interval.scale < 1; if (scaleMetrics && aggs) { - const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); + const metrics = aggs.aggs.filter((a) => isMetricAggType(a.type)); const all = every(metrics, (a: IBucketAggConfig) => { const { type } = a; @@ -275,7 +275,7 @@ export const getDateHistogramBucketAgg = ({ name: 'drop_partials', default: false, write: noop, - shouldShow: agg => { + shouldShow: (agg) => { const field = agg.params.field; return field && field.name && field.name === agg.getIndexPattern().timeFieldName; }, diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 504958854cad4..447347dbfbe10 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -65,7 +65,7 @@ export const getDateRangeBucketAgg = ({ TEXT_CONTEXT_TYPE, fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.DATE) ); - const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { + const DateRangeFormat = FieldFormat.from(function (range: DateRangeKey) { return convertDateRangeToString(range, formatter); }); return new DateRangeFormat(); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/filters.test.ts new file mode 100644 index 0000000000000..295e740a2a780 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/filters.test.ts @@ -0,0 +1,253 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Query } from '../../../../common'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; +import { BUCKET_TYPES } from './bucket_agg_types'; +import { getFiltersBucketAgg, FiltersBucketAggDependencies } from './filters'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { InternalStartServices } from '../../../types'; + +describe('Filters Agg', () => { + let aggTypesDependencies: FiltersBucketAggDependencies; + + beforeEach(() => { + jest.resetAllMocks(); + const { uiSettings } = coreMock.createSetup(); + + aggTypesDependencies = { + uiSettings, + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), + }; + }); + + describe('order agg editor UI', () => { + const getAggConfigs = (params: Record = {}) => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + find: () => field, + }, + } as any; + + const field = { + name: 'field', + indexPattern, + }; + + return new AggConfigs( + indexPattern, + [ + { + id: 'test', + params, + type: BUCKET_TYPES.FILTERS, + }, + ], + { + typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } + ); + }; + + const generateFilter = (label: string, language: string, query: Query['query']) => ({ + label, + input: { + language, + query, + }, + }); + + describe('using Lucene', () => { + test('works with lucene filters', () => { + const aggConfigs = getAggConfigs({ + filters: [ + generateFilter('a', 'lucene', 'foo'), + generateFilter('b', 'lucene', 'status:200'), + generateFilter('c', 'lucene', 'status:[400 TO 499] AND (foo OR bar)'), + ], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.must)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "query_string": Object { + "query": "foo", + }, + }, + ], + Array [ + Object { + "query_string": Object { + "query": "status:200", + }, + }, + ], + Array [ + Object { + "query_string": Object { + "query": "status:[400 TO 499] AND (foo OR bar)", + }, + }, + ], + ] + `); + }); + }); + + describe('using KQL', () => { + test('works with KQL filters', () => { + const aggConfigs = getAggConfigs({ + filters: [ + generateFilter('a', 'kuery', 'status:200'), + generateFilter('b', 'kuery', 'status > 500 and name:hello'), + ], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.filter)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "field": 200, + }, + }, + ], + }, + }, + ], + Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "field": Object { + "gt": 500, + }, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "field": "hello", + }, + }, + ], + }, + }, + ], + }, + }, + ], + ] + `); + }); + + test('works with KQL wildcards', () => { + const aggConfigs = getAggConfigs({ + filters: [generateFilter('a', 'kuery', '*'), generateFilter('b', 'kuery', 'foo*')], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.filter)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "query_string": Object { + "query": "*", + }, + }, + ], + Array [ + Object { + "query_string": Object { + "query": "foo*", + }, + }, + ], + ] + `); + }); + + test('throws with leading wildcards if not allowed', () => { + const aggConfigs = getAggConfigs({ + filters: [generateFilter('a', 'kuery', '*foo*')], + }); + + expect(() => { + aggConfigs.aggs[0].toDsl(); + }).toThrowErrorMatchingInlineSnapshot(` +"Leading wildcards are disabled. See query:allowLeadingWildcards in Advanced Settings. +*foo* +^" +`); + }); + + test('works with leading wildcards if allowed', () => { + aggTypesDependencies.uiSettings.get = (s: any) => + s === 'query:allowLeadingWildcards' ? true : s; + + const aggConfigs = getAggConfigs({ + filters: [generateFilter('a', 'kuery', '*foo*')], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.filter)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "query_string": Object { + "query": "*foo*", + }, + }, + ], + ] + `); + }); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index 8654645d46a9b..4052c0b390155 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -26,7 +26,7 @@ import { toAngularJSON } from '../utils'; import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../plugins/kibana_utils/public'; -import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common'; +import { getEsQueryConfig, buildEsQuery, Query, UI_SETTINGS } from '../../../../common'; import { getQueryLog } from '../../../query'; import { GetInternalStartServicesFn } from '../../../types'; import { BaseAggParams } from '../types'; @@ -69,13 +69,16 @@ export const getFiltersBucketAgg = ({ { name: 'filters', default: [ - { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' }, + { + input: { query: '', language: uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) }, + label: '', + }, ], write(aggConfig, output) { const inFilters: FilterValue[] = aggConfig.params.filters; if (!size(inFilters)) return; - inFilters.forEach(filter => { + inFilters.forEach((filter) => { const persistedLog = getQueryLog( uiSettings, new Storage(window.localStorage), @@ -87,7 +90,7 @@ export const getFiltersBucketAgg = ({ const outFilters = transform( inFilters, - function(filters, filter) { + function (filters, filter) { const input = cloneDeep(filter.input); if (!input) { diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index d04df4f8aac6b..c1fad17f488db 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -24,7 +24,7 @@ import { IUiSettingsClient } from 'src/core/public'; import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../common'; +import { KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; import { BaseAggParams } from '../types'; import { ExtendedBounds } from './lib/extended_bounds'; @@ -155,8 +155,8 @@ export const getHistogramBucketAgg = ({ const range = autoBounds.max - autoBounds.min; const bars = range / interval; - if (bars > uiSettings.get('histogram:maxBars')) { - const minInterval = range / uiSettings.get('histogram:maxBars'); + if (bars > uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS)) { + const minInterval = range / uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); // Round interval by order of magnitude to provide clean intervals // Always round interval up so there will always be less buckets than histogram:maxBars diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/ip_range.ts index 029fd864154be..10fdb2d93b56e 100644 --- a/src/plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/ip_range.ts @@ -73,7 +73,7 @@ export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketA TEXT_CONTEXT_TYPE, fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.IP) ); - const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { + const IpRangeFormat = FieldFormat.from(function (range: IpRangeKey) { return convertIPRangeToString(range, formatter); }); return new IpRangeFormat(); diff --git a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index 116f8cfad60f6..47da7e59af5e0 100644 --- a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -25,7 +25,7 @@ export const isType = (...types: string[]) => { return (agg: IAggConfig): boolean => { const field = agg.params.field; - return types.some(type => field && field.type === type); + return types.some((type) => field && field.type === type); }; }; diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index 1bfc508dc3871..45a76f08ddd13 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -255,7 +255,7 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { defaultMessage: 'Label for other bucket', }), - shouldShow: agg => agg.getParam('otherBucket'), + shouldShow: (agg) => agg.getParam('otherBucket'), write: noop, }, { @@ -274,7 +274,7 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { defaultMessage: 'Label for missing values', }), - shouldShow: agg => agg.getParam('missingBucket'), + shouldShow: (agg) => agg.getParam('missingBucket'), write: noop, }, { diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index 382c5a10c2da5..4864a8b9d013b 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -44,7 +44,7 @@ describe('AggTypesComponent', () => { describe('bucket aggs', () => { test('all extend BucketAggType', () => { - buckets.forEach(bucketAgg => { + buckets.forEach((bucketAgg) => { expect(isBucketAggType(bucketAgg)).toBeTruthy(); }); }); @@ -52,7 +52,7 @@ describe('AggTypesComponent', () => { describe('metric aggs', () => { test('all extend MetricAggType', () => { - metrics.forEach(metricAgg => { + metrics.forEach((metricAgg) => { expect(isMetricAggType(metricAgg)).toBeTruthy(); }); }); diff --git a/src/plugins/data/public/search/aggs/metrics/avg.ts b/src/plugins/data/public/search/aggs/metrics/avg.ts index dba18d562ec19..1aa39ccd2aad9 100644 --- a/src/plugins/data/public/search/aggs/metrics/avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/avg.ts @@ -41,7 +41,7 @@ export const getAvgMetricAgg = ({ getInternalStartServices }: AvgMetricAggDepend { name: METRIC_TYPES.AVG, title: averageTitle, - makeLabel: aggConfig => { + makeLabel: (aggConfig) => { return i18n.translate('data.search.aggs.metrics.averageLabel', { defaultMessage: 'Average {field}', values: { field: aggConfig.getFieldDisplayName() }, diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts index ded17eebf465b..927e9a7ae4458 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -50,7 +50,7 @@ export const getBucketAvgMetricAgg = ({ { name: METRIC_TYPES.AVG_BUCKET, title: averageBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallAverageLabel), + makeLabel: (agg) => makeNestedLabel(agg, overallAverageLabel), subtype: siblingPipelineAggHelper.subtype, params: [...siblingPipelineAggHelper.params()], getFormat: siblingPipelineAggHelper.getFormat, diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts index dde328008b88a..2b171fcbd24fd 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -49,7 +49,7 @@ export const getBucketMaxMetricAgg = ({ { name: METRIC_TYPES.MAX_BUCKET, title: maxBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMaxLabel), + makeLabel: (agg) => makeNestedLabel(agg, overallMaxLabel), subtype: siblingPipelineAggHelper.subtype, params: [...siblingPipelineAggHelper.params()], getFormat: siblingPipelineAggHelper.getFormat, diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts index 9949524ce6110..e6a523eeea374 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -49,7 +49,7 @@ export const getBucketMinMetricAgg = ({ { name: METRIC_TYPES.MIN_BUCKET, title: minBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMinLabel), + makeLabel: (agg) => makeNestedLabel(agg, overallMinLabel), subtype: siblingPipelineAggHelper.subtype, params: [...siblingPipelineAggHelper.params()], getFormat: siblingPipelineAggHelper.getFormat, diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts index e69ae5798c6e1..71c88596ea569 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts @@ -49,7 +49,7 @@ export const getBucketSumMetricAgg = ({ { name: METRIC_TYPES.SUM_BUCKET, title: sumBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallSumLabel), + makeLabel: (agg) => makeNestedLabel(agg, overallSumLabel), subtype: siblingPipelineAggHelper.subtype, params: [...siblingPipelineAggHelper.params()], getFormat: siblingPipelineAggHelper.getFormat, diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/public/search/aggs/metrics/cardinality.ts index af594195fe027..9ff3e84c38cd8 100644 --- a/src/plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/plugins/data/public/search/aggs/metrics/cardinality.ts @@ -59,7 +59,7 @@ export const getCardinalityMetricAgg = ({ name: 'field', type: 'field', filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM + (type) => type !== KBN_FIELD_TYPES.HISTOGRAM ), }, ], diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts index 67e907239799a..44bfca1b6fb87 100644 --- a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts @@ -51,7 +51,7 @@ export const getCumulativeSumMetricAgg = ({ name: METRIC_TYPES.CUMULATIVE_SUM, title: cumulativeSumTitle, subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, cumulativeSumLabel), + makeLabel: (agg) => makeNestedLabel(agg, cumulativeSumLabel), params: [...parentPipelineAggHelper.params()], getFormat: parentPipelineAggHelper.getFormat, }, diff --git a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts index bb16cba1bee62..83656db4ac547 100644 --- a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -84,7 +84,7 @@ export class MetricAggType { + ((agg) => { const { fieldFormats } = dependencies.getInternalStartServices(); const field = agg.getField(); return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts index 38a824629d304..1173ae5358ee7 100644 --- a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts @@ -54,7 +54,7 @@ export const getMovingAvgMetricAgg = ({ dslName: 'moving_fn', title: movingAvgTitle, subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, movingAvgLabel), + makeLabel: (agg) => makeNestedLabel(agg, movingAvgLabel), params: [ ...parentPipelineAggHelper.params(), { diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index f386034ea8a7b..2201366cdf78b 100644 --- a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -28,7 +28,7 @@ import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { GetInternalStartServicesFn, InternalStartServices } from '../../../types'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; -describe('parent pipeline aggs', function() { +describe('parent pipeline aggs', function () { const getInternalStartServices: GetInternalStartServicesFn = () => (({ fieldFormats: fieldFormatsServiceMock.createStartContract(), @@ -61,7 +61,7 @@ describe('parent pipeline aggs', function() { }, ]; - metrics.forEach(metric => { + metrics.forEach((metric) => { describe(`${metric.title} metric`, () => { let aggDsl: Record; let metricAgg: MetricAggType; @@ -243,7 +243,7 @@ describe('parent pipeline aggs', function() { // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; - aggConfig.type.params.forEach(param => { + aggConfig.type.params.forEach((param) => { param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); }); expect(customMetricSpy.mock.calls[0]).toEqual([customMetric, searchSource, {}]); diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 7491f15aa3002..df52c10a82495 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -30,7 +30,7 @@ import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { InternalStartServices } from '../../../types'; -describe('AggTypesMetricsPercentileRanksProvider class', function() { +describe('AggTypesMetricsPercentileRanksProvider class', function () { let aggConfigs: IAggConfigs; let fieldFormats: FieldFormatsStart; let aggTypesDependencies: PercentileRanksMetricAggDependencies; @@ -78,7 +78,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { ); }); - it('uses the custom label if it is set', function() { + it('uses the custom label if it is set', function () { const responseAggs: any = getPercentileRanksMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IPercentileRanksAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts index fe112a50ad3c1..00bc631cefab8 100644 --- a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts +++ b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts @@ -51,7 +51,7 @@ export const getSerialDiffMetricAgg = ({ name: METRIC_TYPES.SERIAL_DIFF, title: serialDiffTitle, subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, serialDiffLabel), + makeLabel: (agg) => makeNestedLabel(agg, serialDiffLabel), params: [...parentPipelineAggHelper.params()], getFormat: parentPipelineAggHelper.getFormat, }, diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index 5e1834d3b4935..56bd33b2b6345 100644 --- a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -61,7 +61,7 @@ describe('sibling pipeline aggs', () => { }, ]; - metrics.forEach(metric => { + metrics.forEach((metric) => { describe(`${metric.title} metric`, () => { let aggDsl: Record; let metricAgg: MetricAggType; @@ -127,7 +127,7 @@ describe('sibling pipeline aggs', () => { expect(metricAgg.makeLabel(aggConfig)).toEqual(`${metric.title} of Count`); }); - it('should set parent aggs', function() { + it('should set parent aggs', function () { init(); expect(aggDsl[metric.name].buckets_path).toBe('2-bucket>_count'); @@ -186,7 +186,7 @@ describe('sibling pipeline aggs', () => { customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; - aggConfig.type.params.forEach(param => { + aggConfig.type.params.forEach((param) => { param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); }); diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts index 617e458cf6243..49e0a3e4b349a 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -78,7 +78,7 @@ describe('Top hit metric', () => { getByName: () => field, filter: () => [field], }, - flattenHit: jest.fn(x => x!._source), + flattenHit: jest.fn((x) => x!._source), } as any; const aggConfigs = new AggConfigs( @@ -336,7 +336,7 @@ describe('Top hit metric', () => { data: [undefined, null], result: null, }, - ].forEach(agg => { + ].forEach((agg) => { it(`should return the result of the ${agg.type} aggregation over the last doc - ${agg.description}`, () => { const bucket = { '1': { diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.ts index df7a76f151c07..c6890f98b20e4 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.ts @@ -80,7 +80,7 @@ export const getTopHitMetricAgg = ({ getInternalStartServices }: TopHitMetricAgg type: 'field', onlyAggregatable: false, filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM + (type) => type !== KBN_FIELD_TYPES.HISTOGRAM ), write(agg, output) { const field = agg.getParam('field'); @@ -230,7 +230,7 @@ export const getTopHitMetricAgg = ({ getInternalStartServices }: TopHitMetricAgg const path = agg.getParam('field').name; let values = _.flatten( - hits.map(hit => + hits.map((hit) => path === '_source' ? hit._source : agg.getIndexPattern().flattenHit(hit, true)[path] ) ); diff --git a/src/plugins/data/public/search/aggs/param_types/base.ts b/src/plugins/data/public/search/aggs/param_types/base.ts index a6f7e5adea043..79e1cf2a540d1 100644 --- a/src/plugins/data/public/search/aggs/param_types/base.ts +++ b/src/plugins/data/public/search/aggs/param_types/base.ts @@ -82,7 +82,7 @@ export class BaseParamType { this.toExpressionAst = config.toExpressionAst; this.options = config.options; this.modifyAggConfigOnSearchRequestStart = - config.modifyAggConfigOnSearchRequestStart || function() {}; + config.modifyAggConfigOnSearchRequestStart || function () {}; this.valueType = config.valueType || config.type; } } diff --git a/src/plugins/data/public/search/aggs/param_types/json.test.ts b/src/plugins/data/public/search/aggs/param_types/json.test.ts index 12fd29b3a1452..82e12d3bd5be6 100644 --- a/src/plugins/data/public/search/aggs/param_types/json.test.ts +++ b/src/plugins/data/public/search/aggs/param_types/json.test.ts @@ -21,7 +21,7 @@ import { BaseParamType } from './base'; import { JsonParamType } from './json'; import { IAggConfig } from '../agg_config'; -describe('JSON', function() { +describe('JSON', function () { const paramName = 'json_test'; let aggConfig: IAggConfig; let output: Record; @@ -33,7 +33,7 @@ describe('JSON', function() { name: paramName, }); - beforeEach(function() { + beforeEach(function () { aggConfig = { params: {} } as IAggConfig; output = { params: {} }; }); diff --git a/src/plugins/data/public/search/aggs/param_types/json.ts b/src/plugins/data/public/search/aggs/param_types/json.ts index bf85b3b890c35..461f3c300c1d3 100644 --- a/src/plugins/data/public/search/aggs/param_types/json.ts +++ b/src/plugins/data/public/search/aggs/param_types/json.ts @@ -49,7 +49,7 @@ export class JsonParamType extends BaseParamType { return _(a) .keys() .union(_.keys(b)) - .transform(function(dest, key) { + .transform(function (dest, key) { const val = compare(a[key], b[key]); if (val !== undefined) dest[key] = val; }, {}) @@ -58,7 +58,7 @@ export class JsonParamType extends BaseParamType { function mergeArrays(a: any, b: any): any { // attempt to merge each value - return _.times(Math.max(a.length, b.length), function(i) { + return _.times(Math.max(a.length, b.length), function (i) { return compare(a[i], b[i]); }); } diff --git a/src/plugins/data/public/search/aggs/param_types/string.test.ts b/src/plugins/data/public/search/aggs/param_types/string.test.ts index 29ec9741611a3..c4afe1d5d1330 100644 --- a/src/plugins/data/public/search/aggs/param_types/string.test.ts +++ b/src/plugins/data/public/search/aggs/param_types/string.test.ts @@ -21,7 +21,7 @@ import { BaseParamType } from './base'; import { StringParamType } from './string'; import { IAggConfig } from '../agg_config'; -describe('String', function() { +describe('String', function () { let paramName = 'json_test'; let aggConfig: IAggConfig; let output: Record; diff --git a/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts b/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts index cb0e37c0296d7..aa27bab8f4bd8 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts @@ -34,7 +34,7 @@ import { * expression functions. */ export const functionWrapper = (spec: T) => { - const defaultArgs = mapValues(spec.args, argSpec => argSpec.default); + const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default); return ( args: T extends ExpressionFunctionDefinition< infer Name, diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 3ff2fbf35ad7e..5549ffd2583b1 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -45,7 +45,7 @@ export function mockAggTypesRegistry | MetricAggTyp const registrySetup = registry.setup(); if (types) { - types.forEach(type => { + types.forEach((type) => { if (type instanceof BucketAggType) { registrySetup.registerBucket(type); } else if (type instanceof MetricAggType) { @@ -68,8 +68,8 @@ export function mockAggTypesRegistry | MetricAggTyp } as unknown) as InternalStartServices), }); - aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); - aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); + aggTypes.buckets.forEach((type) => registrySetup.registerBucket(type)); + aggTypes.metrics.forEach((type) => registrySetup.registerMetric(type)); } return registry.start(); diff --git a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts b/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts index 9d976784329cc..30fcdd9d83a38 100644 --- a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts +++ b/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts @@ -19,7 +19,7 @@ import moment from 'moment'; import { IUiSettingsClient } from 'src/core/public'; import { TimeBuckets } from '../buckets/lib/time_buckets'; -import { toAbsoluteDates, TimeRange } from '../../../../common'; +import { toAbsoluteDates, TimeRange, UI_SETTINGS } from '../../../../common'; export function getCalculateAutoTimeExpression(uiSettings: IUiSettingsClient) { return function calculateAutoTimeExpression(range: TimeRange) { @@ -29,8 +29,8 @@ export function getCalculateAutoTimeExpression(uiSettings: IUiSettingsClient) { } const buckets = new TimeBuckets({ - 'histogram:maxBars': uiSettings.get('histogram:maxBars'), - 'histogram:barTarget': uiSettings.get('histogram:barTarget'), + 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), dateFormat: uiSettings.get('dateFormat'), 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), }); diff --git a/src/plugins/data/public/search/aggs/utils/prop_filter.ts b/src/plugins/data/public/search/aggs/utils/prop_filter.ts index 01e98a68d3949..cad5c437fc896 100644 --- a/src/plugins/data/public/search/aggs/utils/prop_filter.ts +++ b/src/plugins/data/public/search/aggs/utils/prop_filter.ts @@ -44,7 +44,7 @@ export function propFilter

(prop: P) { filters: string[] | string | FilterFunc = [] ): T[] { if (isFunction(filters)) { - return list.filter(item => (filters as FilterFunc)(item[prop])); + return list.filter((item) => (filters as FilterFunc)(item[prop])); } if (!Array.isArray(filters)) { @@ -75,7 +75,7 @@ export function propFilter

(prop: P) { return acc; }, {} as { [type: string]: string[] }); - return list.filter(item => { + return list.filter((item) => { const value = item[prop]; const excluded = options.exclude && options.exclude.includes(value); diff --git a/src/plugins/data/public/search/es_search/get_es_preference.test.ts b/src/plugins/data/public/search/es_search/get_es_preference.test.ts index 8b8156b4519d6..05a74b3e6205a 100644 --- a/src/plugins/data/public/search/es_search/get_es_preference.test.ts +++ b/src/plugins/data/public/search/es_search/get_es_preference.test.ts @@ -20,6 +20,7 @@ import { getEsPreference } from './get_es_preference'; import { CoreStart } from '../../../../../core/public'; import { coreMock } from '../../../../../core/public/mocks'; +import { UI_SETTINGS } from '../../../common'; describe('Get ES preference', () => { let mockCoreStart: MockedKeys; @@ -30,8 +31,8 @@ describe('Get ES preference', () => { test('returns the session ID if set to sessionId', () => { mockCoreStart.uiSettings.get.mockImplementation((key: string) => { - if (key === 'courier:setRequestPreference') return 'sessionId'; - if (key === 'courier:customRequestPreference') return 'foobar'; + if (key === UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE) return 'sessionId'; + if (key === UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) return 'foobar'; }); const preference = getEsPreference(mockCoreStart.uiSettings, 'my_session_id'); expect(preference).toBe('my_session_id'); @@ -39,8 +40,8 @@ describe('Get ES preference', () => { test('returns the custom preference if set to custom', () => { mockCoreStart.uiSettings.get.mockImplementation((key: string) => { - if (key === 'courier:setRequestPreference') return 'custom'; - if (key === 'courier:customRequestPreference') return 'foobar'; + if (key === UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE) return 'custom'; + if (key === UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) return 'foobar'; }); const preference = getEsPreference(mockCoreStart.uiSettings); expect(preference).toBe('foobar'); @@ -48,8 +49,8 @@ describe('Get ES preference', () => { test('returns undefined if set to none', () => { mockCoreStart.uiSettings.get.mockImplementation((key: string) => { - if (key === 'courier:setRequestPreference') return 'none'; - if (key === 'courier:customRequestPreference') return 'foobar'; + if (key === UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE) return 'none'; + if (key === UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) return 'foobar'; }); const preference = getEsPreference(mockCoreStart.uiSettings); expect(preference).toBe(undefined); diff --git a/src/plugins/data/public/search/es_search/get_es_preference.ts b/src/plugins/data/public/search/es_search/get_es_preference.ts index 3f1c2b9b3b736..5e40712067bb0 100644 --- a/src/plugins/data/public/search/es_search/get_es_preference.ts +++ b/src/plugins/data/public/search/es_search/get_es_preference.ts @@ -18,12 +18,13 @@ */ import { IUiSettingsClient } from '../../../../../core/public'; +import { UI_SETTINGS } from '../../../common'; const defaultSessionId = `${Date.now()}`; export function getEsPreference(uiSettings: IUiSettingsClient, sessionId = defaultSessionId) { - const setPreference = uiSettings.get('courier:setRequestPreference'); + const setPreference = uiSettings.get(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE); if (setPreference === 'sessionId') return `${sessionId}`; - const customPreference = uiSettings.get('courier:customRequestPreference'); + const customPreference = uiSettings.get(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE); return setPreference === 'custom' ? customPreference : undefined; } diff --git a/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts b/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts index 89a46db27e894..906b8862d00aa 100644 --- a/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts +++ b/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts @@ -40,8 +40,8 @@ export async function buildTabularInspectorData( table: TabbedTable, queryFilter: { addFilters: (filter: any) => void } ) { - const aggConfigs = table.columns.map(column => column.aggConfig); - const rows = table.rows.map(row => { + const aggConfigs = table.columns.map((column) => column.aggConfig); + const rows = table.rows.map((row) => { return table.columns.reduce>((prev, cur, colIndex) => { const value = row[cur.id]; const fieldFormatter = cur.aggConfig.fieldFormatter('text'); @@ -60,7 +60,7 @@ export async function buildTabularInspectorData( isCellContentFilterable && ((value: { raw: unknown }) => { const rowIndex = rows.findIndex( - row => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw + (row) => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw ); const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw); @@ -72,7 +72,7 @@ export async function buildTabularInspectorData( isCellContentFilterable && ((value: { raw: unknown }) => { const rowIndex = rows.findIndex( - row => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw + (row) => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw ); const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw); @@ -80,7 +80,7 @@ export async function buildTabularInspectorData( const notOther = value.raw !== '__other__'; const notMissing = value.raw !== '__missing__'; if (Array.isArray(filter)) { - filter.forEach(f => set(f, 'meta.negate', notOther && notMissing)); + filter.forEach((f) => set(f, 'meta.negate', notOther && notMissing)); } else { set(filter, 'meta.negate', notOther && notMissing); } diff --git a/src/plugins/data/public/search/expressions/create_filter.ts b/src/plugins/data/public/search/expressions/create_filter.ts index 2e2bd435151b6..94d84380e03df 100644 --- a/src/plugins/data/public/search/expressions/create_filter.ts +++ b/src/plugins/data/public/search/expressions/create_filter.ts @@ -27,16 +27,16 @@ const getOtherBucketFilterTerms = (table: TabbedTable, columnIndex: number, rowI } // get only rows where cell value matches current row for all the fields before columnIndex - const rows = table.rows.filter(row => { + const rows = table.rows.filter((row) => { return table.columns.every((column, i) => { return row[column.id] === table.rows[rowIndex][column.id] || i >= columnIndex; }); }); - const terms = rows.map(row => row[table.columns[columnIndex].id]); + const terms = rows.map((row) => row[table.columns[columnIndex].id]); return [ ...new Set( - terms.filter(term => { + terms.filter((term) => { const notOther = term !== '__other__'; const notMissing = term !== '__missing__'; return notOther && notMissing; diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index d6901da99319a..153eb7de6f2de 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -115,7 +115,7 @@ const handleCourierRequest = async ({ }, }); - requestSearchSource.setField('aggs', function() { + requestSearchSource.setField('aggs', function () { return aggs.toDsl(metricsAtAllLevels); }); @@ -134,7 +134,7 @@ const handleCourierRequest = async ({ if (timeRange && allTimeFields.length > 0) { timeFilterSearchSource.setField('filter', () => { return allTimeFields - .map(fieldName => getTime(indexPattern, timeRange, { fieldName })) + .map((fieldName) => getTime(indexPattern, timeRange, { fieldName })) .filter(isRangeFilter); }); } diff --git a/src/plugins/data/public/search/fetch/get_search_params.test.ts b/src/plugins/data/public/search/fetch/get_search_params.test.ts index edf18405e8ff7..f9b62fdd4fc61 100644 --- a/src/plugins/data/public/search/fetch/get_search_params.test.ts +++ b/src/plugins/data/public/search/fetch/get_search_params.test.ts @@ -19,10 +19,11 @@ import { getSearchParams } from './get_search_params'; import { IUiSettingsClient } from 'kibana/public'; +import { UI_SETTINGS } from '../../../common'; function getConfigStub(config: any = {}) { return { - get: key => config[key], + get: (key) => config[key], } as IUiSettingsClient; } @@ -40,21 +41,21 @@ describe('getSearchParams', () => { }); test('includes ignore_throttled according to search:includeFrozen', () => { - let config = getConfigStub({ 'search:includeFrozen': true }); + let config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true }); let searchParams = getSearchParams(config); expect(searchParams.ignore_throttled).toBe(false); - config = getConfigStub({ 'search:includeFrozen': false }); + config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false }); searchParams = getSearchParams(config); expect(searchParams.ignore_throttled).toBe(true); }); test('includes max_concurrent_shard_requests according to courier:maxConcurrentShardRequests', () => { - let config = getConfigStub({ 'courier:maxConcurrentShardRequests': 0 }); + let config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 0 }); let searchParams = getSearchParams(config); expect(searchParams.max_concurrent_shard_requests).toBe(undefined); - config = getConfigStub({ 'courier:maxConcurrentShardRequests': 5 }); + config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5 }); searchParams = getSearchParams(config); expect(searchParams.max_concurrent_shard_requests).toBe(5); }); diff --git a/src/plugins/data/public/search/fetch/get_search_params.ts b/src/plugins/data/public/search/fetch/get_search_params.ts index f0c43bd2e74cd..60bdc9ed6473a 100644 --- a/src/plugins/data/public/search/fetch/get_search_params.ts +++ b/src/plugins/data/public/search/fetch/get_search_params.ts @@ -18,6 +18,7 @@ */ import { IUiSettingsClient } from 'kibana/public'; +import { UI_SETTINGS } from '../../../common'; const sessionId = Date.now(); @@ -33,19 +34,19 @@ export function getSearchParams(config: IUiSettingsClient, esShardTimeout: numbe } export function getIgnoreThrottled(config: IUiSettingsClient) { - return !config.get('search:includeFrozen'); + return !config.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); } export function getMaxConcurrentShardRequests(config: IUiSettingsClient) { - const maxConcurrentShardRequests = config.get('courier:maxConcurrentShardRequests'); + const maxConcurrentShardRequests = config.get(UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS); return maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined; } export function getPreference(config: IUiSettingsClient) { - const setRequestPreference = config.get('courier:setRequestPreference'); + const setRequestPreference = config.get(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE); if (setRequestPreference === 'sessionId') return sessionId; return setRequestPreference === 'custom' - ? config.get('courier:customRequestPreference') + ? config.get(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) : undefined; } diff --git a/src/plugins/data/public/search/legacy/call_client.test.ts b/src/plugins/data/public/search/legacy/call_client.test.ts index 9a2a9dc222e7e..a3c4e720b4cab 100644 --- a/src/plugins/data/public/search/legacy/call_client.test.ts +++ b/src/plugins/data/public/search/legacy/call_client.test.ts @@ -34,7 +34,7 @@ jest.mock('./default_search_strategy', () => { search: jest.fn(({ searchRequests }: SearchStrategySearchParams) => { return { searching: Promise.resolve( - searchRequests.map(req => { + searchRequests.map((req) => { return { id: req._searchStrategyId, }; diff --git a/src/plugins/data/public/search/legacy/call_client.ts b/src/plugins/data/public/search/legacy/call_client.ts index c484c46aa4879..4b12f517daf78 100644 --- a/src/plugins/data/public/search/legacy/call_client.ts +++ b/src/plugins/data/public/search/legacy/call_client.ts @@ -40,10 +40,10 @@ export function callClient( }); searchRequests.forEach((request, i) => { - const response = searching.then(results => handleResponse(request, results[i])); + const response = searching.then((results) => handleResponse(request, results[i])); const { abortSignal = null } = requestOptionsMap.get(request) || {}; if (abortSignal) abortSignal.addEventListener('abort', abort); requestResponseMap.set(request, response); }); - return searchRequests.map(request => requestResponseMap.get(request)); + return searchRequests.map((request) => requestResponseMap.get(request)); } diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts index 9e3d65a69bf02..436b522744622 100644 --- a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts +++ b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts @@ -21,12 +21,13 @@ import { IUiSettingsClient } from 'kibana/public'; import { defaultSearchStrategy } from './default_search_strategy'; import { searchStartMock } from '../mocks'; import { SearchStrategySearchParams } from './types'; +import { UI_SETTINGS } from '../../../common'; const { search } = defaultSearchStrategy; function getConfigStub(config: any = {}) { return { - get: key => config[key], + get: (key) => config[key], } as IUiSettingsClient; } @@ -38,8 +39,8 @@ const searchMockResponse: any = Promise.resolve([]); searchMockResponse.abort = jest.fn(); const searchMock = jest.fn().mockReturnValue(searchMockResponse); -describe('defaultSearchStrategy', function() { - describe('search', function() { +describe('defaultSearchStrategy', function () { + describe('search', function () { let searchArgs: MockedKeys>; let es: any; @@ -69,30 +70,30 @@ describe('defaultSearchStrategy', function() { }); test('does not send max_concurrent_shard_requests by default', async () => { - const config = getConfigStub({ 'courier:batchSearches': true }); + const config = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); await search({ ...searchArgs, config }); expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined); }); test('allows configuration of max_concurrent_shard_requests', async () => { const config = getConfigStub({ - 'courier:batchSearches': true, - 'courier:maxConcurrentShardRequests': 42, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, + [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 42, }); await search({ ...searchArgs, config }); expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42); }); test('should set rest_total_hits_as_int to true on a request', async () => { - const config = getConfigStub({ 'courier:batchSearches': true }); + const config = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); await search({ ...searchArgs, config }); expect(es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true); }); test('should set ignore_throttled=false when including frozen indices', async () => { const config = getConfigStub({ - 'courier:batchSearches': true, - 'search:includeFrozen': true, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, + [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true, }); await search({ ...searchArgs, config }); expect(es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false); @@ -100,7 +101,7 @@ describe('defaultSearchStrategy', function() { test('should properly call abort with msearch', () => { const config = getConfigStub({ - 'courier:batchSearches': true, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, }); search({ ...searchArgs, config }).abort(); expect(msearchMockResponse.abort).toHaveBeenCalled(); diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.ts b/src/plugins/data/public/search/legacy/default_search_strategy.ts index 3216803dcbfa2..284768bc5a1cc 100644 --- a/src/plugins/data/public/search/legacy/default_search_strategy.ts +++ b/src/plugins/data/public/search/legacy/default_search_strategy.ts @@ -25,7 +25,7 @@ import { SearchStrategyProvider, SearchStrategySearchParams } from './types'; export const defaultSearchStrategy: SearchStrategyProvider = { id: 'default', - search: params => { + search: (params) => { return msearch(params); }, }; diff --git a/src/plugins/data/public/search/legacy/fetch_soon.test.ts b/src/plugins/data/public/search/legacy/fetch_soon.test.ts index 6c0467e3297e8..61d3568350b6b 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.test.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.test.ts @@ -22,10 +22,11 @@ import { callClient } from './call_client'; import { IUiSettingsClient } from 'kibana/public'; import { FetchHandlers, FetchOptions } from '../fetch/types'; import { SearchRequest, SearchResponse } from '../index'; +import { UI_SETTINGS } from '../../../common'; function getConfigStub(config: any = {}) { return { - get: key => config[key], + get: (key) => config[key], } as IUiSettingsClient; } @@ -41,9 +42,9 @@ jest.mock('./call_client', () => ({ callClient: jest.fn((requests: SearchRequest[]) => { // Allow a request object to specify which mockResponse it wants to receive (_mockResponseId) // in addition to how long to simulate waiting before returning a response (_waitMs) - const responses = requests.map(request => { + const responses = requests.map((request) => { const waitMs = requests.reduce((total, { _waitMs }) => total + _waitMs || 0, 0); - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { resolve(mockResponses[request._mockResponseId]); }, waitMs); @@ -60,7 +61,7 @@ describe('fetchSoon', () => { test('should execute asap if config is set to not batch searches', () => { const config = getConfigStub({ - 'courier:batchSearches': false, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false, }); const request = {}; const options = {}; @@ -72,7 +73,7 @@ describe('fetchSoon', () => { test('should delay by 50ms if config is set to batch searches', () => { const config = getConfigStub({ - 'courier:batchSearches': true, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, }); const request = {}; const options = {}; @@ -88,7 +89,7 @@ describe('fetchSoon', () => { test('should send a batch of requests to callClient', () => { const config = getConfigStub({ - 'courier:batchSearches': true, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, }); const requests = [{ foo: 1 }, { foo: 2 }]; const options = [{ bar: 1 }, { bar: 2 }]; @@ -105,11 +106,11 @@ describe('fetchSoon', () => { test('should return the response to the corresponding call for multiple batched requests', async () => { const config = getConfigStub({ - 'courier:batchSearches': true, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, }); const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }]; - const promises = requests.map(request => { + const promises = requests.map((request) => { return fetchSoon(request, {}, { config } as FetchHandlers); }); jest.advanceTimersByTime(50); @@ -120,16 +121,16 @@ describe('fetchSoon', () => { test('should wait for the previous batch to start before starting a new batch', () => { const config = getConfigStub({ - 'courier:batchSearches': true, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, }); const firstBatch = [{ foo: 1 }, { foo: 2 }]; const secondBatch = [{ bar: 1 }, { bar: 2 }]; - firstBatch.forEach(request => { + firstBatch.forEach((request) => { fetchSoon(request, {}, { config } as FetchHandlers); }); jest.advanceTimersByTime(50); - secondBatch.forEach(request => { + secondBatch.forEach((request) => { fetchSoon(request, {}, { config } as FetchHandlers); }); diff --git a/src/plugins/data/public/search/legacy/fetch_soon.ts b/src/plugins/data/public/search/legacy/fetch_soon.ts index 83617d394fe95..fed2c52bc491f 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.ts @@ -20,6 +20,7 @@ import { callClient } from './call_client'; import { FetchHandlers, FetchOptions } from '../fetch/types'; import { SearchRequest, SearchResponse } from '../index'; +import { UI_SETTINGS } from '../../../common'; /** * This function introduces a slight delay in the request process to allow multiple requests to queue @@ -30,7 +31,7 @@ export async function fetchSoon( options: FetchOptions, fetchHandlers: FetchHandlers ) { - const msToDelay = fetchHandlers.config.get('courier:batchSearches') ? 50 : 0; + const msToDelay = fetchHandlers.config.get(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0; return delayedFetch(request, options, fetchHandlers, msToDelay); } @@ -42,7 +43,7 @@ export async function fetchSoon( * @return Promise A promise that resolves with the result of executing the function */ function delay(fn: Function, ms: number) { - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => resolve(fn()), ms); }); } diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts index 9f16d5b408178..dc61e19406631 100644 --- a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts +++ b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts @@ -19,10 +19,11 @@ import { getMSearchParams } from './get_msearch_params'; import { IUiSettingsClient } from '../../../../../core/public'; +import { UI_SETTINGS } from '../../../common'; function getConfigStub(config: any = {}) { return { - get: key => config[key], + get: (key) => config[key], } as IUiSettingsClient; } @@ -34,29 +35,29 @@ describe('getMSearchParams', () => { }); test('includes ignore_throttled according to search:includeFrozen', () => { - let config = getConfigStub({ 'search:includeFrozen': true }); + let config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true }); let msearchParams = getMSearchParams(config); expect(msearchParams.ignore_throttled).toBe(false); - config = getConfigStub({ 'search:includeFrozen': false }); + config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false }); msearchParams = getMSearchParams(config); expect(msearchParams.ignore_throttled).toBe(true); }); test('includes max_concurrent_shard_requests according to courier:maxConcurrentShardRequests if greater than 0', () => { - let config = getConfigStub({ 'courier:maxConcurrentShardRequests': 0 }); + let config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 0 }); let msearchParams = getMSearchParams(config); expect(msearchParams.max_concurrent_shard_requests).toBe(undefined); - config = getConfigStub({ 'courier:maxConcurrentShardRequests': 5 }); + config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5 }); msearchParams = getMSearchParams(config); expect(msearchParams.max_concurrent_shard_requests).toBe(5); }); test('does not include other search params that are included in the msearch header or body', () => { const config = getConfigStub({ - 'search:includeFrozen': false, - 'courier:maxConcurrentShardRequests': 5, + [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false, + [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5, }); const msearchParams = getMSearchParams(config); expect(msearchParams.hasOwnProperty('ignore_unavailable')).toBe(false); diff --git a/src/plugins/data/public/search/long_query_notification.tsx b/src/plugins/data/public/search/long_query_notification.tsx index 0bdf8ab7c66f8..1db298618fae8 100644 --- a/src/plugins/data/public/search/long_query_notification.tsx +++ b/src/plugins/data/public/search/long_query_notification.tsx @@ -44,7 +44,7 @@ export function LongQueryNotification(props: Props) { { - await props.application.navigateToApp('kibana#/management/stack/license_management'); + await props.application.navigateToApp('management/stack/license_management'); }} > count === 0)) + .pipe(filter((count) => count === 0)) .subscribe(this.hideToast); } diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index b69b3261d4a5e..1615aac9e7b7d 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -102,12 +102,12 @@ export class SearchService implements Plugin { uiSettings: core.uiSettings, getInternalStartServices, }); - aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); - aggTypes.metrics.forEach(m => aggTypesSetup.registerMetric(m)); + aggTypes.buckets.forEach((b) => aggTypesSetup.registerBucket(b)); + aggTypes.metrics.forEach((m) => aggTypesSetup.registerMetric(m)); // register expression functions for each agg type const aggFunctions = getAggTypesFunctions(); - aggFunctions.forEach(fn => expressions.registerFunction(fn)); + aggFunctions.forEach((fn) => expressions.registerFunction(fn)); return { aggs: { diff --git a/src/plugins/data/public/search/search_source/filter_docvalue_fields.ts b/src/plugins/data/public/search/search_source/filter_docvalue_fields.ts index 917d26f0decd1..bbac30d7dfdc5 100644 --- a/src/plugins/data/public/search/search_source/filter_docvalue_fields.ts +++ b/src/plugins/data/public/search/search_source/filter_docvalue_fields.ts @@ -26,7 +26,7 @@ export function filterDocvalueFields( docvalueFields: Array, fields: string[] ) { - return docvalueFields.filter(docValue => { + return docvalueFields.filter((docValue) => { const docvalueFieldName = typeof docValue === 'string' ? docValue : docValue.field; return fields.includes(docvalueFieldName); }); diff --git a/src/plugins/data/public/search/search_source/inject_references.ts b/src/plugins/data/public/search/search_source/inject_references.ts index a567c33d2280b..07f37c3c11275 100644 --- a/src/plugins/data/public/search/search_source/inject_references.ts +++ b/src/plugins/data/public/search/search_source/inject_references.ts @@ -27,7 +27,7 @@ export const injectReferences = ( const searchSourceReturnFields: SearchSourceFields = { ...searchSourceFields }; // Inject index id if a reference is saved if (searchSourceFields.indexRefName) { - const reference = references.find(ref => ref.name === searchSourceFields.indexRefName); + const reference = references.find((ref) => ref.name === searchSourceFields.indexRefName); if (!reference) { throw new Error(`Could not find reference for ${searchSourceFields.indexRefName}`); } diff --git a/src/plugins/data/public/search/search_source/normalize_sort_request.test.ts b/src/plugins/data/public/search/search_source/normalize_sort_request.test.ts index 13a6167544b5e..d47aab80ee0bc 100644 --- a/src/plugins/data/public/search/search_source/normalize_sort_request.test.ts +++ b/src/plugins/data/public/search/search_source/normalize_sort_request.test.ts @@ -21,7 +21,7 @@ import { normalizeSortRequest } from './normalize_sort_request'; import { SortDirection } from './types'; import { IIndexPattern } from '../..'; -describe('SearchSource#normalizeSortRequest', function() { +describe('SearchSource#normalizeSortRequest', function () { const scriptedField = { name: 'script string', type: 'number', @@ -40,7 +40,7 @@ describe('SearchSource#normalizeSortRequest', function() { fields: [scriptedField, murmurScriptedField], } as IIndexPattern; - it('should return an array', function() { + it('should return an array', function () { const sortable = { someField: SortDirection.desc }; const result = normalizeSortRequest(sortable, indexPattern); expect(result).toEqual([ @@ -55,7 +55,7 @@ describe('SearchSource#normalizeSortRequest', function() { expect(sortable).toEqual({ someField: SortDirection.desc }); }); - it('should make plain string sort into the more verbose format', function() { + it('should make plain string sort into the more verbose format', function () { const result = normalizeSortRequest([{ someField: SortDirection.desc }], indexPattern); expect(result).toEqual([ { @@ -66,7 +66,7 @@ describe('SearchSource#normalizeSortRequest', function() { ]); }); - it('should append default sort options', function() { + it('should append default sort options', function () { const defaultSortOptions = { unmapped_type: 'boolean', }; @@ -85,7 +85,7 @@ describe('SearchSource#normalizeSortRequest', function() { ]); }); - it('should enable script based sorting', function() { + it('should enable script based sorting', function () { const result = normalizeSortRequest( { [scriptedField.name]: SortDirection.desc, @@ -106,7 +106,7 @@ describe('SearchSource#normalizeSortRequest', function() { ]); }); - it('should use script based sorting only on sortable types', function() { + it('should use script based sorting only on sortable types', function () { const result = normalizeSortRequest( [ { @@ -125,7 +125,7 @@ describe('SearchSource#normalizeSortRequest', function() { ]); }); - it('should remove unmapped_type parameter from _score sorting', function() { + it('should remove unmapped_type parameter from _score sorting', function () { const result = normalizeSortRequest({ _score: SortDirection.desc }, indexPattern, { unmapped_type: 'boolean', }); diff --git a/src/plugins/data/public/search/search_source/normalize_sort_request.ts b/src/plugins/data/public/search/search_source/normalize_sort_request.ts index 9e36d2e416f03..9a0cf371ce81d 100644 --- a/src/plugins/data/public/search/search_source/normalize_sort_request.ts +++ b/src/plugins/data/public/search/search_source/normalize_sort_request.ts @@ -26,7 +26,7 @@ export function normalizeSortRequest( defaultSortOptions: SortOptions = {} ) { const sortArray: EsQuerySortValue[] = Array.isArray(sortObject) ? sortObject : [sortObject]; - return sortArray.map(function(sortable) { + return sortArray.map(function (sortable) { return normalize(sortable, indexPattern, defaultSortOptions); }); } diff --git a/src/plugins/data/public/search/search_source/search_source.test.ts b/src/plugins/data/public/search/search_source/search_source.test.ts index 7783e65889a12..6d53b8dfc4b4e 100644 --- a/src/plugins/data/public/search/search_source/search_source.test.ts +++ b/src/plugins/data/public/search/search_source/search_source.test.ts @@ -58,7 +58,7 @@ describe('SearchSource', () => { const data = dataPluginMock.createStartContract(); mockSearchMethod = jest.fn(() => { - return new Observable(subscriber => { + return new Observable((subscriber) => { setTimeout(() => { subscriber.next({ rawResponse: '', diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index 9fdef5e1f3eb0..b926739112e0e 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -75,12 +75,11 @@ import { CoreStart } from 'kibana/public'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/public'; -import { META_FIELDS_SETTING, DOC_HIGHLIGHT_SETTING } from '../../../common'; import { IIndexPattern, ISearchGeneric, SearchRequest } from '../..'; import { SearchSourceOptions, SearchSourceFields } from './types'; import { FetchOptions, RequestFailure, getSearchParams, handleResponse } from '../fetch'; -import { getEsQueryConfig, buildEsQuery, Filter } from '../../../common'; +import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; import { fetchSoon } from '../legacy'; import { extractReferences } from './extract_references'; @@ -251,7 +250,7 @@ export class SearchSource { this.history = [searchRequest]; let response; - if (uiSettings.get('courier:batchSearches')) { + if (uiSettings.get(UI_SETTINGS.COURIER_BATCH_SEARCHES)) { response = await this.legacyFetch(searchRequest, options); } else { response = this.fetch$(searchRequest, options.abortSignal).toPromise(); @@ -309,7 +308,7 @@ export class SearchSource { } } - return Promise.all(handlers.map(fn => fn(this, options))); + return Promise.all(handlers.map((fn) => fn(this, options))); } /** @@ -365,7 +364,7 @@ export class SearchSource { const sort = normalizeSortRequest( val, this.getField('index'), - uiSettings.get('sort:options') + uiSettings.get(UI_SETTINGS.SORT_OPTIONS) ); return addToBody(key, sort); default: @@ -425,7 +424,7 @@ export class SearchSource { // exclude source fields for this index pattern specified by the user const filter = fieldWildcardFilter( body._source.excludes, - uiSettings.get(META_FIELDS_SETTING) + uiSettings.get(UI_SETTINGS.META_FIELDS) ); body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) => filter(docvalueField.field) @@ -448,7 +447,7 @@ export class SearchSource { body.query = buildEsQuery(index, query, filters, esQueryConfigs); if (highlightAll && body.query) { - body.highlight = getHighlightRequest(body.query, uiSettings.get(DOC_HIGHLIGHT_SETTING)); + body.highlight = getHighlightRequest(body.query, uiSettings.get(UI_SETTINGS.DOC_HIGHLIGHT)); delete searchRequest.highlightAll; } diff --git a/src/plugins/data/public/search/sync_search_strategy.test.ts b/src/plugins/data/public/search/sync_search_strategy.test.ts index 31a1adfa01c75..6197980314b86 100644 --- a/src/plugins/data/public/search/sync_search_strategy.test.ts +++ b/src/plugins/data/public/search/sync_search_strategy.test.ts @@ -61,7 +61,7 @@ describe('Sync search strategy', () => { const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; - loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); + loadingCount$.subscribe((value) => receivedLoadingCountValues.push(value)); await syncSearch.search(request, {}).toPromise(); @@ -82,7 +82,7 @@ describe('Sync search strategy', () => { const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; - loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); + loadingCount$.subscribe((value) => receivedLoadingCountValues.push(value)); try { await syncSearch.search(request, {}).toPromise(); diff --git a/src/plugins/data/public/search/tabify/buckets.test.ts b/src/plugins/data/public/search/tabify/buckets.test.ts index 81d9f3d5ca3fd..dfd9462d87b68 100644 --- a/src/plugins/data/public/search/tabify/buckets.test.ts +++ b/src/plugins/data/public/search/tabify/buckets.test.ts @@ -86,7 +86,7 @@ describe('Buckets wrapper', () => { expect(buckets).toHaveLength(2); - buckets._keys.forEach(key => { + buckets._keys.forEach((key) => { expect(typeof key).toBe('string'); }); }); @@ -116,7 +116,7 @@ describe('Buckets wrapper', () => { expect(buckets).toHaveLength(2); - buckets._keys.forEach(key => { + buckets._keys.forEach((key) => { expect(typeof key).toBe('string'); }); }); @@ -141,7 +141,7 @@ describe('Buckets wrapper', () => { expect(buckets).toHaveLength(1); - buckets._keys.forEach(key => { + buckets._keys.forEach((key) => { expect(typeof key).toBe('string'); }); }); diff --git a/src/plugins/data/public/search/tabify/buckets.ts b/src/plugins/data/public/search/tabify/buckets.ts index cd52a09caeaad..e6e5ba4e68ea3 100644 --- a/src/plugins/data/public/search/tabify/buckets.ts +++ b/src/plugins/data/public/search/tabify/buckets.ts @@ -68,7 +68,7 @@ export class TabifyBuckets { const buckets = this.buckets; if (this.objectMode) { - this._keys.forEach(key => { + this._keys.forEach((key) => { fn(buckets[key], key); }); } else { diff --git a/src/plugins/data/public/search/tabify/get_columns.test.ts b/src/plugins/data/public/search/tabify/get_columns.test.ts index 1072e9318b40e..b4498aca4079c 100644 --- a/src/plugins/data/public/search/tabify/get_columns.test.ts +++ b/src/plugins/data/public/search/tabify/get_columns.test.ts @@ -159,7 +159,7 @@ describe('get columns', () => { false ); - expect(columns.map(c => c.name)).toEqual([ + expect(columns.map((c) => c.name)).toEqual([ '@timestamp per 20 seconds', 'Sum of @timestamp', '@timestamp per 10 seconds', diff --git a/src/plugins/data/public/search/tabify/get_columns.ts b/src/plugins/data/public/search/tabify/get_columns.ts index ee8c636fb2e86..8c538288d2fea 100644 --- a/src/plugins/data/public/search/tabify/get_columns.ts +++ b/src/plugins/data/public/search/tabify/get_columns.ts @@ -45,7 +45,7 @@ export function tabifyGetColumns(aggs: IAggConfig[], minimalColumns: boolean): T const columns: TabbedAggColumn[] = []; // separate the metrics - const grouped = groupBy(aggs, agg => { + const grouped = groupBy(aggs, (agg) => { return agg.type.type; }); @@ -56,9 +56,9 @@ export function tabifyGetColumns(aggs: IAggConfig[], minimalColumns: boolean): T let columnIndex = 0; // return the buckets, and after each place all of the metrics - grouped.buckets.forEach(agg => { + grouped.buckets.forEach((agg) => { columns.push(getColumn(agg, columnIndex++)); - grouped.metrics.forEach(metric => { + grouped.metrics.forEach((metric) => { columns.push(getColumn(metric, columnIndex++)); }); }); diff --git a/src/plugins/data/public/search/tabify/response_writer.ts b/src/plugins/data/public/search/tabify/response_writer.ts index 02e18d75ae6e5..da9b59cc92791 100644 --- a/src/plugins/data/public/search/tabify/response_writer.ts +++ b/src/plugins/data/public/search/tabify/response_writer.ts @@ -61,15 +61,15 @@ export class TabbedAggResponseWriter { row() { const rowBuffer: TabbedAggRow = {}; - this.bucketBuffer.forEach(bucket => { + this.bucketBuffer.forEach((bucket) => { rowBuffer[bucket.id] = bucket.value; }); - this.metricBuffer.forEach(metric => { + this.metricBuffer.forEach((metric) => { rowBuffer[metric.id] = metric.value; }); - const isPartialRow = !this.columns.every(column => rowBuffer.hasOwnProperty(column.id)); + const isPartialRow = !this.columns.every((column) => rowBuffer.hasOwnProperty(column.id)); const removePartial = isPartialRow && !this.partialRows; if (!isEmpty(rowBuffer) && !removePartial) { this.rows.push(rowBuffer); diff --git a/src/plugins/data/public/search/tabify/tabify.test.ts b/src/plugins/data/public/search/tabify/tabify.test.ts index 63685cc87f5cf..f6691360cec31 100644 --- a/src/plugins/data/public/search/tabify/tabify.test.ts +++ b/src/plugins/data/public/search/tabify/tabify.test.ts @@ -142,7 +142,7 @@ describe('tabifyAggResponse Integration', () => { expectColumns(tabbed, [ext, src, os, avg]); - tabbed.rows.forEach(row => { + tabbed.rows.forEach((row) => { expectRow(row, [expectExtension, expectCountry, expectOS, expectAvgBytes]); }); }); @@ -152,7 +152,7 @@ describe('tabifyAggResponse Integration', () => { expectColumns(tabbed, [ext, avg, src, avg, os, avg]); - tabbed.rows.forEach(row => { + tabbed.rows.forEach((row) => { expectRow(row, [ expectExtension, expectAvgBytes, diff --git a/src/plugins/data/public/stubs.ts b/src/plugins/data/public/stubs.ts index d2519716dd83e..1b44d35addb66 100644 --- a/src/plugins/data/public/stubs.ts +++ b/src/plugins/data/public/stubs.ts @@ -17,6 +17,4 @@ * under the License. */ -export { stubIndexPattern, stubIndexPatternWithFields } from './index_patterns/index_pattern.stub'; -export { stubFields } from './index_patterns/field.stub'; -export * from '../common/es_query/filters/stubs'; +export * from '../common/stubs'; diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss index 24adf0093af95..73ec14de82b43 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss @@ -32,15 +32,26 @@ font-style: italic; } -.globalFilterItem-isInvalid { +.globalFilterItem-isError, .globalFilterItem-isWarning { text-decoration: none; .globalFilterLabel__value { - color: $euiColorDanger; font-weight: $euiFontWeightBold; } } +.globalFilterItem-isError { + .globalFilterLabel__value { + color: makeHighContrastColor($euiColorDangerText, $euiColorLightShade); + } +} + +.globalFilterItem-isWarning { + .globalFilterLabel__value { + color: makeHighContrastColor($euiColorWarningText, $euiColorLightShade); + } +} + .globalFilterItem-isPinned { position: relative; diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index 6852152d059be..43dba150bf8d4 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -36,6 +36,7 @@ import { toggleFilterDisabled, toggleFilterNegated, unpinFilter, + UI_SETTINGS, } from '../../../common'; interface Props { @@ -64,8 +65,9 @@ function FilterBarUI(props: Props) { onUpdate(i, newFilter)} + onUpdate={(newFilter) => onUpdate(i, newFilter)} onRemove={() => onRemove(i)} indexPatterns={props.indexPatterns} uiSettings={uiSettings!} @@ -75,7 +77,7 @@ function FilterBarUI(props: Props) { } function renderAddFilter() { - const isPinned = uiSettings!.get('filters:pinnedByDefault'); + const isPinned = uiSettings!.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT); const [indexPattern] = props.indexPatterns; const index = indexPattern && indexPattern.id; const newFilter = buildEmptyFilter(isPinned, index); diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx index a5db8b66caa01..66adbfe9a6fc3 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx @@ -38,12 +38,12 @@ export function GenericComboBox(props: GenericComboBoxProps) { const { options, selectedOptions, getLabel, onChange, ...otherProps } = props; const labels = options.map(getLabel); - const euiOptions: EuiComboBoxOptionOption[] = labels.map(label => ({ label })); + const euiOptions: EuiComboBoxOptionOption[] = labels.map((label) => ({ label })); const selectedEuiOptions = selectedOptions - .filter(option => { + .filter((option) => { return options.indexOf(option) !== -1; }) - .map(option => { + .map((option) => { return euiOptions[options.indexOf(option)]; }); diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx index ffe2a153a87f3..0e2bcc7581950 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx @@ -198,9 +198,14 @@ class FilterEditorUI extends Component { if ( this.props.indexPatterns.length <= 1 && this.props.indexPatterns.find( - indexPattern => indexPattern === this.state.selectedIndexPattern + (indexPattern) => indexPattern === this.getIndexPatternFromFilter() ) ) { + /** + * Don't render the index pattern selector if there's just one \ zero index patterns + * and if the index pattern the filter was LOADED with is in the indexPatterns list. + **/ + return ''; } const { selectedIndexPattern } = this.state; @@ -220,7 +225,7 @@ class FilterEditorUI extends Component { })} options={this.props.indexPatterns} selectedOptions={selectedIndexPattern ? [selectedIndexPattern] : []} - getLabel={indexPattern => indexPattern.title} + getLabel={(indexPattern) => indexPattern.title} onChange={this.onIndexPatternChange} singleSelection={{ asPlainText: true }} isClearable={false} @@ -267,7 +272,7 @@ class FilterEditorUI extends Component { })} options={fields} selectedOptions={selectedField ? [selectedField] : []} - getLabel={field => field.name} + getLabel={(field) => field.name} onChange={this.onFieldChange} singleSelection={{ asPlainText: true }} isClearable={false} diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts index 771743a3e5df2..12cdf13caeb55 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts @@ -115,7 +115,7 @@ describe('Filter editor utils', () => { it('limits the fields to the filterable fields', () => { const fieldOptions = getFilterableFields(stubIndexPattern); - const nonFilterableFields = fieldOptions.filter(field => !field.filterable); + const nonFilterableFields = fieldOptions.filter((field) => !field.filterable); expect(nonFilterableFields.length).toBe(0); }); }); @@ -124,14 +124,14 @@ describe('Filter editor utils', () => { it('returns range for number fields', () => { const [field] = stubFields.filter(({ type }) => type === 'number'); const operatorOptions = getOperatorOptions(field); - const rangeOperator = operatorOptions.find(operator => operator.type === 'range'); + const rangeOperator = operatorOptions.find((operator) => operator.type === 'range'); expect(rangeOperator).not.toBeUndefined(); }); it('does not return range for string fields', () => { const [field] = stubFields.filter(({ type }) => type === 'string'); const operatorOptions = getOperatorOptions(field); - const rangeOperator = operatorOptions.find(operator => operator.type === 'range'); + const rangeOperator = operatorOptions.find((operator) => operator.type === 'range'); expect(rangeOperator).toBeUndefined(); }); }); diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts index beb7714ffcca3..01a664837e704 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts @@ -29,11 +29,11 @@ import { } from '../../../../../common'; export function getFieldFromFilter(filter: FieldFilter, indexPattern: IIndexPattern) { - return indexPattern.fields.find(field => field.name === filter.meta.key); + return indexPattern.fields.find((field) => field.name === filter.meta.key); } export function getOperatorFromFilter(filter: Filter) { - return FILTER_OPERATORS.find(operator => { + return FILTER_OPERATORS.find((operator) => { return filter.meta.type === operator.type && filter.meta.negate === operator.negate; }); } @@ -43,7 +43,7 @@ export function getFilterableFields(indexPattern: IIndexPattern) { } export function getOperatorOptions(field: IFieldType) { - return FILTER_OPERATORS.filter(operator => { + return FILTER_OPERATORS.filter((operator) => { return !operator.fieldTypes || operator.fieldTypes.includes(field.type); }); } @@ -80,7 +80,7 @@ export function isFilterValid( if (!Array.isArray(params) || !params.length) { return false; } - return params.every(phrase => validateParams(phrase, field.type)); + return params.every((phrase) => validateParams(phrase, field.type)); case 'range': if (typeof params !== 'object') { return false; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx index 546365b89d9be..94138f60b52b1 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx @@ -22,6 +22,7 @@ import { debounce } from 'lodash'; import { withKibana, KibanaReactContextValue } from '../../../../../kibana_react/public'; import { IDataPluginServices, IIndexPattern, IFieldType } from '../../..'; +import { UI_SETTINGS } from '../../../../common'; export interface PhraseSuggestorProps { kibana: KibanaReactContextValue; @@ -54,7 +55,9 @@ export class PhraseSuggestorUI extends React.Com } protected isSuggestingValues() { - const shouldSuggestValues = this.services.uiSettings.get('filterEditor:suggestValues'); + const shouldSuggestValues = this.services.uiSettings.get( + UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES + ); const { field } = this.props; return shouldSuggestValues && field && field.aggregatable && field.type === 'string'; } diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_value_input.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_value_input.tsx index b16994cb0057b..ca94970afbafd 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_value_input.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_value_input.tsx @@ -71,7 +71,7 @@ class PhraseValueInputUI extends PhraseSuggestorUI { defaultMessage: 'Select a value', })} options={options} - getLabel={option => option} + getLabel={(option) => option} selectedOptions={value ? [valueAsStr] : []} onChange={([newValue = '']) => onChange(newValue)} onSearchChange={this.onSearchChange} diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrases_values_input.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrases_values_input.tsx index 72f92268f3330..7ca46f60bba5b 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrases_values_input.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrases_values_input.tsx @@ -49,7 +49,7 @@ class PhrasesValuesInputUI extends PhraseSuggestorUI { defaultMessage: 'Select values', })} options={options} - getLabel={option => option} + getLabel={(option) => option} selectedOptions={values || []} onSearchChange={this.onSearchChange} onCreateOption={(option: string) => onChange([...(values || []), option])} diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 528ec4800e7b9..053fca7d5773b 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -18,9 +18,9 @@ */ import { EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { InjectedIntl } from '@kbn/i18n/react'; import classNames from 'classnames'; -import React, { Component, MouseEvent } from 'react'; +import React, { MouseEvent, useState, useEffect } from 'react'; import { IUiSettingsClient } from 'src/core/public'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; @@ -32,8 +32,9 @@ import { toggleFilterNegated, toggleFilterPinned, toggleFilterDisabled, + getIndexPatternFromFilter, } from '../../../common'; -import { getNotifications } from '../../services'; +import { getIndexPatterns } from '../../services'; interface Props { id: string; @@ -46,95 +47,124 @@ interface Props { uiSettings: IUiSettingsClient; } -interface State { - isPopoverOpen: boolean; +interface LabelOptions { + title: string; + status: string; + message?: string; } -class FilterItemUI extends Component { - public state = { - isPopoverOpen: false, - }; +const FILTER_ITEM_OK = ''; +const FILTER_ITEM_WARNING = 'warn'; +const FILTER_ITEM_ERROR = 'error'; - private handleBadgeClick = (e: MouseEvent) => { - if (e.shiftKey) { - this.onToggleDisabled(); +export function FilterItem(props: Props) { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [indexPatternExists, setIndexPatternExists] = useState(undefined); + const { id, filter, indexPatterns } = props; + + useEffect(() => { + const index = props.filter.meta.index; + if (index) { + getIndexPatterns() + .get(index) + .then((indexPattern) => { + setIndexPatternExists(!!indexPattern); + }) + .catch(() => { + setIndexPatternExists(false); + }); } else { - this.togglePopover(); + // Allow filters without an index pattern and don't validate them. + setIndexPatternExists(true); } - }; - public render() { - const { filter, id } = this.props; - const { negate, disabled } = filter.meta; - let hasError: boolean = false; + }, [props.filter.meta.index]); - let valueLabel; - try { - valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); - } catch (e) { - getNotifications().toasts.addError(e, { - title: this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorMessage', - defaultMessage: 'Failed to display filter', - }), - }); - valueLabel = this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorText', - defaultMessage: 'Error', - }); - hasError = true; + function handleBadgeClick(e: MouseEvent) { + if (e.shiftKey) { + onToggleDisabled(); + } else { + setIsPopoverOpen(!isPopoverOpen); } - const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; - const dataTestSubjValue = filter.meta.value ? `filter-value-${valueLabel}` : ''; - const dataTestSubjDisabled = `filter-${ - this.props.filter.meta.disabled ? 'disabled' : 'enabled' - }`; - const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + } + + function onSubmit(f: Filter) { + setIsPopoverOpen(false); + props.onUpdate(f); + } + + function onTogglePinned() { + const f = toggleFilterPinned(filter); + props.onUpdate(f); + } + + function onToggleNegated() { + const f = toggleFilterNegated(filter); + props.onUpdate(f); + } + + function onToggleDisabled() { + const f = toggleFilterDisabled(filter); + props.onUpdate(f); + } + + function isValidLabel(labelConfig: LabelOptions) { + return labelConfig.status === FILTER_ITEM_OK; + } + + function isDisabled(labelConfig: LabelOptions) { + const { disabled } = filter.meta; + return disabled || labelConfig.status === FILTER_ITEM_ERROR; + } - const classes = classNames( + function getClasses(negate: boolean, labelConfig: LabelOptions) { + return classNames( 'globalFilterItem', { - 'globalFilterItem-isDisabled': disabled || hasError, - 'globalFilterItem-isInvalid': hasError, + 'globalFilterItem-isDisabled': isDisabled(labelConfig), + 'globalFilterItem-isError': labelConfig.status === FILTER_ITEM_ERROR, + 'globalFilterItem-isWarning': labelConfig.status === FILTER_ITEM_WARNING, 'globalFilterItem-isPinned': isFilterPinned(filter), 'globalFilterItem-isExcluded': negate, }, - this.props.className + props.className ); + } - const badge = ( - this.props.onRemove()} - onClick={this.handleBadgeClick} - data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`} - /> - ); + function getDataTestSubj(labelConfig: LabelOptions) { + const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; + const dataTestSubjValue = filter.meta.value + ? `filter-value-${isValidLabel(labelConfig) ? labelConfig.title : labelConfig.status}` + : ''; + const dataTestSubjDisabled = `filter-${isDisabled(labelConfig) ? 'disabled' : 'enabled'}`; + const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + return `filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`; + } - const panelTree = [ + function getPanels() { + const { negate, disabled } = filter.meta; + return [ { id: 0, items: [ { name: isFilterPinned(filter) - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.unpinFilterButtonLabel', defaultMessage: 'Unpin', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.pinFilterButtonLabel', defaultMessage: 'Pin across all apps', }), icon: 'pin', onClick: () => { - this.closePopover(); - this.onTogglePinned(); + setIsPopoverOpen(false); + onTogglePinned(); }, 'data-test-subj': 'pinFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.editFilterButtonLabel', defaultMessage: 'Edit filter', }), @@ -144,47 +174,47 @@ class FilterItemUI extends Component { }, { name: negate - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.includeFilterButtonLabel', defaultMessage: 'Include results', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.excludeFilterButtonLabel', defaultMessage: 'Exclude results', }), icon: negate ? 'plusInCircle' : 'minusInCircle', onClick: () => { - this.closePopover(); - this.onToggleNegated(); + setIsPopoverOpen(false); + onToggleNegated(); }, 'data-test-subj': 'negateFilter', }, { name: disabled - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.enableFilterButtonLabel', defaultMessage: 'Re-enable', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.disableFilterButtonLabel', defaultMessage: 'Temporarily disable', }), icon: `${disabled ? 'eye' : 'eyeClosed'}`, onClick: () => { - this.closePopover(); - this.onToggleDisabled(); + setIsPopoverOpen(false); + onToggleDisabled(); }, 'data-test-subj': 'disableFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.deleteFilterButtonLabel', defaultMessage: 'Delete', }), icon: 'trash', onClick: () => { - this.closePopover(); - this.props.onRemove(); + setIsPopoverOpen(false); + props.onRemove(); }, 'data-test-subj': 'deleteFilter', }, @@ -197,63 +227,127 @@ class FilterItemUI extends Component {

{ + setIsPopoverOpen(false); + }} />
), }, ]; - - return ( - - - - ); } - private closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - }; + /** + * Checks if filter field exists in any of the index patterns provided, + * Because if so, a filter for the wrong index pattern may still be applied. + * This function makes this behavior explicit, but it needs to be revised. + */ + function isFilterApplicable() { + // Any filter is applicable if no index patterns were provided to FilterBar. + if (!props.indexPatterns.length) return true; + + const ip = getIndexPatternFromFilter(filter, indexPatterns); + if (ip) return true; - private togglePopover = () => { - this.setState({ - isPopoverOpen: !this.state.isPopoverOpen, + const allFields = indexPatterns.map((indexPattern) => { + return indexPattern.fields.map((field) => field.name); }); - }; + const flatFields = allFields.reduce((acc: string[], it: string[]) => [...acc, ...it], []); + return flatFields.includes(filter.meta?.key || ''); + } - private onSubmit = (filter: Filter) => { - this.closePopover(); - this.props.onUpdate(filter); - }; + function getValueLabel(): LabelOptions { + const label = { + title: '', + message: '', + status: FILTER_ITEM_OK, + }; + if (indexPatternExists === false) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelErrorInfo', + defaultMessage: 'Index pattern {indexPattern} not found', + }, + { + indexPattern: filter.meta.index, + } + ); + } else if (isFilterApplicable()) { + try { + label.title = getDisplayValueFromFilter(filter, indexPatterns); + } catch (e) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = e.message; + } + } else { + label.status = FILTER_ITEM_WARNING; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelWarningText', + defaultMessage: `Warning`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelWarningInfo', + defaultMessage: 'Field {fieldName} does not exist in current view', + }, + { + fieldName: filter.meta.key, + } + ); + } - private onTogglePinned = () => { - const filter = toggleFilterPinned(this.props.filter); - this.props.onUpdate(filter); - }; + return label; + } - private onToggleNegated = () => { - const filter = toggleFilterNegated(this.props.filter); - this.props.onUpdate(filter); - }; + // Don't render until we know if the index pattern is valid + if (indexPatternExists === undefined) return null; + const valueLabelConfig = getValueLabel(); - private onToggleDisabled = () => { - const filter = toggleFilterDisabled(this.props.filter); - this.props.onUpdate(filter); - }; -} + // Disable errored filters and re-render + if (valueLabelConfig.status === FILTER_ITEM_ERROR && !filter.meta.disabled) { + filter.meta.disabled = true; + props.onUpdate(filter); + return null; + } -export const FilterItem = injectI18n(FilterItemUI); + const badge = ( + props.onRemove()} + onClick={handleBadgeClick} + data-test-subj={getDataTestSubj(valueLabelConfig)} + /> + ); + + return ( + { + setIsPopoverOpen(false); + }} + button={badge} + anchorPosition="downLeft" + withTitle={true} + panelPaddingSize="none" + > + + + ); +} diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.tsx index eaa7c291ca00d..3fb7f198d5466 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_options.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_options.tsx @@ -43,7 +43,7 @@ class FilterOptionsUI extends Component { }; public togglePopover = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; diff --git a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx index 6ff261e3cfb8a..f9328875cc910 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx @@ -26,6 +26,7 @@ import { Filter, isFilterPinned } from '../../../../common'; interface Props { filter: Filter; valueLabel: string; + errorMessage?: string; [propName: string]: any; } @@ -34,14 +35,17 @@ export const FilterView: FC = ({ iconOnClick, onClick, valueLabel, + errorMessage, ...rest }: Props) => { const [ref, innerText] = useInnerText(); - let title = i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { - defaultMessage: 'Filter: {innerText}. Select for more filter actions.', - values: { innerText }, - }); + let title = + errorMessage || + i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { + defaultMessage: 'Filter: {innerText}. Select for more filter actions.', + values: { innerText }, + }); if (isFilterPinned(filter)) { title = `${i18n.translate('data.filter.filterBar.pinnedFilterPrefix', { diff --git a/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts b/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts index 1e01d2452ce04..127dc0f1f41d3 100644 --- a/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts +++ b/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts @@ -29,7 +29,7 @@ export async function fetchIndexPatterns( return []; } - const searchString = indexPatternStrings.map(string => `"${string}"`).join(' | '); + const searchString = indexPatternStrings.map((string) => `"${string}"`).join(' | '); const indexPatternsFromSavedObjects = await savedObjectsClient.find({ type: 'index-pattern', fields: ['title', 'fields'], @@ -37,7 +37,7 @@ export async function fetchIndexPatterns( searchFields: ['title'], }); - const exactMatches = indexPatternsFromSavedObjects.savedObjects.filter(savedObject => { + const exactMatches = indexPatternsFromSavedObjects.savedObjects.filter((savedObject) => { return indexPatternStrings.includes(savedObject.attributes.title); }); diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx index f579adbc0c7e2..5f2d4c00cd6b6 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx @@ -28,6 +28,7 @@ import { dataPluginMock } from '../../mocks'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; import { stubIndexPatternWithFields } from '../../stubs'; +import { UI_SETTINGS } from '../../../common'; const startMock = coreMock.createStart(); const mockTimeHistory = { @@ -38,7 +39,7 @@ const mockTimeHistory = { startMock.uiSettings.get.mockImplementation((key: string) => { switch (key) { - case 'timepicker:quickRanges': + case UI_SETTINGS.TIMEPICKER_QUICK_RANGES: return [ { from: 'now/d', @@ -48,7 +49,7 @@ startMock.uiSettings.get.mockImplementation((key: string) => { ]; case 'dateFormat': return 'MMM D, YYYY @ HH:mm:ss.SSS'; - case 'history:limit': + case UI_SETTINGS.HISTORY_LIMIT: return 10; case 'timepicker:timeDefaults': return { diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index 433cb652ee5ce..f65bf97e391e2 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -38,7 +38,7 @@ import { Toast } from 'src/core/public'; import { IDataPluginServices, IIndexPattern, TimeRange, TimeHistoryContract, Query } from '../..'; import { useKibana, toMountPoint } from '../../../../kibana_react/public'; import { QueryStringInput } from './query_string_input'; -import { doesKueryExpressionHaveLuceneSyntaxError } from '../../../common'; +import { doesKueryExpressionHaveLuceneSyntaxError, UI_SETTINGS } from '../../../common'; import { PersistedLog, getQueryLog } from '../../query'; interface Props { @@ -255,7 +255,7 @@ export function QueryBarTopRow(props: Props) { } const commonlyUsedRanges = uiSettings! - .get('timepicker:quickRanges') + .get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES) .map(({ from, to, display }: { from: string; to: string; display: string }) => { return { start: from, diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx index 738c9cfb39398..755716aee8f48 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx @@ -160,10 +160,7 @@ describe('QueryStringInput', () => { ) ); - component - .find(QueryLanguageSwitcher) - .props() - .onSelectLanguage('lucene'); + component.find(QueryLanguageSwitcher).props().onSelectLanguage('lucene'); expect(mockStorage.set).toHaveBeenCalledWith('kibana.userQueryLanguage', 'lucene'); expect(mockCallback).toHaveBeenCalledWith({ query: '', language: 'lucene' }); }); diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index a51362d0ba92e..32295745ce217 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -105,10 +105,10 @@ export class QueryStringInputUI extends Component { private fetchIndexPatterns = async () => { const stringPatterns = this.props.indexPatterns.filter( - indexPattern => typeof indexPattern === 'string' + (indexPattern) => typeof indexPattern === 'string' ) as string[]; const objectPatterns = this.props.indexPatterns.filter( - indexPattern => typeof indexPattern !== 'string' + (indexPattern) => typeof indexPattern !== 'string' ) as IIndexPattern[]; const objectPatternsFromStrings = (await fetchIndexPatterns( @@ -175,11 +175,11 @@ export class QueryStringInputUI extends Component { return []; } const recentSearches = this.persistedLog.get(); - const matchingRecentSearches = recentSearches.filter(recentQuery => { + const matchingRecentSearches = recentSearches.filter((recentQuery) => { const recentQueryString = typeof recentQuery === 'object' ? toUser(recentQuery) : recentQuery; return recentQueryString.includes(query); }); - return matchingRecentSearches.map(recentSearch => { + return matchingRecentSearches.map((recentSearch) => { const text = toUser(recentSearch); const start = 0; const end = query.length; @@ -536,7 +536,7 @@ export class QueryStringInputUI extends Component { onClick={this.onClickInput} fullWidth autoFocus={!this.props.disableAutoFocus} - inputRef={node => { + inputRef={(node) => { if (node) { this.inputRef = node; } diff --git a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx index 5550ea16c22df..c61625dc06c18 100644 --- a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx +++ b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx @@ -104,7 +104,7 @@ export function SaveQueryForm({ const errors = []; if ( !!savedQueries.find( - existingSavedQuery => !savedQuery && existingSavedQuery.attributes.title === title + (existingSavedQuery) => !savedQuery && existingSavedQuery.attributes.title === title ) ) { errors.push(titleConflictErrorText); @@ -129,7 +129,7 @@ export function SaveQueryForm({ } }, [validate, onSave, title, description, shouldIncludeFilters, shouldIncludeTimefilter]); - const onInputChange = useCallback(event => { + const onInputChange = useCallback((event) => { setEnabledSaveButton(Boolean(event.target.value)); setFormErrors([]); setTitle(event.target.value); @@ -178,7 +178,7 @@ export function SaveQueryForm({ { + onChange={(event) => { setDescription(event.target.value); }} data-test-subj="saveQueryFormDescription" diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx index 8ad1b5d392f3b..6108de0280183 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx @@ -116,7 +116,7 @@ export function SavedQueryManagementComponent({ const onDeleteSavedQuery = async (savedQuery: SavedQuery) => { cancelPendingListingRequest.current(); setSavedQueries( - savedQueries.filter(currentSavedQuery => currentSavedQuery.id !== savedQuery.id) + savedQueries.filter((currentSavedQuery) => currentSavedQuery.id !== savedQuery.id) ); if (loadedSavedQuery && loadedSavedQuery.id === savedQuery.id) { @@ -146,7 +146,7 @@ export function SavedQueryManagementComponent({ ); const savedQueryRows = () => { - const savedQueriesWithoutCurrent = savedQueries.filter(savedQuery => { + const savedQueriesWithoutCurrent = savedQueries.filter((savedQuery) => { if (!loadedSavedQuery) return true; return savedQuery.id !== loadedSavedQuery.id; }); @@ -154,16 +154,16 @@ export function SavedQueryManagementComponent({ loadedSavedQuery && savedQueriesWithoutCurrent.length !== savedQueries.length ? [loadedSavedQuery, ...savedQueriesWithoutCurrent] : [...savedQueriesWithoutCurrent]; - return savedQueriesReordered.map(savedQuery => ( + return savedQueriesReordered.map((savedQuery) => ( { + onSelect={(savedQueryToSelect) => { onLoad(savedQueryToSelect); setIsOpen(false); }} - onDelete={savedQueryToDelete => onDeleteSavedQuery(savedQueryToDelete)} + onDelete={(savedQueryToDelete) => onDeleteSavedQuery(savedQueryToDelete)} showWriteOperations={!!showSaveQuery} /> )); diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 7723254f3aa51..18ed632e0a8ec 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -27,7 +27,7 @@ import { useFilterManager } from './lib/use_filter_manager'; import { useTimefilter } from './lib/use_timefilter'; import { useSavedQuery } from './lib/use_saved_query'; import { DataPublicPluginStart } from '../../types'; -import { Filter, Query, TimeRange } from '../../../common'; +import { Filter, Query, TimeRange, UI_SETTINGS } from '../../../common'; interface StatefulSearchBarDeps { core: CoreStart; @@ -125,7 +125,8 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) const defaultQuery = { query: '', language: - storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'), + storage.get('kibana.userQueryLanguage') || + core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), }; const [query, setQuery] = useState(props.query || defaultQuery); diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 2371ccdde068c..a5ac227559115 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -414,13 +414,13 @@ class SearchBarUI extends Component { filterBar = (
{ + ref={(node) => { this.filterBarWrapperRef = node; }} className={filterGroupClasses} >
{ + ref={(node) => { this.filterBarRef = node; }} > @@ -453,7 +453,7 @@ class SearchBarUI extends Component { {this.state.showSaveNewQueryModal ? ( this.onSave(savedQueryMeta, true)} + onSave={(savedQueryMeta) => this.onSave(savedQueryMeta, true)} onClose={() => this.setState({ showSaveNewQueryModal: false })} showFilterOption={this.props.showFilterBar} showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()} diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description_header.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description_header.tsx index 947f33efa242c..23cf087de23f3 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description_header.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description_header.tsx @@ -26,8 +26,8 @@ export function getFailurePropsForSummary( ): Array<{ key: string; value: string }> { const failureDetailProps: Array = ['shard', 'index', 'node']; return failureDetailProps - .filter(key => typeof failure[key] === 'number' || typeof failure[key] === 'string') - .map(key => ({ key, value: String(failure[key]) })); + .filter((key) => typeof failure[key] === 'number' || typeof failure[key] === 'string') + .map((key) => ({ key, value: String(failure[key]) })); } export function getFailureSummaryText(failure: ShardFailure, failureDetails?: string): string { @@ -49,7 +49,7 @@ export function getFailureSummaryDetailsText(failure: ShardFailure): string { } export function ShardFailureDescriptionHeader(props: ShardFailure) { - const failureDetails = getFailurePropsForSummary(props).map(kv => ( + const failureDetails = getFailurePropsForSummary(props).map((kv) => ( {kv.key} {kv.value} diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx index 3dcab7732f769..535f63161966d 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx @@ -104,7 +104,7 @@ export function ShardFailureModal({ request, response, title, onClose }: Props) - {copy => ( + {(copy) => ( { /> ); - component - .find(SuggestionComponent) - .at(1) - .simulate('click'); + component.find(SuggestionComponent).at(1).simulate('click'); expect(mockCallback).toHaveBeenCalledTimes(1); expect(mockCallback).toHaveBeenCalledWith(mockSuggestions[1]); }); @@ -140,10 +137,7 @@ describe('SuggestionsComponent', () => { /> ); - component - .find(SuggestionComponent) - .at(1) - .simulate('mouseenter'); + component.find(SuggestionComponent).at(1).simulate('mouseenter'); expect(mockCallback).toHaveBeenCalledTimes(1); expect(mockCallback).toHaveBeenCalledWith(1); }); diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index cdc6cd5b9e772..77dd7dcec01ee 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -43,7 +43,7 @@ export class SuggestionsComponent extends Component { const suggestions = this.props.suggestions.map((suggestion, index) => { return ( (this.childNodes[index] = node)} + innerRef={(node) => (this.childNodes[index] = node)} selected={index === this.props.index} suggestion={suggestion} onClick={this.props.onClick} @@ -62,7 +62,7 @@ export class SuggestionsComponent extends Component { id="kbnTypeahead__items" className="kbnTypeahead__items" role="listbox" - ref={node => (this.parentNode = node)} + ref={(node) => (this.parentNode = node)} onScroll={this.handleScroll} > {suggestions} diff --git a/src/plugins/data/server/autocomplete/autocomplete_service.ts b/src/plugins/data/server/autocomplete/autocomplete_service.ts index 412e9b6236195..07f0ce0e7c1e5 100644 --- a/src/plugins/data/server/autocomplete/autocomplete_service.ts +++ b/src/plugins/data/server/autocomplete/autocomplete_service.ts @@ -17,14 +17,23 @@ * under the License. */ +import { TypeOf } from '@kbn/config-schema'; import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { registerRoutes } from './routes'; +import { ConfigSchema, configSchema } from '../../config'; export class AutocompleteService implements Plugin { - constructor(private initializerContext: PluginInitializerContext) {} + private valueSuggestionsEnabled: boolean = true; + + constructor(private initializerContext: PluginInitializerContext) { + initializerContext.config.create>().subscribe((configUpdate) => { + this.valueSuggestionsEnabled = configUpdate.autocomplete.valueSuggestions.enabled; + }); + } public setup(core: CoreSetup) { - registerRoutes(core, this.initializerContext.config.legacy.globalConfig$); + if (this.valueSuggestionsEnabled) + registerRoutes(core, this.initializerContext.config.legacy.globalConfig$); } public start() {} diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index b7569a22e9fc9..f68d7e1552ccb 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -55,7 +55,7 @@ export function registerValueSuggestionsRoute( const config = await config$.pipe(first()).toPromise(); const { field: fieldName, query, boolFilter } = request.body; const { index } = request.params; - const { dataClient } = context.core.elasticsearch; + const { client } = context.core.elasticsearch.legacy; const signal = getRequestAbortedSignal(request.events.aborted$); const autocompleteSearchOptions = { @@ -69,7 +69,7 @@ export function registerValueSuggestionsRoute( const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter); try { - const result = await dataClient.callAsCurrentUser('search', { index, body }, { signal }); + const result = await client.callAsCurrentUser('search', { index, body }, { signal }); const buckets: any[] = get(result, 'aggregations.suggestions.buckets') || @@ -93,7 +93,7 @@ async function getBody( // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators const getEscapedQuery = (q: string = '') => - q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, match => `\\${match}`); + q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); // Helps ensure that the regex is not evaluated eagerly against the terms dictionary const executionHint = 'map'; diff --git a/src/plugins/data/server/field_formats/converters/date_server.ts b/src/plugins/data/server/field_formats/converters/date_server.ts index f4e6296259196..85eb65dfc6a8d 100644 --- a/src/plugins/data/server/field_formats/converters/date_server.ts +++ b/src/plugins/data/server/field_formats/converters/date_server.ts @@ -77,7 +77,7 @@ export class DateFormat extends FieldFormat { }; } - textConvert: TextContextTypeConvert = val => { + textConvert: TextContextTypeConvert = (val) => { // don't give away our ref to converter so we can hot-swap when config changes const pattern = this.param('pattern'); const timezone = this.param('timezone'); diff --git a/src/plugins/data/server/field_formats/field_formats_service.ts b/src/plugins/data/server/field_formats/field_formats_service.ts index 3404fe8cee9fd..70584efbee0a0 100644 --- a/src/plugins/data/server/field_formats/field_formats_service.ts +++ b/src/plugins/data/server/field_formats/field_formats_service.ts @@ -42,7 +42,7 @@ export class FieldFormatsService { const uiConfigs = await uiSettings.getAll(); const registeredUiSettings = uiSettings.getRegistered(); - Object.keys(registeredUiSettings).forEach(key => { + Object.keys(registeredUiSettings).forEach((key) => { if (has(uiConfigs, key) && registeredUiSettings[key].type === 'json') { uiConfigs[key] = JSON.parse(uiConfigs[key]); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 47bef4255347c..831d23864d228 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -17,7 +17,8 @@ * under the License. */ -import { PluginInitializerContext } from '../../../core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../core/server'; +import { ConfigSchema, configSchema } from '../config'; import { DataServerPlugin, DataPluginSetup, DataPluginStart } from './plugin'; import { @@ -145,6 +146,7 @@ export { ES_FIELD_TYPES, KBN_FIELD_TYPES, IndexPatternAttributes, + UI_SETTINGS, } from '../common'; /** @@ -213,7 +215,7 @@ export { * @public */ -export function plugin(initializerContext: PluginInitializerContext) { +export function plugin(initializerContext: PluginInitializerContext) { return new DataServerPlugin(initializerContext); } @@ -222,3 +224,10 @@ export { DataPluginSetup as PluginSetup, DataPluginStart as PluginStart, }; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + autocomplete: true, + }, + schema: configSchema, +}; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js index ba4c0865a8d80..a0af7582ac6f3 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js @@ -44,7 +44,7 @@ describe('index_patterns/field_capabilities/field_capabilities', () => { // assert that the stub was called with the exact `args`, using === matching const calledWithExactly = (stub, args, matcher = sinon.match.same) => { - sinon.assert.calledWithExactly(stub, ...args.map(arg => matcher(arg))); + sinon.assert.calledWithExactly(stub, ...args.map((arg) => matcher(arg))); }; const stubDeps = (options = {}) => { @@ -83,10 +83,10 @@ describe('index_patterns/field_capabilities/field_capabilities', () => { const sortedLetters = sortBy(letters); stubDeps({ - fieldsFromFieldCaps: shuffle(letters.map(name => ({ name }))), + fieldsFromFieldCaps: shuffle(letters.map((name) => ({ name }))), }); - const fieldNames = (await getFieldCapabilities()).map(field => field.name); + const fieldNames = (await getFieldCapabilities()).map((field) => field.name); expect(fieldNames).toEqual(sortedLetters); }); }); @@ -99,7 +99,7 @@ describe('index_patterns/field_capabilities/field_capabilities', () => { const resp = await getFieldCapabilities(undefined, undefined, ['meta1', 'meta2']); expect(resp).toHaveLength(4); - expect(resp.map(field => field.name)).toEqual(['bar', 'foo', 'meta1', 'meta2']); + expect(resp.map((field) => field.name)).toEqual(['bar', 'foo', 'meta1', 'meta2']); }); }); @@ -115,7 +115,7 @@ describe('index_patterns/field_capabilities/field_capabilities', () => { }); describe('ensures that every field has property:', () => { - properties.forEach(property => { + properties.forEach((property) => { it(property, async () => { const field = createField(); delete field[property]; @@ -131,7 +131,7 @@ describe('index_patterns/field_capabilities/field_capabilities', () => { // ensure field object was not mutated expect(field).not.toHaveProperty(property); - Object.keys(field).forEach(key => { + Object.keys(field).forEach((key) => { // ensure response field has original values from field expect(resp[0][key]).toBe(footballs[0]); }); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index 2a30c89342cf3..d8c9466432204 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -47,10 +47,10 @@ export async function getFieldCapabilities( const fieldsFromFieldCapsByName = indexBy(readFieldCapsResponse(esFieldCaps), 'name'); const allFieldsUnsorted = Object.keys(fieldsFromFieldCapsByName) - .filter(name => !name.startsWith('_')) + .filter((name) => !name.startsWith('_')) .concat(metaFields) .reduce(concatIfUniq, [] as string[]) - .map(name => + .map((name) => defaults({}, fieldsFromFieldCapsByName[name], { name, type: 'string', diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index de905ce4f336d..1a4e2b1fe9ee2 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -49,7 +49,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { responseClone.fields['@timestamp'].date.extraCapability = true; const fields = readFieldCapsResponse(responseClone); - fields.forEach(field => { + fields.forEach((field) => { const fieldWithoutOptionalKeys = omit(field, 'conflictDescriptions', 'subType'); expect(Object.keys(fieldWithoutOptionalKeys)).toEqual([ @@ -67,13 +67,13 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { it('calls shouldReadFieldFromDocValues() for each non-conflict field', () => { sandbox.spy(shouldReadFieldFromDocValuesNS, 'shouldReadFieldFromDocValues'); const fields = readFieldCapsResponse(esResponse); - const conflictCount = fields.filter(f => f.type === 'conflict').length; + const conflictCount = fields.filter((f) => f.type === 'conflict').length; // +2 is for the object and nested fields which get filtered out of the final return value from readFieldCapsResponse sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 2); }); it('converts es types to kibana types', () => { - readFieldCapsResponse(esResponse).forEach(field => { + readFieldCapsResponse(esResponse).forEach((field) => { if (!getKbnFieldType(field.type)) { throw new Error(`expected field to have kibana type, got ${field.type}`); } @@ -82,7 +82,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { it('should include the original ES types found for each field across indices', () => { const fields = readFieldCapsResponse(esResponse); - fields.forEach(field => { + fields.forEach((field) => { const fixtureTypes = Object.keys(esResponse.fields[field.name]); expect(field.esTypes).toEqual(fixtureTypes); }); @@ -90,7 +90,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { it('returns fields with multiple types as conflicts', () => { const fields = readFieldCapsResponse(esResponse); - const conflicts = fields.filter(f => f.type === 'conflict'); + const conflicts = fields.filter((f) => f.type === 'conflict'); expect(conflicts).toEqual([ { name: 'success', @@ -109,43 +109,43 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { it('does not return conflicted fields if the types are resolvable to the same kibana type', () => { const fields = readFieldCapsResponse(esResponse); - const resolvableToString = fields.find(f => f.name === 'resolvable_to_string'); - const resolvableToNumber = fields.find(f => f.name === 'resolvable_to_number'); + const resolvableToString = fields.find((f) => f.name === 'resolvable_to_string'); + const resolvableToNumber = fields.find((f) => f.name === 'resolvable_to_number'); expect(resolvableToString.type).toBe('string'); expect(resolvableToNumber.type).toBe('number'); }); it('returns aggregatable if at least one field is aggregatable', () => { const fields = readFieldCapsResponse(esResponse); - const mixAggregatable = fields.find(f => f.name === 'mix_aggregatable'); - const mixAggregatableOther = fields.find(f => f.name === 'mix_aggregatable_other'); + const mixAggregatable = fields.find((f) => f.name === 'mix_aggregatable'); + const mixAggregatableOther = fields.find((f) => f.name === 'mix_aggregatable_other'); expect(mixAggregatable.aggregatable).toBe(true); expect(mixAggregatableOther.aggregatable).toBe(true); }); it('returns searchable if at least one field is searchable', () => { const fields = readFieldCapsResponse(esResponse); - const mixSearchable = fields.find(f => f.name === 'mix_searchable'); - const mixSearchableOther = fields.find(f => f.name === 'mix_searchable_other'); + const mixSearchable = fields.find((f) => f.name === 'mix_searchable'); + const mixSearchableOther = fields.find((f) => f.name === 'mix_searchable_other'); expect(mixSearchable.searchable).toBe(true); expect(mixSearchableOther.searchable).toBe(true); }); it('returns multi fields with a subType key describing the relationship', () => { const fields = readFieldCapsResponse(esResponse); - const child = fields.find(f => f.name === 'multi_parent.child'); + const child = fields.find((f) => f.name === 'multi_parent.child'); expect(child).toHaveProperty('subType', { multi: { parent: 'multi_parent' } }); }); it('returns nested sub-fields with a subType key describing the relationship', () => { const fields = readFieldCapsResponse(esResponse); - const child = fields.find(f => f.name === 'nested_object_parent.child'); + const child = fields.find((f) => f.name === 'nested_object_parent.child'); expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' } }); }); it('handles fields that are both nested and multi', () => { const fields = readFieldCapsResponse(esResponse); - const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); + const child = fields.find((f) => f.name === 'nested_object_parent.child.keyword'); expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' }, multi: { parent: 'nested_object_parent.child' }, @@ -154,7 +154,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { it('does not include the field actually mapped as nested itself', () => { const fields = readFieldCapsResponse(esResponse); - const child = fields.find(f => f.name === 'nested_object_parent'); + const child = fields.find((f) => f.name === 'nested_object_parent'); expect(child).toBeUndefined(); }); @@ -163,7 +163,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { // to see if their parents are *not* object fields. In the future we may want to // add subType info for object fields but for now we don't need it. const fields = readFieldCapsResponse(esResponse); - const child = fields.find(f => f.name === 'object_parent.child'); + const child = fields.find((f) => f.name === 'object_parent.child'); expect(child).not.toHaveProperty('subType'); }); }); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index 06eb30db0b24b..cb1ec6a2ebcf3 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -93,74 +93,76 @@ export interface FieldCapsResponse { */ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): FieldDescriptor[] { const capsByNameThenType = fieldCapsResponse.fields; - const kibanaFormattedCaps: FieldDescriptor[] = Object.keys(capsByNameThenType).map(fieldName => { - const capsByType = capsByNameThenType[fieldName]; - const types = Object.keys(capsByType); + const kibanaFormattedCaps: FieldDescriptor[] = Object.keys(capsByNameThenType).map( + (fieldName) => { + const capsByType = capsByNameThenType[fieldName]; + const types = Object.keys(capsByType); - // If a single type is marked as searchable or aggregatable, all the types are searchable or aggregatable - const isSearchable = types.some(type => { - return ( - !!capsByType[type].searchable || - (!!capsByType[type].non_searchable_indices && - capsByType[type].non_searchable_indices!.length > 0) - ); - }); + // If a single type is marked as searchable or aggregatable, all the types are searchable or aggregatable + const isSearchable = types.some((type) => { + return ( + !!capsByType[type].searchable || + (!!capsByType[type].non_searchable_indices && + capsByType[type].non_searchable_indices!.length > 0) + ); + }); - const isAggregatable = types.some(type => { - return ( - !!capsByType[type].aggregatable || - (!!capsByType[type].non_aggregatable_indices && - capsByType[type].non_aggregatable_indices!.length > 0) - ); - }); + const isAggregatable = types.some((type) => { + return ( + !!capsByType[type].aggregatable || + (!!capsByType[type].non_aggregatable_indices && + capsByType[type].non_aggregatable_indices!.length > 0) + ); + }); + + // If there are multiple types but they all resolve to the same kibana type + // ignore the conflict and carry on (my wayward son) + const uniqueKibanaTypes = uniq(types.map(castEsToKbnFieldTypeName)); + if (uniqueKibanaTypes.length > 1) { + return { + name: fieldName, + type: 'conflict', + esTypes: types, + searchable: isSearchable, + aggregatable: isAggregatable, + readFromDocValues: false, + conflictDescriptions: types.reduce( + (acc, esType) => ({ + ...acc, + [esType]: capsByType[esType].indices, + }), + {} + ), + }; + } - // If there are multiple types but they all resolve to the same kibana type - // ignore the conflict and carry on (my wayward son) - const uniqueKibanaTypes = uniq(types.map(castEsToKbnFieldTypeName)); - if (uniqueKibanaTypes.length > 1) { + const esType = types[0]; return { name: fieldName, - type: 'conflict', + type: castEsToKbnFieldTypeName(esType), esTypes: types, searchable: isSearchable, aggregatable: isAggregatable, - readFromDocValues: false, - conflictDescriptions: types.reduce( - (acc, esType) => ({ - ...acc, - [esType]: capsByType[esType].indices, - }), - {} - ), + readFromDocValues: shouldReadFieldFromDocValues(isAggregatable, esType), }; } - - const esType = types[0]; - return { - name: fieldName, - type: castEsToKbnFieldTypeName(esType), - esTypes: types, - searchable: isSearchable, - aggregatable: isAggregatable, - readFromDocValues: shouldReadFieldFromDocValues(isAggregatable, esType), - }; - }); + ); // Get all types of sub fields. These could be multi fields or children of nested/object types - const subFields = kibanaFormattedCaps.filter(field => { + const subFields = kibanaFormattedCaps.filter((field) => { return field.name.includes('.'); }); // Determine the type of each sub field. - subFields.forEach(field => { + subFields.forEach((field) => { const parentFieldNames = field.name .split('.') .slice(0, -1) .map((_, index, parentFieldNameParts) => { return parentFieldNameParts.slice(0, index + 1).join('.'); }); - const parentFieldCaps = parentFieldNames.map(parentFieldName => { - return kibanaFormattedCaps.find(caps => caps.name === parentFieldName); + const parentFieldCaps = parentFieldNames.map((parentFieldName) => { + return kibanaFormattedCaps.find((caps) => caps.name === parentFieldName); }); const parentFieldCapsAscending = parentFieldCaps.reverse(); @@ -174,7 +176,7 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie // We need to know if any parent field is nested const nestedParentCaps = parentFieldCapsAscending.find( - parentCaps => parentCaps && parentCaps.type === 'nested' + (parentCaps) => parentCaps && parentCaps.type === 'nested' ); if (nestedParentCaps) { subType = { ...subType, nested: { path: nestedParentCaps.name } }; @@ -186,7 +188,7 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie } }); - return kibanaFormattedCaps.filter(field => { + return kibanaFormattedCaps.filter((field) => { return !['object', 'nested'].includes(field.type); }); } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts index 7504d7bc9c460..764307bef0ba6 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts @@ -47,7 +47,7 @@ export async function resolveTimePattern(callCluster: APICaller, timePattern: st ) .sortBy((indexName: string) => indexName) .uniq(true) - .map(indexName => { + .map((indexName) => { const parsed = moment(indexName, timePattern, true); if (!parsed.isValid()) { return { @@ -69,8 +69,10 @@ export async function resolveTimePattern(callCluster: APICaller, timePattern: st .value(); return { - all: allIndexDetails.map(details => details.indexName), + all: allIndexDetails.map((details) => details.indexName), - matches: allIndexDetails.filter(details => details.isMatch).map(details => details.indexName), + matches: allIndexDetails + .filter((details) => details.isMatch) + .map((details) => details.indexName), }; } diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 8b9fa28c77165..428e7fef6deea 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -46,7 +46,7 @@ export function registerRoutes(http: HttpServiceSetup) { }, }, async (context, request, response) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, meta_fields: metaFields } = request.query; @@ -105,7 +105,7 @@ export function registerRoutes(http: HttpServiceSetup) { }, }, async (context: RequestHandlerContext, request: any, response: any) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, interval, look_back: lookBack, meta_fields: metaFields } = request.query; diff --git a/src/plugins/data/server/index_patterns/utils.ts b/src/plugins/data/server/index_patterns/utils.ts index b7adafaeb3e94..e841097fe49c2 100644 --- a/src/plugins/data/server/index_patterns/utils.ts +++ b/src/plugins/data/server/index_patterns/utils.ts @@ -25,7 +25,7 @@ export const getFieldByName = ( indexPattern: IIndexPattern ): IFieldType | undefined => { const fields: IFieldType[] = indexPattern && JSON.parse(indexPattern.attributes.fields); - const field = fields && fields.find(f => f.name === fieldName); + const field = fields && fields.find((f) => f.name === fieldName); return field; }; diff --git a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts index 3dfaa9c6d0a98..d8ccdcc6a61c5 100644 --- a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts +++ b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts @@ -42,8 +42,8 @@ export class KqlTelemetryService implements Plugin { this.initializerContext.config.legacy.globalConfig$ .pipe(first()) .toPromise() - .then(config => makeKQLUsageCollector(usageCollection, config.kibana.index)) - .catch(e => { + .then((config) => makeKQLUsageCollector(usageCollection, config.kibana.index)) + .catch((e) => { this.initializerContext.logger .get('kql-telemetry') .warn(`Registering KQL telemetry collector failed: ${e}`); diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts index 446320b09757a..81e352fea51b2 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts @@ -22,6 +22,9 @@ import { APICaller } from 'kibana/server'; jest.mock('../../../common', () => ({ DEFAULT_QUERY_LANGUAGE: 'lucene', + UI_SETTINGS: { + SEARCH_QUERY_LANGUAGE: 'search:queryLanguage', + }, })); let fetch: ReturnType; diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts index 9f3437161541f..157716b38f523 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts @@ -19,7 +19,7 @@ import { get } from 'lodash'; import { APICaller } from 'kibana/server'; -import { DEFAULT_QUERY_LANGUAGE } from '../../../common'; +import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../../../common'; const defaultSearchQueryLanguageSetting = DEFAULT_QUERY_LANGUAGE; @@ -40,7 +40,7 @@ export function fetchProvider(index: string) { const queryLanguageConfigValue = get( config, - 'hits.hits[0]._source.config.search:queryLanguage' + `hits.hits[0]._source.config.${UI_SETTINGS.SEARCH_QUERY_LANGUAGE}` ); // search:queryLanguage can potentially be in four states in the .kibana index: diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 83a5358642ce4..8c9d0df2ed894 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -18,6 +18,7 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/server'; +import { ConfigSchema } from '../config'; import { IndexPatternsService } from './index_patterns'; import { ISearchSetup } from './search'; import { SearchService } from './search/search_service'; @@ -27,7 +28,7 @@ import { KqlTelemetryService } from './kql_telemetry'; import { UsageCollectionSetup } from '../../usage_collection/server'; import { AutocompleteService } from './autocomplete'; import { FieldFormatsService, FieldFormatsSetup, FieldFormatsStart } from './field_formats'; -import { uiSettings } from './ui_settings'; +import { getUiSettings } from './ui_settings'; export interface DataPluginSetup { search: ISearchSetup; @@ -51,7 +52,7 @@ export class DataServerPlugin implements Plugin) { this.searchService = new SearchService(initializerContext); this.scriptsService = new ScriptsService(); this.kqlTelemetryService = new KqlTelemetryService(initializerContext); @@ -64,7 +65,8 @@ export class DataServerPlugin implements Plugin = doc => ({ +const migrateAttributeTypeAndAttributeTypeMeta: SavedObjectMigrationFn = (doc) => ({ ...doc, attributes: { ...doc.attributes, @@ -29,12 +29,12 @@ const migrateAttributeTypeAndAttributeTypeMeta: SavedObjectMigrationFn }, }); -const migrateSubTypeAndParentFieldProperties: SavedObjectMigrationFn = doc => { +const migrateSubTypeAndParentFieldProperties: SavedObjectMigrationFn = (doc) => { if (!doc.attributes.fields) return doc; const fieldsString = doc.attributes.fields; const fields = JSON.parse(fieldsString) as any[]; - const migratedFields = fields.map(field => { + const migratedFields = fields.map((field) => { if (field.subType === 'multi') { return { ...omit(field, 'parent'), diff --git a/src/plugins/data/server/saved_objects/index_patterns.ts b/src/plugins/data/server/saved_objects/index_patterns.ts index a212d7f88e4eb..ee02f38427914 100644 --- a/src/plugins/data/server/saved_objects/index_patterns.ts +++ b/src/plugins/data/server/saved_objects/index_patterns.ts @@ -36,7 +36,7 @@ export const indexPatternSavedObjectType: SavedObjectsType = { }, getInAppUrl(obj) { return { - path: `/app/kibana#/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, + path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, uiCapabilitiesPath: 'management.kibana.index_patterns', }; }, diff --git a/src/plugins/data/server/saved_objects/search_migrations.test.ts b/src/plugins/data/server/saved_objects/search_migrations.test.ts index f9b4af7d6d2bf..69db08a689255 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.test.ts +++ b/src/plugins/data/server/saved_objects/search_migrations.test.ts @@ -277,7 +277,7 @@ Object { }); }); - describe('7.4.0', function() { + describe('7.4.0', function () { const migrationFn = searchSavedObjectTypeMigrations['7.4.0']; test('transforms one dimensional sort arrays into two dimensional arrays', () => { diff --git a/src/plugins/data/server/saved_objects/search_migrations.ts b/src/plugins/data/server/saved_objects/search_migrations.ts index c8ded51193c92..2e37cd1255cee 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.ts +++ b/src/plugins/data/server/saved_objects/search_migrations.ts @@ -21,7 +21,7 @@ import { flow, get } from 'lodash'; import { SavedObjectMigrationFn } from 'kibana/server'; import { DEFAULT_QUERY_LANGUAGE } from '../../common'; -const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { +const migrateMatchAllQuery: SavedObjectMigrationFn = (doc) => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); if (searchSourceJSON) { @@ -55,7 +55,7 @@ const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { return doc; }; -const migrateIndexPattern: SavedObjectMigrationFn = doc => { +const migrateIndexPattern: SavedObjectMigrationFn = (doc) => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); if (typeof searchSourceJSON !== 'string') { return doc; @@ -103,7 +103,7 @@ const setNewReferences: SavedObjectMigrationFn = (doc, context) => { return migrateIndexPattern(doc, context); }; -const migrateSearchSortToNestedArray: SavedObjectMigrationFn = doc => { +const migrateSearchSortToNestedArray: SavedObjectMigrationFn = (doc) => { const sort = get(doc, 'attributes.sort'); if (!sort) return doc; diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts index 6ea0799f790fc..f5e6507d977cd 100644 --- a/src/plugins/data/server/search/routes.test.ts +++ b/src/plugins/data/server/search/routes.test.ts @@ -38,8 +38,9 @@ describe('Search service', () => { const mockContext = { core: { elasticsearch: { - dataClient: {} as ScopedClusterClient, - adminClient: {} as ScopedClusterClient, + legacy: { + client: {} as ScopedClusterClient, + }, }, }, search: { @@ -75,8 +76,9 @@ describe('Search service', () => { const mockContext = { core: { elasticsearch: { - dataClient: {} as ScopedClusterClient, - adminClient: {} as ScopedClusterClient, + legacy: { + client: {} as ScopedClusterClient, + }, }, }, search: { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 5ee19cd3df19f..1c267c32ebc37 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -57,9 +57,9 @@ export class SearchService implements Plugin { core.savedObjects.registerType(searchSavedObjectType); - core.http.registerRouteHandlerContext<'search'>('search', context => { + core.http.registerRouteHandlerContext<'search'>('search', (context) => { return createApi({ - caller: context.core.elasticsearch.dataClient.callAsCurrentUser, + caller: context.core.elasticsearch.legacy.client.callAsCurrentUser, searchStrategies: this.searchStrategies, }); }); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 76eb3b5b87424..ab8903f8c6790 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -152,6 +152,13 @@ import { Url } from 'url'; // @public export const castEsToKbnFieldTypeName: (esType: string) => KBN_FIELD_TYPES; +// Warning: (ae-forgotten-export) The symbol "PluginConfigDescriptor" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ConfigSchema" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "config" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const config: PluginConfigDescriptor; + // @public (undocumented) export enum ES_FIELD_TYPES { // (undocumented) @@ -604,7 +611,7 @@ export function parseInterval(interval: string): moment.Duration | null; // @public (undocumented) export class Plugin implements Plugin_2 { // Warning: (ae-forgotten-export) The symbol "PluginInitializerContext" needs to be exported by the entry point index.d.ts - constructor(initializerContext: PluginInitializerContext); + constructor(initializerContext: PluginInitializerContext); // Warning: (ae-forgotten-export) The symbol "DataPluginSetupDependencies" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -627,7 +634,7 @@ export class Plugin implements Plugin_2 { } // @public -export function plugin(initializerContext: PluginInitializerContext): Plugin; +export function plugin(initializerContext: PluginInitializerContext): Plugin; // Warning: (ae-missing-release-tag) "DataPluginSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -713,38 +720,71 @@ export interface TimeRange { // @public export type TSearchStrategyProvider = (context: ISearchContext, caller: APICaller_2, search: ISearchGeneric) => ISearchStrategy; +// Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const UI_SETTINGS: { + META_FIELDS: string; + DOC_HIGHLIGHT: string; + QUERY_STRING_OPTIONS: string; + QUERY_ALLOW_LEADING_WILDCARDS: string; + SEARCH_QUERY_LANGUAGE: string; + SORT_OPTIONS: string; + COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; + COURIER_SET_REQUEST_PREFERENCE: string; + COURIER_CUSTOM_REQUEST_PREFERENCE: string; + COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; + COURIER_BATCH_SEARCHES: string; + SEARCH_INCLUDE_FROZEN: string; + HISTOGRAM_BAR_TARGET: string; + HISTOGRAM_MAX_BARS: string; + HISTORY_LIMIT: string; + SHORT_DOTS_ENABLE: string; + FORMAT_DEFAULT_TYPE_MAP: string; + FORMAT_NUMBER_DEFAULT_PATTERN: string; + FORMAT_PERCENT_DEFAULT_PATTERN: string; + FORMAT_BYTES_DEFAULT_PATTERN: string; + FORMAT_CURRENCY_DEFAULT_PATTERN: string; + FORMAT_NUMBER_DEFAULT_LOCALE: string; + TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; + TIMEPICKER_QUICK_RANGES: string; + INDEXPATTERN_PLACEHOLDER: string; + FILTERS_PINNED_BY_DEFAULT: string; + FILTERS_EDITOR_SUGGEST_VALUES: string; +}; + // Warnings were encountered during analysis: // -// src/plugins/data/server/index.ts:39:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:39:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:70:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:70:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:130:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:130:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:182:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:183:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/plugin.ts:65:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:191:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/plugin.ts:66:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 5af62be295201..de978c7968aee 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -19,33 +19,652 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; - import { UiSettingsParams } from 'kibana/server'; -import { META_FIELDS_SETTING, DOC_HIGHLIGHT_SETTING } from '../common'; +// @ts-ignore untyped module +import numeralLanguages from '@elastic/numeral/languages'; +import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../common'; + +const luceneQueryLanguageLabel = i18n.translate('data.advancedSettings.searchQueryLanguageLucene', { + defaultMessage: 'Lucene', +}); + +const queryLanguageSettingName = i18n.translate('data.advancedSettings.searchQueryLanguageTitle', { + defaultMessage: 'Query language', +}); -export const uiSettings: Record = { - [META_FIELDS_SETTING]: { - name: i18n.translate('data.advancedSettings.metaFieldsTitle', { - defaultMessage: 'Meta fields', - }), - value: ['_source', '_id', '_type', '_index', '_score'], - description: i18n.translate('data.advancedSettings.metaFieldsText', { - defaultMessage: - 'Fields that exist outside of _source to merge into our document when displaying it', - }), - schema: schema.arrayOf(schema.string()), - }, - [DOC_HIGHLIGHT_SETTING]: { - name: i18n.translate('data.advancedSettings.docTableHighlightTitle', { - defaultMessage: 'Highlight results', - }), - value: true, - description: i18n.translate('data.advancedSettings.docTableHighlightText', { - defaultMessage: - 'Highlight results in Discover and Saved Searches Dashboard. ' + - 'Highlighting makes requests slow when working on big documents.', - }), - category: ['discover'], - schema: schema.boolean(), - }, +const requestPreferenceOptionLabels = { + sessionId: i18n.translate('data.advancedSettings.courier.requestPreferenceSessionId', { + defaultMessage: 'Session ID', + }), + custom: i18n.translate('data.advancedSettings.courier.requestPreferenceCustom', { + defaultMessage: 'Custom', + }), + none: i18n.translate('data.advancedSettings.courier.requestPreferenceNone', { + defaultMessage: 'None', + }), }; + +// We add the `en` key manually here, since that's not a real numeral locale, but the +// default fallback in case the locale is not found. +const numeralLanguageIds = [ + 'en', + ...numeralLanguages.map((numeralLanguage: any) => { + return numeralLanguage.id; + }), +]; + +export function getUiSettings(): Record> { + return { + [UI_SETTINGS.META_FIELDS]: { + name: i18n.translate('data.advancedSettings.metaFieldsTitle', { + defaultMessage: 'Meta fields', + }), + value: ['_source', '_id', '_type', '_index', '_score'], + description: i18n.translate('data.advancedSettings.metaFieldsText', { + defaultMessage: + 'Fields that exist outside of _source to merge into our document when displaying it', + }), + schema: schema.arrayOf(schema.string()), + }, + [UI_SETTINGS.DOC_HIGHLIGHT]: { + name: i18n.translate('data.advancedSettings.docTableHighlightTitle', { + defaultMessage: 'Highlight results', + }), + value: true, + description: i18n.translate('data.advancedSettings.docTableHighlightText', { + defaultMessage: + 'Highlight results in Discover and Saved Searches Dashboard. ' + + 'Highlighting makes requests slow when working on big documents.', + }), + category: ['discover'], + schema: schema.boolean(), + }, + [UI_SETTINGS.QUERY_STRING_OPTIONS]: { + name: i18n.translate('data.advancedSettings.query.queryStringOptionsTitle', { + defaultMessage: 'Query string options', + }), + value: '{ "analyze_wildcard": true }', + description: i18n.translate('data.advancedSettings.query.queryStringOptionsText', { + defaultMessage: + '{optionsLink} for the lucene query string parser. Is only used when "{queryLanguage}" is set ' + + 'to {luceneLanguage}.', + description: + 'Part of composite text: data.advancedSettings.query.queryStringOptions.optionsLinkText + ' + + 'data.advancedSettings.query.queryStringOptionsText', + values: { + optionsLink: + '' + + i18n.translate('data.advancedSettings.query.queryStringOptions.optionsLinkText', { + defaultMessage: 'Options', + }) + + '', + luceneLanguage: luceneQueryLanguageLabel, + queryLanguage: queryLanguageSettingName, + }, + }), + type: 'json', + schema: schema.object({ + analyze_wildcard: schema.boolean(), + }), + }, + [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: { + name: i18n.translate('data.advancedSettings.query.allowWildcardsTitle', { + defaultMessage: 'Allow leading wildcards in query', + }), + value: true, + description: i18n.translate('data.advancedSettings.query.allowWildcardsText', { + defaultMessage: + 'When set, * is allowed as the first character in a query clause. ' + + 'Currently only applies when experimental query features are enabled in the query bar. ' + + 'To disallow leading wildcards in basic lucene queries, use {queryStringOptionsPattern}.', + values: { + queryStringOptionsPattern: UI_SETTINGS.QUERY_STRING_OPTIONS, + }, + }), + schema: schema.boolean(), + }, + [UI_SETTINGS.SEARCH_QUERY_LANGUAGE]: { + name: queryLanguageSettingName, + value: DEFAULT_QUERY_LANGUAGE, + description: i18n.translate('data.advancedSettings.searchQueryLanguageText', { + defaultMessage: + 'Query language used by the query bar. KQL is a new language built specifically for Kibana.', + }), + type: 'select', + options: ['lucene', 'kuery'], + optionLabels: { + lucene: luceneQueryLanguageLabel, + kuery: i18n.translate('data.advancedSettings.searchQueryLanguageKql', { + defaultMessage: 'KQL', + }), + }, + schema: schema.string(), + }, + [UI_SETTINGS.SORT_OPTIONS]: { + name: i18n.translate('data.advancedSettings.sortOptionsTitle', { + defaultMessage: 'Sort options', + }), + value: '{ "unmapped_type": "boolean" }', + description: i18n.translate('data.advancedSettings.sortOptionsText', { + defaultMessage: '{optionsLink} for the Elasticsearch sort parameter', + description: + 'Part of composite text: data.advancedSettings.sortOptions.optionsLinkText + ' + + 'data.advancedSettings.sortOptionsText', + values: { + optionsLink: + '' + + i18n.translate('data.advancedSettings.sortOptions.optionsLinkText', { + defaultMessage: 'Options', + }) + + '', + }, + }), + type: 'json', + schema: schema.object({ + unmapped_type: schema.string(), + }), + }, + defaultIndex: { + name: i18n.translate('data.advancedSettings.defaultIndexTitle', { + defaultMessage: 'Default index', + }), + value: null, + type: 'string', + description: i18n.translate('data.advancedSettings.defaultIndexText', { + defaultMessage: 'The index to access if no index is set', + }), + schema: schema.nullable(schema.string()), + }, + [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: { + name: i18n.translate('data.advancedSettings.courier.ignoreFilterTitle', { + defaultMessage: 'Ignore filter(s)', + }), + value: false, + description: i18n.translate('data.advancedSettings.courier.ignoreFilterText', { + defaultMessage: + 'This configuration enhances support for dashboards containing visualizations accessing dissimilar indexes. ' + + 'When disabled, all filters are applied to all visualizations. ' + + 'When enabled, filter(s) will be ignored for a visualization ' + + `when the visualization's index does not contain the filtering field.`, + }), + category: ['search'], + schema: schema.boolean(), + }, + [UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE]: { + name: i18n.translate('data.advancedSettings.courier.requestPreferenceTitle', { + defaultMessage: 'Request preference', + }), + value: 'sessionId', + options: ['sessionId', 'custom', 'none'], + optionLabels: requestPreferenceOptionLabels, + type: 'select', + description: i18n.translate('data.advancedSettings.courier.requestPreferenceText', { + defaultMessage: `Allows you to set which shards handle your search requests. +
    +
  • {sessionId}: restricts operations to execute all search requests on the same shards. + This has the benefit of reusing shard caches across requests.
  • +
  • {custom}: allows you to define a your own preference. + Use 'courier:customRequestPreference' to customize your preference value.
  • +
  • {none}: means do not set a preference. + This might provide better performance because requests can be spread across all shard copies. + However, results might be inconsistent because different shards might be in different refresh states.
  • +
`, + values: { + sessionId: requestPreferenceOptionLabels.sessionId, + custom: requestPreferenceOptionLabels.custom, + none: requestPreferenceOptionLabels.none, + }, + }), + category: ['search'], + schema: schema.string(), + }, + [UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE]: { + name: i18n.translate('data.advancedSettings.courier.customRequestPreferenceTitle', { + defaultMessage: 'Custom request preference', + }), + value: '_local', + type: 'string', + description: i18n.translate('data.advancedSettings.courier.customRequestPreferenceText', { + defaultMessage: + '{requestPreferenceLink} used when {setRequestReferenceSetting} is set to {customSettingValue}.', + description: + 'Part of composite text: data.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText + ' + + 'data.advancedSettings.courier.customRequestPreferenceText', + values: { + setRequestReferenceSetting: `${UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE}`, + customSettingValue: '"custom"', + requestPreferenceLink: + '' + + i18n.translate( + 'data.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText', + { + defaultMessage: 'Request Preference', + } + ) + + '', + }, + }), + category: ['search'], + schema: schema.string(), + }, + [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: { + name: i18n.translate('data.advancedSettings.courier.maxRequestsTitle', { + defaultMessage: 'Max Concurrent Shard Requests', + }), + value: 0, + type: 'number', + description: i18n.translate('data.advancedSettings.courier.maxRequestsText', { + defaultMessage: + 'Controls the {maxRequestsLink} setting used for _msearch requests sent by Kibana. ' + + 'Set to 0 to disable this config and use the Elasticsearch default.', + values: { + maxRequestsLink: `max_concurrent_shard_requests`, + }, + }), + category: ['search'], + schema: schema.number(), + }, + [UI_SETTINGS.COURIER_BATCH_SEARCHES]: { + name: i18n.translate('data.advancedSettings.courier.batchSearchesTitle', { + defaultMessage: 'Batch concurrent searches', + }), + value: false, + type: 'boolean', + description: i18n.translate('data.advancedSettings.courier.batchSearchesText', { + defaultMessage: `When disabled, dashboard panels will load individually, and search requests will terminate when users navigate + away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, and + searches will not terminate.`, + }), + deprecation: { + message: i18n.translate('data.advancedSettings.courier.batchSearchesTextDeprecation', { + defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.', + }), + docLinksKey: 'kibanaSearchSettings', + }, + category: ['search'], + schema: schema.boolean(), + }, + [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: { + name: 'Search in frozen indices', + description: `Will include frozen indices in results if enabled. Searching through frozen indices + might increase the search time.`, + value: false, + category: ['search'], + schema: schema.boolean(), + }, + [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: { + name: i18n.translate('data.advancedSettings.histogram.barTargetTitle', { + defaultMessage: 'Target bars', + }), + value: 50, + description: i18n.translate('data.advancedSettings.histogram.barTargetText', { + defaultMessage: + 'Attempt to generate around this many bars when using "auto" interval in date histograms', + }), + schema: schema.number(), + }, + [UI_SETTINGS.HISTOGRAM_MAX_BARS]: { + name: i18n.translate('data.advancedSettings.histogram.maxBarsTitle', { + defaultMessage: 'Maximum bars', + }), + value: 100, + description: i18n.translate('data.advancedSettings.histogram.maxBarsText', { + defaultMessage: + 'Never show more than this many bars in date histograms, scale values if needed', + }), + schema: schema.number(), + }, + [UI_SETTINGS.HISTORY_LIMIT]: { + name: i18n.translate('data.advancedSettings.historyLimitTitle', { + defaultMessage: 'History limit', + }), + value: 10, + description: i18n.translate('data.advancedSettings.historyLimitText', { + defaultMessage: + 'In fields that have history (e.g. query inputs), show this many recent values', + }), + schema: schema.number(), + }, + [UI_SETTINGS.SHORT_DOTS_ENABLE]: { + name: i18n.translate('data.advancedSettings.shortenFieldsTitle', { + defaultMessage: 'Shorten fields', + }), + value: false, + description: i18n.translate('data.advancedSettings.shortenFieldsText', { + defaultMessage: 'Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz', + }), + schema: schema.boolean(), + }, + [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: { + name: i18n.translate('data.advancedSettings.format.defaultTypeMapTitle', { + defaultMessage: 'Field type format name', + }), + value: `{ + "ip": { "id": "ip", "params": {} }, + "date": { "id": "date", "params": {} }, + "date_nanos": { "id": "date_nanos", "params": {}, "es": true }, + "number": { "id": "number", "params": {} }, + "boolean": { "id": "boolean", "params": {} }, + "_source": { "id": "_source", "params": {} }, + "_default_": { "id": "string", "params": {} } +}`, + type: 'json', + description: i18n.translate('data.advancedSettings.format.defaultTypeMapText', { + defaultMessage: + 'Map of the format name to use by default for each field type. ' + + '{defaultFormat} is used if the field type is not mentioned explicitly', + values: { + defaultFormat: '"_default_"', + }, + }), + schema: schema.object({ + ip: schema.object({ + id: schema.string(), + params: schema.object({}), + }), + date: schema.object({ + id: schema.string(), + params: schema.object({}), + }), + date_nanos: schema.object({ + id: schema.string(), + params: schema.object({}), + es: schema.boolean(), + }), + number: schema.object({ + id: schema.string(), + params: schema.object({}), + }), + boolean: schema.object({ + id: schema.string(), + params: schema.object({}), + }), + _source: schema.object({ + id: schema.string(), + params: schema.object({}), + }), + _default_: schema.object({ + id: schema.string(), + params: schema.object({}), + }), + }), + }, + [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: { + name: i18n.translate('data.advancedSettings.format.numberFormatTitle', { + defaultMessage: 'Number format', + }), + value: '0,0.[000]', + type: 'string', + description: i18n.translate('data.advancedSettings.format.numberFormatText', { + defaultMessage: 'Default {numeralFormatLink} for the "number" format', + description: + 'Part of composite text: data.advancedSettings.format.numberFormatText + ' + + 'data.advancedSettings.format.numberFormat.numeralFormatLinkText', + values: { + numeralFormatLink: + '' + + i18n.translate('data.advancedSettings.format.numberFormat.numeralFormatLinkText', { + defaultMessage: 'numeral format', + }) + + '', + }, + }), + schema: schema.string(), + }, + [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: { + name: i18n.translate('data.advancedSettings.format.percentFormatTitle', { + defaultMessage: 'Percent format', + }), + value: '0,0.[000]%', + type: 'string', + description: i18n.translate('data.advancedSettings.format.percentFormatText', { + defaultMessage: 'Default {numeralFormatLink} for the "percent" format', + description: + 'Part of composite text: data.advancedSettings.format.percentFormatText + ' + + 'data.advancedSettings.format.percentFormat.numeralFormatLinkText', + values: { + numeralFormatLink: + '' + + i18n.translate('data.advancedSettings.format.percentFormat.numeralFormatLinkText', { + defaultMessage: 'numeral format', + }) + + '', + }, + }), + schema: schema.string(), + }, + [UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: { + name: i18n.translate('data.advancedSettings.format.bytesFormatTitle', { + defaultMessage: 'Bytes format', + }), + value: '0,0.[0]b', + type: 'string', + description: i18n.translate('data.advancedSettings.format.bytesFormatText', { + defaultMessage: 'Default {numeralFormatLink} for the "bytes" format', + description: + 'Part of composite text: data.advancedSettings.format.bytesFormatText + ' + + 'data.advancedSettings.format.bytesFormat.numeralFormatLinkText', + values: { + numeralFormatLink: + '' + + i18n.translate('data.advancedSettings.format.bytesFormat.numeralFormatLinkText', { + defaultMessage: 'numeral format', + }) + + '', + }, + }), + schema: schema.string(), + }, + [UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN]: { + name: i18n.translate('data.advancedSettings.format.currencyFormatTitle', { + defaultMessage: 'Currency format', + }), + value: '($0,0.[00])', + type: 'string', + description: i18n.translate('data.advancedSettings.format.currencyFormatText', { + defaultMessage: 'Default {numeralFormatLink} for the "currency" format', + description: + 'Part of composite text: data.advancedSettings.format.currencyFormatText + ' + + 'data.advancedSettings.format.currencyFormat.numeralFormatLinkText', + values: { + numeralFormatLink: + '' + + i18n.translate('data.advancedSettings.format.currencyFormat.numeralFormatLinkText', { + defaultMessage: 'numeral format', + }) + + '', + }, + }), + schema: schema.string(), + }, + [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: { + name: i18n.translate('data.advancedSettings.format.formattingLocaleTitle', { + defaultMessage: 'Formatting locale', + }), + value: 'en', + type: 'select', + options: numeralLanguageIds, + optionLabels: Object.fromEntries( + numeralLanguages.map((language: Record) => [language.id, language.name]) + ), + description: i18n.translate('data.advancedSettings.format.formattingLocaleText', { + defaultMessage: `{numeralLanguageLink} locale`, + description: + 'Part of composite text: data.advancedSettings.format.formattingLocale.numeralLanguageLinkText + ' + + 'data.advancedSettings.format.formattingLocaleText', + values: { + numeralLanguageLink: + '' + + i18n.translate( + 'data.advancedSettings.format.formattingLocale.numeralLanguageLinkText', + { + defaultMessage: 'Numeral language', + } + ) + + '', + }, + }), + schema: schema.string(), + }, + [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`, + }), + requiresPageReload: true, + schema: schema.object({ + pause: schema.boolean(), + value: schema.number(), + }), + }, + [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: { + name: i18n.translate('data.advancedSettings.timepicker.quickRangesTitle', { + defaultMessage: 'Time filter quick ranges', + }), + value: JSON.stringify( + [ + { + from: 'now/d', + to: 'now/d', + display: i18n.translate('data.advancedSettings.timepicker.today', { + defaultMessage: 'Today', + }), + }, + { + from: 'now/w', + to: 'now/w', + display: i18n.translate('data.advancedSettings.timepicker.thisWeek', { + defaultMessage: 'This week', + }), + }, + { + from: 'now-15m', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last15Minutes', { + defaultMessage: 'Last 15 minutes', + }), + }, + { + from: 'now-30m', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last30Minutes', { + defaultMessage: 'Last 30 minutes', + }), + }, + { + from: 'now-1h', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last1Hour', { + defaultMessage: 'Last 1 hour', + }), + }, + { + from: 'now-24h', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last24Hours', { + defaultMessage: 'Last 24 hours', + }), + }, + { + from: 'now-7d', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last7Days', { + defaultMessage: 'Last 7 days', + }), + }, + { + from: 'now-30d', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last30Days', { + defaultMessage: 'Last 30 days', + }), + }, + { + from: 'now-90d', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last90Days', { + defaultMessage: 'Last 90 days', + }), + }, + { + from: 'now-1y', + to: 'now', + display: i18n.translate('data.advancedSettings.timepicker.last1Year', { + defaultMessage: 'Last 1 year', + }), + }, + ], + null, + 2 + ), + type: 'json', + description: i18n.translate('data.advancedSettings.timepicker.quickRangesText', { + defaultMessage: + 'The list of ranges to show in the Quick section of the time filter. This should be an array of objects, ' + + 'with each object containing "from", "to" (see {acceptedFormatsLink}), and ' + + '"display" (the title to be displayed).', + description: + 'Part of composite text: data.advancedSettings.timepicker.quickRangesText + ' + + 'data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', + values: { + acceptedFormatsLink: + `` + + i18n.translate('data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', { + defaultMessage: 'accepted formats', + }) + + '', + }, + }), + schema: schema.arrayOf( + schema.object({ + from: schema.string(), + to: schema.string(), + display: schema.string(), + }) + ), + }, + [UI_SETTINGS.INDEXPATTERN_PLACEHOLDER]: { + name: i18n.translate('data.advancedSettings.indexPatternPlaceholderTitle', { + defaultMessage: 'Index pattern placeholder', + }), + value: '', + description: i18n.translate('data.advancedSettings.indexPatternPlaceholderText', { + defaultMessage: + 'The placeholder for the "Index pattern name" field in "Management > Index Patterns > Create Index Pattern".', + }), + schema: schema.string(), + }, + [UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT]: { + name: i18n.translate('data.advancedSettings.pinFiltersTitle', { + defaultMessage: 'Pin filters by default', + }), + value: false, + description: i18n.translate('data.advancedSettings.pinFiltersText', { + defaultMessage: 'Whether the filters should have a global state (be pinned) by default', + }), + schema: schema.boolean(), + }, + [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: { + name: i18n.translate('data.advancedSettings.suggestFilterValuesTitle', { + defaultMessage: 'Filter editor suggest values', + description: '"Filter editor" refers to the UI you create filters in.', + }), + value: true, + description: i18n.translate('data.advancedSettings.suggestFilterValuesText', { + defaultMessage: + 'Set this property to false to prevent the filter editor from suggesting values for fields.', + }), + schema: schema.boolean(), + }, + }; +} diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index 4e9128c327fe1..1a9d6bf4848f4 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -60,7 +60,7 @@ function DevToolsWrapper({ return (
- {devTools.map(currentDevTool => ( + {devTools.map((currentDevTool) => ( { + ref={async (element) => { if ( element && (mountedTool.current === null || @@ -172,13 +172,13 @@ export function renderApp( {devTools // Only create routes for devtools that are not disabled - .filter(devTool => !devTool.isDisabled()) - .map(devTool => ( + .filter((devTool) => !devTool.isDisabled()) + .map((devTool) => ( ( + render={(props) => ( { +const k7Breadcrumbs = ($route) => { const { indexPattern } = $route.current.locals; const { id } = $route.current.params; @@ -44,7 +44,7 @@ const k7Breadcrumbs = $route => { ]; }; -getAngularModule().config($routeProvider => { +getAngularModule().config(($routeProvider) => { $routeProvider // deprecated route, kept for compatibility // should be removed in the future @@ -97,7 +97,7 @@ function ContextAppRouteController($routeParams, $scope, $route) { 'contextAppRoute.state.predecessorCount', 'contextAppRoute.state.successorCount', ], - newValues => { + (newValues) => { const [columns, predecessorCount, successorCount] = newValues; if (Array.isArray(columns) && predecessorCount >= 0 && successorCount >= 0) { setAppState({ columns, predecessorCount, successorCount }); diff --git a/src/plugins/discover/public/application/angular/context/api/_stubs.js b/src/plugins/discover/public/application/angular/context/api/_stubs.js index acd609df203e3..35ddf396c2dba 100644 --- a/src/plugins/discover/public/application/angular/context/api/_stubs.js +++ b/src/plugins/discover/public/application/angular/context/api/_stubs.js @@ -22,7 +22,7 @@ import moment from 'moment'; export function createIndexPatternsStub() { return { - get: sinon.spy(indexPatternId => + get: sinon.spy((indexPatternId) => Promise.resolve({ id: indexPatternId, isTimeNanosBased: () => false, @@ -48,7 +48,7 @@ export function createSearchSourceStub(hits, timeField) { searchSourceStub.setParent = sinon.spy(() => searchSourceStub); searchSourceStub.setField = sinon.spy(() => searchSourceStub); - searchSourceStub.getField = sinon.spy(key => { + searchSourceStub.getField = sinon.spy((key) => { const previousSetCall = searchSourceStub.setField.withArgs(key).lastCall; return previousSetCall ? previousSetCall.args[1] : null; }); @@ -83,7 +83,7 @@ export function createContextSearchSourceStub(hits, timeField = '@timestamp') { : (first, second) => second[timeField] - first[timeField]; const filteredHits = searchSourceStub._stubHits .filter( - hit => + (hit) => moment(hit[timeField]).isSameOrAfter(timeRange.gte) && moment(hit[timeField]).isSameOrBefore(timeRange.lte) ) diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.test.js b/src/plugins/discover/public/application/angular/context/api/anchor.test.js index 757e74589555a..993aefc4f59e3 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.test.js +++ b/src/plugins/discover/public/application/angular/context/api/anchor.test.js @@ -21,8 +21,8 @@ import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; import { fetchAnchorProvider } from './anchor'; -describe('context app', function() { - describe('function fetchAnchor', function() { +describe('context app', function () { + describe('function fetchAnchor', function () { let fetchAnchor; let searchSourceStub; @@ -31,7 +31,7 @@ describe('context app', function() { fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); }); - it('should use the `fetch` method of the SearchSource', function() { + it('should use the `fetch` method of the SearchSource', function () { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, @@ -40,7 +40,7 @@ describe('context app', function() { }); }); - it('should configure the SearchSource to not inherit from the implicit root', function() { + it('should configure the SearchSource to not inherit from the implicit root', function () { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, @@ -51,7 +51,7 @@ describe('context app', function() { }); }); - it('should set the SearchSource index pattern', function() { + it('should set the SearchSource index pattern', function () { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, @@ -61,7 +61,7 @@ describe('context app', function() { }); }); - it('should set the SearchSource version flag to true', function() { + it('should set the SearchSource version flag to true', function () { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, @@ -72,7 +72,7 @@ describe('context app', function() { }); }); - it('should set the SearchSource size to 1', function() { + it('should set the SearchSource size to 1', function () { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, @@ -83,7 +83,7 @@ describe('context app', function() { }); }); - it('should set the SearchSource query to an ids query', function() { + it('should set the SearchSource query to an ids query', function () { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, @@ -105,7 +105,7 @@ describe('context app', function() { }); }); - it('should set the SearchSource sort order', function() { + it('should set the SearchSource sort order', function () { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, @@ -116,7 +116,7 @@ describe('context app', function() { }); }); - it('should reject with an error when no hits were found', function() { + it('should reject with an error when no hits were found', function () { searchSourceStub._stubHits = []; return fetchAnchor('INDEX_PATTERN_ID', 'id', [ @@ -126,19 +126,19 @@ describe('context app', function() { () => { expect().fail('expected the promise to be rejected'); }, - error => { + (error) => { expect(error).toBeInstanceOf(Error); } ); }); - it('should return the first hit after adding an anchor marker', function() { + it('should return the first hit after adding an anchor marker', function () { searchSourceStub._stubHits = [{ property1: 'value1' }, { property2: 'value2' }]; return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, { _doc: 'desc' }, - ]).then(anchorDocument => { + ]).then((anchorDocument) => { expect(anchorDocument).toHaveProperty('property1', 'value1'); expect(anchorDocument).toHaveProperty('$$_isAnchor', true); }); diff --git a/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.js b/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.js index ebd4af536aabd..fcde2ade0b2c6 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.js +++ b/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.js @@ -29,8 +29,8 @@ const ANCHOR_TIMESTAMP_3 = new Date(MS_PER_DAY * 3).toJSON(); const ANCHOR_TIMESTAMP_1000 = new Date(MS_PER_DAY * 1000).toJSON(); const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); -describe('context app', function() { - describe('function fetchPredecessors', function() { +describe('context app', function () { + describe('function fetchPredecessors', function () { let fetchPredecessors; let mockSearchSource; @@ -77,7 +77,7 @@ describe('context app', function() { }; }); - it('should perform exactly one query when enough hits are returned', function() { + it('should perform exactly one query when enough hits are returned', function () { mockSearchSource._stubHits = [ mockSearchSource._createStubHit(MS_PER_DAY * 3000 + 2), mockSearchSource._createStubHit(MS_PER_DAY * 3000 + 1), @@ -96,13 +96,13 @@ describe('context app', function() { 0, 3, [] - ).then(hits => { + ).then((hits) => { expect(mockSearchSource.fetch.calledOnce).toBe(true); expect(hits).toEqual(mockSearchSource._stubHits.slice(0, 3)); }); }); - it('should perform multiple queries with the last being unrestricted when too few hits are returned', function() { + it('should perform multiple queries with the last being unrestricted when too few hits are returned', function () { mockSearchSource._stubHits = [ mockSearchSource._createStubHit(MS_PER_DAY * 3010), mockSearchSource._createStubHit(MS_PER_DAY * 3002), @@ -121,7 +121,7 @@ describe('context app', function() { 0, 6, [] - ).then(hits => { + ).then((hits) => { const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => @@ -141,7 +141,7 @@ describe('context app', function() { }); }); - it('should perform multiple queries until the expected hit count is returned', function() { + it('should perform multiple queries until the expected hit count is returned', function () { mockSearchSource._stubHits = [ mockSearchSource._createStubHit(MS_PER_DAY * 1700), mockSearchSource._createStubHit(MS_PER_DAY * 1200), @@ -159,7 +159,7 @@ describe('context app', function() { 0, 3, [] - ).then(hits => { + ).then((hits) => { const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => @@ -175,7 +175,7 @@ describe('context app', function() { }); }); - it('should return an empty array when no hits were found', function() { + it('should return an empty array when no hits were found', function () { return fetchPredecessors( 'INDEX_PATTERN_ID', '@timestamp', @@ -186,12 +186,12 @@ describe('context app', function() { 0, 3, [] - ).then(hits => { + ).then((hits) => { expect(hits).toEqual([]); }); }); - it('should configure the SearchSource to not inherit from the implicit root', function() { + it('should configure the SearchSource to not inherit from the implicit root', function () { return fetchPredecessors( 'INDEX_PATTERN_ID', '@timestamp', @@ -209,7 +209,7 @@ describe('context app', function() { }); }); - it('should set the tiebreaker sort order to the opposite as the time field', function() { + it('should set the tiebreaker sort order to the opposite as the time field', function () { return fetchPredecessors( 'INDEX_PATTERN_ID', '@timestamp', diff --git a/src/plugins/discover/public/application/angular/context/api/context.successors.test.js b/src/plugins/discover/public/application/angular/context/api/context.successors.test.js index 452d0cc9fd1a0..0f84aa82a989a 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.successors.test.js +++ b/src/plugins/discover/public/application/angular/context/api/context.successors.test.js @@ -30,8 +30,8 @@ const ANCHOR_TIMESTAMP = new Date(MS_PER_DAY).toJSON(); const ANCHOR_TIMESTAMP_3 = new Date(MS_PER_DAY * 3).toJSON(); const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); -describe('context app', function() { - describe('function fetchSuccessors', function() { +describe('context app', function () { + describe('function fetchSuccessors', function () { let fetchSuccessors; let mockSearchSource; @@ -78,7 +78,7 @@ describe('context app', function() { }; }); - it('should perform exactly one query when enough hits are returned', function() { + it('should perform exactly one query when enough hits are returned', function () { mockSearchSource._stubHits = [ mockSearchSource._createStubHit(MS_PER_DAY * 5000), mockSearchSource._createStubHit(MS_PER_DAY * 4000), @@ -97,13 +97,13 @@ describe('context app', function() { 0, 3, [] - ).then(hits => { + ).then((hits) => { expect(mockSearchSource.fetch.calledOnce).toBe(true); expect(hits).toEqual(mockSearchSource._stubHits.slice(-3)); }); }); - it('should perform multiple queries with the last being unrestricted when too few hits are returned', function() { + it('should perform multiple queries with the last being unrestricted when too few hits are returned', function () { mockSearchSource._stubHits = [ mockSearchSource._createStubHit(MS_PER_DAY * 3010), mockSearchSource._createStubHit(MS_PER_DAY * 3002), @@ -122,7 +122,7 @@ describe('context app', function() { 0, 6, [] - ).then(hits => { + ).then((hits) => { const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => @@ -142,7 +142,7 @@ describe('context app', function() { }); }); - it('should perform multiple queries until the expected hit count is returned', function() { + it('should perform multiple queries until the expected hit count is returned', function () { mockSearchSource._stubHits = [ mockSearchSource._createStubHit(MS_PER_DAY * 3000), mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 1), @@ -162,7 +162,7 @@ describe('context app', function() { 0, 4, [] - ).then(hits => { + ).then((hits) => { const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => @@ -179,7 +179,7 @@ describe('context app', function() { }); }); - it('should return an empty array when no hits were found', function() { + it('should return an empty array when no hits were found', function () { return fetchSuccessors( 'INDEX_PATTERN_ID', '@timestamp', @@ -190,12 +190,12 @@ describe('context app', function() { 0, 3, [] - ).then(hits => { + ).then((hits) => { expect(hits).toEqual([]); }); }); - it('should configure the SearchSource to not inherit from the implicit root', function() { + it('should configure the SearchSource to not inherit from the implicit root', function () { return fetchSuccessors( 'INDEX_PATTERN_ID', '@timestamp', @@ -213,7 +213,7 @@ describe('context app', function() { }); }); - it('should set the tiebreaker sort order to the same as the time field', function() { + it('should set the tiebreaker sort order to the same as the time field', function () { return fetchSuccessors( 'INDEX_PATTERN_ID', '@timestamp', diff --git a/src/plugins/discover/public/application/angular/context/api/context.ts b/src/plugins/discover/public/application/angular/context/api/context.ts index a47005b640538..e244176914a9b 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -37,7 +37,7 @@ export type EsHitRecordList = EsHitRecord[]; const DAY_MILLIS = 24 * 60 * 60 * 1000; // look from 1 day up to 10000 days into the past and future -const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map(days => days * DAY_MILLIS); +const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map((days) => days * DAY_MILLIS); function fetchContextProvider(indexPatterns: IndexPatternsContract) { return { diff --git a/src/plugins/discover/public/application/angular/context/api/utils/date_conversion.test.ts b/src/plugins/discover/public/application/angular/context/api/utils/date_conversion.test.ts index 223b174718296..7cb3ac1f3f745 100644 --- a/src/plugins/discover/public/application/angular/context/api/utils/date_conversion.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/date_conversion.test.ts @@ -18,14 +18,14 @@ */ import { extractNanos } from './date_conversion'; -describe('function extractNanos', function() { - test('extract nanos of 2014-01-01', function() { +describe('function extractNanos', function () { + test('extract nanos of 2014-01-01', function () { expect(extractNanos('2014-01-01')).toBe('000000000'); }); - test('extract nanos of 2014-01-01T12:12:12.234Z', function() { + test('extract nanos of 2014-01-01T12:12:12.234Z', function () { expect(extractNanos('2014-01-01T12:12:12.234Z')).toBe('234000000'); }); - test('extract nanos of 2014-01-01T12:12:12.234123321Z', function() { + test('extract nanos of 2014-01-01T12:12:12.234123321Z', function () { expect(extractNanos('2014-01-01T12:12:12.234123321Z')).toBe('234123321'); }); }); diff --git a/src/plugins/discover/public/application/angular/context/api/utils/generate_intervals.ts b/src/plugins/discover/public/application/angular/context/api/utils/generate_intervals.ts index 1497f54aa5079..81533917dfff8 100644 --- a/src/plugins/discover/public/application/angular/context/api/utils/generate_intervals.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/generate_intervals.ts @@ -50,5 +50,5 @@ export function generateIntervals( ? 1 : -1; // ending with `null` opens the last interval - return asPairs([...offsets.map(offset => startTime + offset * offsetSign), null]); + return asPairs([...offsets.map((offset) => startTime + offset * offsetSign), null]); } diff --git a/src/plugins/discover/public/application/angular/context/api/utils/sorting.test.ts b/src/plugins/discover/public/application/angular/context/api/utils/sorting.test.ts index 350a0a8ede8d5..20b868367471c 100644 --- a/src/plugins/discover/public/application/angular/context/api/utils/sorting.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/sorting.test.ts @@ -18,8 +18,8 @@ */ import { reverseSortDir, SortDirection } from './sorting'; -describe('function reverseSortDir', function() { - test('reverse a given sort direction', function() { +describe('function reverseSortDir', function () { + test('reverse a given sort direction', function () { expect(reverseSortDir(SortDirection.asc)).toBe(SortDirection.desc); expect(reverseSortDir(SortDirection.desc)).toBe(SortDirection.asc); }); diff --git a/src/plugins/discover/public/application/angular/context/api/utils/sorting.ts b/src/plugins/discover/public/application/angular/context/api/utils/sorting.ts index ef1be8d48d338..2a5d6312921b8 100644 --- a/src/plugins/discover/public/application/angular/context/api/utils/sorting.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/sorting.ts @@ -36,7 +36,7 @@ const META_FIELD_NAMES: string[] = ['_seq_no', '_doc', '_uid']; */ export function getFirstSortableField(indexPattern: IndexPattern, fieldNames: string[]) { const sortableFields = fieldNames.filter( - fieldName => + (fieldName) => META_FIELD_NAMES.includes(fieldName) || // @ts-ignore (indexPattern.fields.getByName(fieldName) || { sortable: false }).sortable diff --git a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx index 325cfb2c9f0bb..8976f8ea3c107 100644 --- a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx @@ -25,7 +25,7 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from '../../query_parameters/constants'; describe('Test Discover Context ActionBar for successor | predecessor records', () => { - ['successors', 'predecessors'].forEach(type => { + ['successors', 'predecessors'].forEach((type) => { const onChangeCount = jest.fn(); const props = { defaultStepSize: 5, diff --git a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx index 97a29ab21c581..ac88d2aa36696 100644 --- a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx @@ -131,7 +131,7 @@ export function ActionBar({ disabled={isDisabled} min={MIN_CONTEXT_SIZE} max={MAX_CONTEXT_SIZE} - onChange={ev => { + onChange={(ev) => { setNewDocCount(ev.target.valueAsNumber); }} onBlur={() => { diff --git a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_directive.ts b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_directive.ts index b705b4e4faeb5..2a77b762c2966 100644 --- a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_directive.ts +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_directive.ts @@ -19,6 +19,6 @@ import { getAngularModule } from '../../../../../kibana_services'; import { ActionBar } from './action_bar'; -getAngularModule().directive('contextActionBar', function(reactDirective: any) { +getAngularModule().directive('contextActionBar', function (reactDirective: any) { return reactDirective(ActionBar); }); diff --git a/src/plugins/discover/public/application/angular/context/query/actions.js b/src/plugins/discover/public/application/angular/context/query/actions.js index 6f8d5fe64f831..0e057e0a715c4 100644 --- a/src/plugins/discover/public/application/angular/context/query/actions.js +++ b/src/plugins/discover/public/application/angular/context/query/actions.js @@ -37,24 +37,24 @@ export function QueryActionsProvider(Promise) { indexPatterns ); - const setFailedStatus = state => (subject, details = {}) => + const setFailedStatus = (state) => (subject, details = {}) => (state.loadingStatus[subject] = { status: LOADING_STATUS.FAILED, reason: FAILURE_REASONS.UNKNOWN, ...details, }); - const setLoadedStatus = state => subject => + const setLoadedStatus = (state) => (subject) => (state.loadingStatus[subject] = { status: LOADING_STATUS.LOADED, }); - const setLoadingStatus = state => subject => + const setLoadingStatus = (state) => (subject) => (state.loadingStatus[subject] = { status: LOADING_STATUS.LOADING, }); - const fetchAnchorRow = state => () => { + const fetchAnchorRow = (state) => () => { const { queryParameters: { indexPatternId, anchorId, sort, tieBreakerField }, } = state; @@ -72,12 +72,12 @@ export function QueryActionsProvider(Promise) { return Promise.try(() => fetchAnchor(indexPatternId, anchorId, [_.zipObject([sort]), { [tieBreakerField]: sort[1] }]) ).then( - anchorDocument => { + (anchorDocument) => { setLoadedStatus(state)('anchor'); state.rows.anchor = anchorDocument; return anchorDocument; }, - error => { + (error) => { setFailedStatus(state)('anchor', { error }); getServices().toastNotifications.addDanger({ title: i18n.translate('discover.context.unableToLoadAnchorDocumentDescription', { @@ -125,12 +125,12 @@ export function QueryActionsProvider(Promise) { filters ) ).then( - documents => { + (documents) => { setLoadedStatus(state)(type); state.rows[type] = documents; return documents; }, - error => { + (error) => { setFailedStatus(state)(type, { error }); getServices().toastNotifications.addDanger({ title: i18n.translate('discover.context.unableToLoadDocumentDescription', { @@ -143,36 +143,36 @@ export function QueryActionsProvider(Promise) { ); }; - const fetchContextRows = state => () => + const fetchContextRows = (state) => () => Promise.all([ fetchSurroundingRows('predecessors', state), fetchSurroundingRows('successors', state), ]); - const fetchAllRows = state => () => + const fetchAllRows = (state) => () => Promise.try(fetchAnchorRow(state)).then(fetchContextRows(state)); - const fetchContextRowsWithNewQueryParameters = state => queryParameters => { + const fetchContextRowsWithNewQueryParameters = (state) => (queryParameters) => { setQueryParameters(state)(queryParameters); return fetchContextRows(state)(); }; - const fetchAllRowsWithNewQueryParameters = state => queryParameters => { + const fetchAllRowsWithNewQueryParameters = (state) => (queryParameters) => { setQueryParameters(state)(queryParameters); return fetchAllRows(state)(); }; - const fetchGivenPredecessorRows = state => count => { + const fetchGivenPredecessorRows = (state) => (count) => { setPredecessorCount(state)(count); return fetchSurroundingRows('predecessors', state); }; - const fetchGivenSuccessorRows = state => count => { + const fetchGivenSuccessorRows = (state) => (count) => { setSuccessorCount(state)(count); return fetchSurroundingRows('successors', state); }; - const setAllRows = state => (predecessorRows, anchorRow, successorRows) => + const setAllRows = (state) => (predecessorRows, anchorRow, successorRows) => (state.rows.all = [ ...(predecessorRows || []), ...(anchorRow ? [anchorRow] : []), diff --git a/src/plugins/discover/public/application/angular/context/query_parameters/actions.js b/src/plugins/discover/public/application/angular/context/query_parameters/actions.js index 4f86dea08fe84..fcd4b8ac02cfb 100644 --- a/src/plugins/discover/public/application/angular/context/query_parameters/actions.js +++ b/src/plugins/discover/public/application/angular/context/query_parameters/actions.js @@ -23,28 +23,28 @@ import { esFilters } from '../../../../../../data/public'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, QUERY_PARAMETER_KEYS } from './constants'; export function getQueryParameterActions(filterManager, indexPatterns) { - const setPredecessorCount = state => predecessorCount => + const setPredecessorCount = (state) => (predecessorCount) => (state.queryParameters.predecessorCount = clamp( MIN_CONTEXT_SIZE, MAX_CONTEXT_SIZE, predecessorCount )); - const setSuccessorCount = state => successorCount => + const setSuccessorCount = (state) => (successorCount) => (state.queryParameters.successorCount = clamp( MIN_CONTEXT_SIZE, MAX_CONTEXT_SIZE, successorCount )); - const setQueryParameters = state => queryParameters => + const setQueryParameters = (state) => (queryParameters) => Object.assign(state.queryParameters, _.pick(queryParameters, QUERY_PARAMETER_KEYS)); - const updateFilters = () => filters => { + const updateFilters = () => (filters) => { filterManager.setFilters(filters); }; - const addFilter = state => async (field, values, operation) => { + const addFilter = (state) => async (field, values, operation) => { const indexPatternId = state.queryParameters.indexPatternId; const newFilters = esFilters.generateFilters( filterManager, diff --git a/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts b/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts index 00747fcc81227..6309c61d3bb34 100644 --- a/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts +++ b/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts @@ -48,7 +48,7 @@ beforeEach(() => { }; }); -describe('context query_parameter actions', function() { +describe('context query_parameter actions', function () { describe('action addFilter', () => { it('should pass the given arguments to the filterManager', () => { const { addFilter } = getQueryParameterActions(filterManager); @@ -90,7 +90,7 @@ describe('context query_parameter actions', function() { }); }); describe('action setSuccessorCount', () => { - it('should set the successorCount to the given value', function() { + it('should set the successorCount to the given value', function () { const { setSuccessorCount } = getQueryParameterActions(filterManager); setSuccessorCount(state)(20); @@ -109,10 +109,10 @@ describe('context query_parameter actions', function() { expect(state.queryParameters.successorCount).toBe(10000); }); }); - describe('action setQueryParameters', function() { + describe('action setQueryParameters', function () { const { setQueryParameters } = getQueryParameterActions(filterManager); - it('should update the queryParameters with valid properties from the given object', function() { + it('should update the queryParameters with valid properties from the given object', function () { const newState = { ...state, queryParameters: { @@ -144,7 +144,7 @@ describe('context query_parameter actions', function() { }); }); - it('should ignore invalid properties', function() { + it('should ignore invalid properties', function () { const newState = { ...state }; setQueryParameters(newState)({ diff --git a/src/plugins/discover/public/application/angular/context_app.js b/src/plugins/discover/public/application/angular/context_app.js index acb94bf97d21f..f698ed84a8948 100644 --- a/src/plugins/discover/public/application/angular/context_app.js +++ b/src/plugins/discover/public/application/angular/context_app.js @@ -69,7 +69,7 @@ function ContextAppController($scope, Private) { ...queryParameterActions, ...queryActions, }, - action => (...args) => action(this.state)(...args) + (action) => (...args) => action(this.state)(...args) ); this.constants = { @@ -83,7 +83,7 @@ function ContextAppController($scope, Private) { () => this.state.rows.anchor, () => this.state.rows.successors, ], - newValues => this.actions.setAllRows(...newValues) + (newValues) => this.actions.setAllRows(...newValues) ); /** @@ -94,7 +94,7 @@ function ContextAppController($scope, Private) { ..._.pick(this, QUERY_PARAMETER_KEYS), indexPatternId: this.indexPattern.id, }), - newQueryParameters => { + (newQueryParameters) => { const { queryParameters } = this.state; if ( newQueryParameters.indexPatternId !== queryParameters.indexPatternId || @@ -120,7 +120,7 @@ function ContextAppController($scope, Private) { predecessorCount: this.state.queryParameters.predecessorCount, successorCount: this.state.queryParameters.successorCount, }), - newParameters => { + (newParameters) => { _.assign(this, newParameters); } ); diff --git a/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/collapsible_sidebar.ts b/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/collapsible_sidebar.ts index 5b6de7f16d444..16fbb0af9f3fd 100644 --- a/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/collapsible_sidebar.ts +++ b/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/collapsible_sidebar.ts @@ -27,7 +27,7 @@ interface LazyScope extends IScope { export function CollapsibleSidebarProvider() { // simply a list of all of all of angulars .col-md-* classes except 12 - const listOfWidthClasses = _.times(11, function(i) { + const listOfWidthClasses = _.times(11, function (i) { return 'col-md-' + i; }); @@ -60,7 +60,7 @@ export function CollapsibleSidebarProvider() { // If there is are only two elements we can assume the other one will take 100% of the width. const hasSingleSibling = $siblings.length === 1 && siblingsClass; - $collapser.on('click', function() { + $collapser.on('click', function () { if (isCollapsed) { isCollapsed = false; $elem.removeClass('closed'); diff --git a/src/plugins/discover/public/application/angular/directives/debounce/debounce.js b/src/plugins/discover/public/application/angular/directives/debounce/debounce.js index 54507f673c2d6..586e8ed4fab59 100644 --- a/src/plugins/discover/public/application/angular/directives/debounce/debounce.js +++ b/src/plugins/discover/public/application/angular/directives/debounce/debounce.js @@ -22,7 +22,7 @@ import _ from 'lodash'; // borrowed heavily from https://github.com/shahata/angular-debounce export function DebounceProviderTimeout($timeout) { - return function(func, wait, options) { + return function (func, wait, options) { let timeout; let args; let self; @@ -37,7 +37,7 @@ export function DebounceProviderTimeout($timeout) { self = this; args = arguments; - const later = function() { + const later = function () { timeout = null; if (!options.leading || options.trailing) { result = func.apply(self, args); @@ -58,7 +58,7 @@ export function DebounceProviderTimeout($timeout) { return result; } - debounce.cancel = function() { + debounce.cancel = function () { $timeout.cancel(timeout); timeout = null; }; diff --git a/src/plugins/discover/public/application/angular/directives/debounce/debounce.test.ts b/src/plugins/discover/public/application/angular/directives/debounce/debounce.test.ts index bc08d8566d48a..ccdee153002e4 100644 --- a/src/plugins/discover/public/application/angular/directives/debounce/debounce.test.ts +++ b/src/plugins/discover/public/application/angular/directives/debounce/debounce.test.ts @@ -31,7 +31,7 @@ import { navigationPluginMock } from '../../../../../../navigation/public/mocks' import { dataPluginMock } from '../../../../../../data/public/mocks'; import { initAngularBootstrap } from '../../../../../../kibana_legacy/public'; -describe('debounce service', function() { +describe('debounce service', function () { let debounce: (fn: () => void, timeout: number, options?: any) => any; let debounceFromProvider: (fn: () => void, timeout: number, options?: any) => any; let $timeout: ITimeoutService; @@ -61,7 +61,7 @@ describe('debounce service', function() { ); }); - it('should have a cancel method', function() { + it('should have a cancel method', function () { const bouncer = debounce(() => {}, 100); const bouncerFromProvider = debounceFromProvider(() => {}, 100); @@ -69,13 +69,13 @@ describe('debounce service', function() { expect(bouncerFromProvider).toHaveProperty('cancel'); }); - describe('delayed execution', function() { + describe('delayed execution', function () { const sandbox = sinon.createSandbox(); beforeEach(() => sandbox.useFakeTimers()); afterEach(() => sandbox.restore()); - it('should delay execution', function() { + it('should delay execution', function () { const bouncer = debounce(spy, 100); const bouncerFromProvider = debounceFromProvider(spy, 100); @@ -92,7 +92,7 @@ describe('debounce service', function() { sinon.assert.calledOnce(spy); }); - it('should fire on leading edge', function() { + it('should fire on leading edge', function () { const bouncer = debounce(spy, 100, { leading: true }); const bouncerFromProvider = debounceFromProvider(spy, 100, { leading: true }); @@ -109,7 +109,7 @@ describe('debounce service', function() { sinon.assert.calledTwice(spy); }); - it('should only fire on leading edge', function() { + it('should only fire on leading edge', function () { const bouncer = debounce(spy, 100, { leading: true, trailing: false }); const bouncerFromProvider = debounceFromProvider(spy, 100, { leading: true, @@ -129,7 +129,7 @@ describe('debounce service', function() { sinon.assert.calledOnce(spy); }); - it('should reset delayed execution', function() { + it('should reset delayed execution', function () { const cancelSpy = sinon.spy($timeout, 'cancel'); const bouncer = debounce(spy, 100); const bouncerFromProvider = debounceFromProvider(spy, 100); @@ -157,8 +157,8 @@ describe('debounce service', function() { }); }); - describe('cancel', function() { - it('should cancel the $timeout', function() { + describe('cancel', function () { + it('should cancel the $timeout', function () { const cancelSpy = sinon.spy($timeout, 'cancel'); const bouncer = debounce(spy, 100); const bouncerFromProvider = debounceFromProvider(spy, 100); diff --git a/src/plugins/discover/public/application/angular/directives/fixed_scroll.js b/src/plugins/discover/public/application/angular/directives/fixed_scroll.js index bc159c14a16a7..182b4aeca9a23 100644 --- a/src/plugins/discover/public/application/angular/directives/fixed_scroll.js +++ b/src/plugins/discover/public/application/angular/directives/fixed_scroll.js @@ -33,7 +33,7 @@ export function FixedScrollProvider(Private) { return { restrict: 'A', - link: function($scope, $el) { + link: function ($scope, $el) { let $window = $(window); let $scroller = $('
').height(SCROLLER_HEIGHT); @@ -69,12 +69,12 @@ export function FixedScrollProvider(Private) { } $from.on('scroll', handler); - return function() { + return function () { $from.off('scroll', handler); }; } - unlisten = _.flow(bind($el, $scroller), bind($scroller, $el), function() { + unlisten = _.flow(bind($el, $scroller), bind($scroller, $el), function () { unlisten = _.noop; }); } @@ -141,7 +141,7 @@ export function FixedScrollProvider(Private) { $scope.$watch(debouncedCheckWidth); // cleanup when the scope is destroyed - $scope.$on('$destroy', function() { + $scope.$on('$destroy', function () { cleanUp(); debouncedCheckWidth.cancel(); $scroller = $window = null; diff --git a/src/plugins/discover/public/application/angular/directives/histogram.tsx b/src/plugins/discover/public/application/angular/directives/histogram.tsx index d856be58958f1..8b646106fe52f 100644 --- a/src/plugins/discover/public/application/angular/directives/histogram.tsx +++ b/src/plugins/discover/public/application/angular/directives/histogram.tsx @@ -66,10 +66,7 @@ function findIntervalFromDuration( ) { const date = moment.tz(dateValue, timeZone); const startOfDate = moment.tz(date, timeZone).startOf(esUnit); - const endOfDate = moment - .tz(date, timeZone) - .startOf(esUnit) - .add(esValue, esUnit); + const endOfDate = moment.tz(date, timeZone).startOf(esUnit).add(esValue, esUnit); return endOfDate.valueOf() - startOfDate.valueOf(); } @@ -242,10 +239,7 @@ export class DiscoverHistogram extends Component reactDirective(DiscoverNoResults)); +app.directive('discoverNoResults', (reactDirective) => reactDirective(DiscoverNoResults)); -app.directive('discoverUninitialized', reactDirective => reactDirective(DiscoverUninitialized)); +app.directive('discoverUninitialized', (reactDirective) => reactDirective(DiscoverUninitialized)); -app.directive('discoverHistogram', reactDirective => reactDirective(DiscoverHistogram)); +app.directive('discoverHistogram', (reactDirective) => reactDirective(DiscoverHistogram)); diff --git a/src/plugins/discover/public/application/angular/discover.html b/src/plugins/discover/public/application/angular/discover.html index d70d5dad9130b..022c39afff27f 100644 --- a/src/plugins/discover/public/application/angular/discover.html +++ b/src/plugins/discover/public/application/angular/discover.html @@ -59,18 +59,7 @@

{{screenTitle}}

fetch-error="fetchError" > -
-

-
-
-
+
diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 84b4b2254e703..88885b3eb211b 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -72,6 +72,7 @@ import { syncQueryStateWithUrl, getDefaultQuery, search, + UI_SETTINGS, } from '../../../../data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; import { addFatalError } from '../../../../kibana_legacy/public'; @@ -91,13 +92,13 @@ const fetchStatuses = { const app = getAngularModule(); -app.config($routeProvider => { +app.config(($routeProvider) => { const defaults = { requireDefaultIndex: true, requireUICapability: 'discover.show', k7Breadcrumbs: ($route, $injector) => $injector.invoke($route.current.params.id ? getSavedSearchBreadcrumbs : getRootBreadcrumbs), - badge: uiCapabilities => { + badge: (uiCapabilities) => { if (uiCapabilities.discover.save) { return undefined; } @@ -118,14 +119,14 @@ app.config($routeProvider => { template: indexTemplate, reloadOnSearch: false, resolve: { - savedObjects: function($route, Promise) { + savedObjects: function ($route, Promise) { const history = getHistory(); const savedSearchId = $route.current.params.id; return data.indexPatterns.ensureDefaultIndexPattern(history).then(() => { const { appStateContainer } = getState({ history }); const { index } = appStateContainer.getState(); return Promise.props({ - ip: indexPatterns.getCache().then(indexPatternList => { + ip: indexPatterns.getCache().then((indexPatternList) => { /** * In making the indexPattern modifiable it was placed in appState. Unfortunately, * the load order of AppState conflicts with the load order of many other things @@ -145,7 +146,7 @@ app.config($routeProvider => { }), savedSearch: getServices() .getSavedSearchById(savedSearchId) - .then(savedSearch => { + .then((savedSearch) => { if (savedSearchId) { chrome.recentlyAccessed.add( savedSearch.getFullPath(), @@ -162,8 +163,8 @@ app.config($routeProvider => { mapping: { search: '/', 'index-pattern': { - app: 'kibana', - path: `#/management/kibana/objects/savedSearches/${$route.current.params.id}`, + app: 'management', + path: `kibana/objects/savedSearches/${$route.current.params.id}`, }, }, toastNotifications, @@ -179,7 +180,7 @@ app.config($routeProvider => { }); }); -app.directive('discoverApp', function() { +app.directive('discoverApp', function () { return { restrict: 'E', controllerAs: 'discoverApp', @@ -249,7 +250,7 @@ function discoverController( { filters: esFilters.FilterStateStore.APP_STATE } ); - const appStateUnsubscribe = appStateContainer.subscribe(async newState => { + const appStateUnsubscribe = appStateContainer.subscribe(async (newState) => { const { state: newStatePartial } = splitState(newState); const { state: oldStatePartial } = splitState(getPreviousAppState()); @@ -259,7 +260,7 @@ function discoverController( // detect changes that should trigger fetching of new data const changes = ['interval', 'sort', 'query'].filter( - prop => !_.isEqual(newStatePartial[prop], oldStatePartial[prop]) + (prop) => !_.isEqual(newStatePartial[prop], oldStatePartial[prop]) ); if (changes.length) { @@ -278,7 +279,7 @@ function discoverController( } }); - $scope.setIndexPattern = async id => { + $scope.setIndexPattern = async (id) => { await replaceUrlAppState({ index: id }); $route.reload(); }; @@ -294,7 +295,7 @@ function discoverController( $scope.updateDataSource(); }, }, - error => addFatalError(core.fatalErrors, error) + (error) => addFatalError(core.fatalErrors, error) ) ); @@ -302,7 +303,7 @@ function discoverController( requests: new RequestAdapter(), }; - $scope.timefilterUpdateHandler = ranges => { + $scope.timefilterUpdateHandler = (ranges) => { timefilter.setTime({ from: moment(ranges.from).toISOString(), to: moment(ranges.to).toISOString(), @@ -316,7 +317,7 @@ function discoverController( $scope.$watch( () => uiCapabilities.discover.saveQuery, - newCapability => { + (newCapability) => { $scope.showSaveQuery = newCapability; } ); @@ -342,7 +343,7 @@ function discoverController( description: i18n.translate('discover.localMenu.newSearchDescription', { defaultMessage: 'New Search', }), - run: function() { + run: function () { $scope.$evalAsync(() => { history.push('/'); }); @@ -374,7 +375,7 @@ function discoverController( isTitleDuplicateConfirmed, onTitleDuplicate, }; - return saveDataSource(saveOptions).then(response => { + return saveDataSource(saveOptions).then((response) => { // If the save wasn't successful, put the original values back. if (!response.id || response.error) { savedSearch.title = currentTitle; @@ -414,7 +415,7 @@ function discoverController( testId: 'discoverOpenButton', run: () => { showOpenSearchPanel({ - makeUrl: searchId => `#/${encodeURIComponent(searchId)}`, + makeUrl: (searchId) => `#/${encodeURIComponent(searchId)}`, I18nContext: core.i18n.Context, }); }, @@ -429,7 +430,7 @@ function discoverController( defaultMessage: 'Share Search', }), testId: 'shareTopNavButton', - run: async anchorElement => { + run: async (anchorElement) => { const sharingData = await this.getSharingData(); share.toggleShareContextMenu({ anchorElement, @@ -524,8 +525,8 @@ function discoverController( return $scope.fieldCounts; } - return await new Promise(resolve => { - const unwatch = $scope.$watch('fetchStatus', newValue => { + return await new Promise((resolve) => { + const unwatch = $scope.$watch('fetchStatus', (newValue) => { if (newValue === fetchStatuses.COMPLETE) { unwatch(); resolve($scope.fieldCounts); @@ -582,8 +583,8 @@ function discoverController( fields: selectFields, metaFields: $scope.indexPattern.metaFields, conflictedTypesFields: $scope.indexPattern.fields - .filter(f => f.type === 'conflict') - .map(f => f.name), + .filter((f) => f.type === 'conflict') + .map((f) => f.name), indexPatternId: searchSource.getField('index').id, }; }; @@ -592,7 +593,8 @@ function discoverController( const query = $scope.searchSource.getField('query') || getDefaultQuery( - localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + localStorage.get('kibana.userQueryLanguage') || + config.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) ); return { query, @@ -644,7 +646,7 @@ function discoverController( { next: $scope.fetch, }, - error => addFatalError(core.fatalErrors, error) + (error) => addFatalError(core.fatalErrors, error) ) ); subscriptions.add( @@ -656,11 +658,11 @@ function discoverController( $scope.updateTime(); }, }, - error => addFatalError(core.fatalErrors, error) + (error) => addFatalError(core.fatalErrors, error) ) ); - $scope.changeInterval = interval => { + $scope.changeInterval = (interval) => { if (interval) { setAppState({ interval }); } @@ -694,7 +696,7 @@ function discoverController( else return status.NO_RESULTS; } - return function() { + return function () { const current = { rows: $scope.rows, fetchStatus: $scope.fetchStatus, @@ -750,6 +752,13 @@ function discoverController( // Update defaults so that "reload saved query" functions correctly setAppState(getStateDefaults()); chrome.docTitle.change(savedSearch.lastSavedTitle); + chrome.setBreadcrumbs([ + { + text: discoverBreadcrumbsTitle, + href: '#/', + }, + { text: savedSearch.title }, + ]); } } }); @@ -768,7 +777,7 @@ function discoverController( } } - $scope.opts.fetch = $scope.fetch = function() { + $scope.opts.fetch = $scope.fetch = function () { // ignore requests to fetch before the app inits if (!init.complete) return; $scope.fetchCounter++; @@ -781,7 +790,7 @@ function discoverController( $scope .updateDataSource() .then(setupVisualization) - .then(function() { + .then(function () { $scope.fetchStatus = fetchStatuses.LOADING; logInspectorRequest(); return $scope.searchSource.fetch({ @@ -789,7 +798,7 @@ function discoverController( }); }) .then(onResults) - .catch(error => { + .catch((error) => { // If the request was aborted then no need to surface this error in the UI if (error instanceof Error && error.name === 'AbortError') return; @@ -808,14 +817,14 @@ function discoverController( }); }; - $scope.updateQuery = function({ query }, isUpdate = true) { + $scope.updateQuery = function ({ query }, isUpdate = true) { if (!_.isEqual(query, appStateContainer.getState().query) || isUpdate === false) { setAppState({ query }); $fetchObservable.next(); } }; - $scope.updateSavedQueryId = newSavedQueryId => { + $scope.updateSavedQueryId = (newSavedQueryId) => { if (newSavedQueryId) { setAppState({ savedQuery: newSavedQueryId }); } else { @@ -870,6 +879,7 @@ function discoverController( if ($scope.vis.data.aggs.aggs[1]) { $scope.bucketInterval = $scope.vis.data.aggs.aggs[1].buckets.getInterval(); } + $scope.updateTime(); } $scope.hits = resp.hits.total; @@ -878,9 +888,9 @@ function discoverController( // if we haven't counted yet, reset the counts const counts = ($scope.fieldCounts = $scope.fieldCounts || {}); - $scope.rows.forEach(hit => { + $scope.rows.forEach((hit) => { const fields = Object.keys($scope.indexPattern.flattenHit(hit)); - fields.forEach(fieldName => { + fields.forEach((fieldName) => { counts[fieldName] = (counts[fieldName] || 0) + 1; }); }); @@ -898,12 +908,12 @@ function discoverController( }); inspectorRequest = inspectorAdapters.requests.start(title, { description }); inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); - $scope.searchSource.getSearchRequestBody().then(body => { + $scope.searchSource.getSearchRequestBody().then((body) => { inspectorRequest.json(body); }); } - $scope.updateTime = function() { + $scope.updateTime = function () { //this is the timerange for the histogram, should be refactored $scope.timeRange = { from: dateMath.parse(timefilter.getTime().from), @@ -911,16 +921,16 @@ function discoverController( }; }; - $scope.toMoment = function(datetime) { + $scope.toMoment = function (datetime) { return moment(datetime).format(config.get('dateFormat')); }; - $scope.resetQuery = function() { + $scope.resetQuery = function () { history.push(`/${encodeURIComponent($route.current.params.id)}`); $route.reload(); }; - $scope.newQuery = function() { + $scope.newQuery = function () { history.push('/'); }; @@ -947,7 +957,7 @@ function discoverController( }; // TODO: On array fields, negating does not negate the combination, rather all terms - $scope.filterQuery = function(field, values, operation) { + $scope.filterQuery = function (field, values, operation) { $scope.indexPattern.popularizeField(field, 1); const newFilters = esFilters.generateFilters( filterManager, @@ -976,18 +986,18 @@ function discoverController( setAppState({ columns }); }; - $scope.scrollToTop = function() { + $scope.scrollToTop = function () { $window.scrollTo(0, 0); }; - $scope.scrollToBottom = function() { + $scope.scrollToBottom = function () { // delay scrolling to after the rows have been rendered $timeout(() => { $element.find('#discoverBottomMarker').focus(); }, 0); }; - $scope.showAllRows = function() { + $scope.showAllRows = function () { $scope.minimumVisibleRows = $scope.hits; }; @@ -1029,7 +1039,7 @@ function discoverController( return $scope.vis.data.aggs.onSearchRequestStart(searchSource, options); }); - $scope.searchSource.setField('aggs', function() { + $scope.searchSource.setField('aggs', function () { if (!$scope.vis) return; return $scope.vis.data.aggs.toDsl(); }); diff --git a/src/plugins/discover/public/application/angular/doc.ts b/src/plugins/discover/public/application/angular/doc.ts index 45075f3742f3e..347caa0f3431c 100644 --- a/src/plugins/discover/public/application/angular/doc.ts +++ b/src/plugins/discover/public/application/angular/doc.ts @@ -28,7 +28,7 @@ interface LazyScope extends ng.IScope { const { timefilter } = getServices(); const app = getAngularModule(); -app.directive('discoverDoc', function(reactDirective: any) { +app.directive('discoverDoc', function (reactDirective: any) { return reactDirective( Doc, [ @@ -51,7 +51,7 @@ app.config(($routeProvider: any) => { .when('/doc/:indexPattern/:index', { // have to be written as function expression, because it's not compiled in dev mode // eslint-disable-next-line object-shorthand - controller: function($scope: LazyScope, $route: any, es: any) { + controller: function ($scope: LazyScope, $route: any, es: any) { timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); $scope.esClient = es; diff --git a/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts b/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts index cec1a097da5bf..8257c79af7e8a 100644 --- a/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts +++ b/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts @@ -24,7 +24,7 @@ */ function buildColumns(columns: string[]) { if (columns.length > 1 && columns.indexOf('_source') !== -1) { - return columns.filter(col => col !== '_source'); + return columns.filter((col) => col !== '_source'); } else if (columns.length !== 0) { return columns; } @@ -42,7 +42,7 @@ export function removeColumn(columns: string[], columnName: string) { if (!columns.includes(columnName)) { return columns; } - return buildColumns(columns.filter(col => col !== columnName)); + return buildColumns(columns.filter((col) => col !== columnName)); } export function moveColumn(columns: string[], columnName: string, newIndex: number) { diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts index 60dfb69e85e74..82bfcc8bc42f9 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts @@ -19,6 +19,7 @@ import { TableHeader } from './table_header/table_header'; import { getServices } from '../../../../kibana_services'; import { SORT_DEFAULT_ORDER_SETTING, DOC_HIDE_TIME_COLUMN_SETTING } from '../../../../../common'; +import { UI_SETTINGS } from '../../../../../../data/public'; export function createTableHeaderDirective(reactDirective: any) { const { uiSettings: config } = getServices(); @@ -38,7 +39,7 @@ export function createTableHeaderDirective(reactDirective: any) { { restrict: 'A' }, { hideTimeColumn: config.get(DOC_HIDE_TIME_COLUMN_SETTING, false), - isShortDots: config.get('shortDots:enable'), + isShortDots: config.get(UI_SETTINGS.SHORT_DOTS_ENABLE), defaultSortOrder: config.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), } ); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx index 2ca53d5a34b03..dd5f2a10b8a77 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx @@ -51,7 +51,7 @@ export function TableHeader({ return ( - {displayedColumns.map(col => { + {displayedColumns.map((col) => { return ( name === sortPair[0]) || []; - const currentSortWithoutColumn = sortOrder.filter(pair => pair[0] !== name); - const currentColumnSort = sortOrder.find(pair => pair[0] === name); + const [, sortDirection = ''] = sortOrder.find((sortPair) => name === sortPair[0]) || []; + const currentSortWithoutColumn = sortOrder.filter((pair) => pair[0] !== name); + const currentColumnSort = sortOrder.find((pair) => pair[0] === name); const currentColumnSortDirection = (currentColumnSort && currentColumnSort[1]) || ''; const btnSortIcon = sortDirectionToIcon[sortDirection]; @@ -172,7 +172,7 @@ export function TableHeaderColumn({ {displayName} {buttons - .filter(button => button.active) + .filter((button) => button.active) .map((button, idx) => ( { indexPattern = FixturesStubbedLogstashIndexPatternProvider() as IndexPattern; }); - describe('getSort function', function() { - test('should be a function', function() { + describe('getSort function', function () { + test('should be a function', function () { expect(typeof getSort === 'function').toBeTruthy(); }); - test('should return an array of objects', function() { + test('should return an array of objects', function () { expect(getSort([['bytes', 'desc']], indexPattern)).toEqual([{ bytes: 'desc' }]); delete indexPattern.timeFieldName; @@ -45,7 +45,7 @@ describe('docTable', function() { expect(getSort([{ bytes: 'desc' }], indexPattern)).toEqual([{ bytes: 'desc' }]); }); - test('should return an empty array when passed an unsortable field', function() { + test('should return an empty array when passed an unsortable field', function () { expect(getSort([['non-sortable', 'asc']], indexPattern)).toEqual([]); expect(getSort([['lol_nope', 'asc']], indexPattern)).toEqual([]); @@ -53,27 +53,27 @@ describe('docTable', function() { expect(getSort([['non-sortable', 'asc']], indexPattern)).toEqual([]); }); - test('should return an empty array ', function() { + test('should return an empty array ', function () { expect(getSort([], indexPattern)).toEqual([]); expect(getSort([['foo', 'bar']], indexPattern)).toEqual([]); expect(getSort([{ foo: 'bar' }], indexPattern)).toEqual([]); }); }); - describe('getSortArray function', function() { - test('should have an array method', function() { + describe('getSortArray function', function () { + test('should have an array method', function () { expect(getSortArray).toBeInstanceOf(Function); }); - test('should return an array of arrays for sortable fields', function() { + test('should return an array of arrays for sortable fields', function () { expect(getSortArray([['bytes', 'desc']], indexPattern)).toEqual([['bytes', 'desc']]); }); - test('should return an array of arrays from an array of elasticsearch sort objects', function() { + test('should return an array of arrays from an array of elasticsearch sort objects', function () { expect(getSortArray([{ bytes: 'desc' }], indexPattern)).toEqual([['bytes', 'desc']]); }); - test('should sort by an empty array when an unsortable field is given', function() { + test('should sort by an empty array when an unsortable field is given', function () { expect(getSortArray([{ 'non-sortable': 'asc' }], indexPattern)).toEqual([]); expect(getSortArray([{ lol_nope: 'asc' }], indexPattern)).toEqual([]); diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts index aa23aa4390673..c28519692318e 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts @@ -57,7 +57,7 @@ export function getSort(sort: SortPair[], indexPattern: IndexPattern): SortPairO if (Array.isArray(sort)) { return sort .map((sortPair: SortPair) => createSortObject(sortPair, indexPattern)) - .filter(sortPairObj => typeof sortPairObj === 'object') as SortPairObj[]; + .filter((sortPairObj) => typeof sortPairObj === 'object') as SortPairObj[]; } return []; } @@ -67,5 +67,5 @@ export function getSort(sort: SortPair[], indexPattern: IndexPattern): SortPairO * [[fieldToSort: directionToSort]] */ export function getSortArray(sort: SortPair[], indexPattern: IndexPattern) { - return getSort(sort, indexPattern).map(sortPair => Object.entries(sortPair).pop()); + return getSort(sort, indexPattern).map((sortPair) => Object.entries(sortPair).pop()); } diff --git a/src/plugins/discover/public/application/angular/helpers/point_series.ts b/src/plugins/discover/public/application/angular/helpers/point_series.ts index db0ca85b0399a..4c280bf43ea12 100644 --- a/src/plugins/discover/public/application/angular/helpers/point_series.ts +++ b/src/plugins/discover/public/application/angular/helpers/point_series.ts @@ -84,7 +84,7 @@ export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => { const yAccessor = table.columns[y.accessor].id; const chart = {} as Chart; - chart.xAxisOrderedValues = uniq(table.rows.map(r => r[xAccessor] as number)); + chart.xAxisOrderedValues = uniq(table.rows.map((r) => r[xAccessor] as number)); chart.xAxisFormat = x.format; chart.xAxisLabel = table.columns[x.accessor].name; @@ -101,8 +101,8 @@ export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => { chart.yAxisLabel = table.columns[y.accessor].name; chart.values = table.rows - .filter(row => row && row[yAccessor] !== 'NaN') - .map(row => ({ + .filter((row) => row && row[yAccessor] !== 'NaN') + .map((row) => ({ x: row[xAccessor] as number, y: row[yAccessor] as number, })); diff --git a/src/plugins/discover/public/application/angular/response_handler.js b/src/plugins/discover/public/application/angular/response_handler.js index 04ccb67ec7e25..00a9ee934430d 100644 --- a/src/plugins/discover/public/application/angular/response_handler.js +++ b/src/plugins/discover/public/application/angular/response_handler.js @@ -87,7 +87,7 @@ function convertTableGroup(tableGroup, convertTable) { const out = {}; let outList; - tables.forEach(function(table) { + tables.forEach(function (table) { if (!outList) { const direction = tableGroup.direction === 'row' ? 'rows' : 'columns'; outList = out[direction] = []; @@ -105,7 +105,7 @@ function convertTableGroup(tableGroup, convertTable) { export const discoverResponseHandler = (response, dimensions) => { const tableGroup = tableResponseHandler(response, dimensions); - let converted = convertTableGroup(tableGroup, table => { + let converted = convertTableGroup(tableGroup, (table) => { return buildPointSeriesData(table, dimensions); }); if (!converted) { diff --git a/src/plugins/discover/public/application/components/doc/doc.test.tsx b/src/plugins/discover/public/application/components/doc/doc.test.tsx index 2cbc1547d1082..0bc621714c70f 100644 --- a/src/plugins/discover/public/application/components/doc/doc.test.tsx +++ b/src/plugins/discover/public/application/components/doc/doc.test.tsx @@ -53,7 +53,7 @@ beforeEach(() => { const waitForPromises = async () => act(async () => { - await new Promise(resolve => setTimeout(resolve)); + await new Promise((resolve) => setTimeout(resolve)); }); /** diff --git a/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx b/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx index ae5fbd05b0aaf..84ad19dbddcbf 100644 --- a/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx +++ b/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx @@ -23,7 +23,7 @@ import { HitsCounter, HitsCounterProps } from './hits_counter'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -describe('hits counter', function() { +describe('hits counter', function () { let props: HitsCounterProps; let component: ReactWrapper; @@ -47,13 +47,13 @@ describe('hits counter', function() { expect(findTestSubject(component, 'resetSavedSearch').length).toBe(0); }); - it('expect to render the number of hits', function() { + it('expect to render the number of hits', function () { component = mountWithIntl(); const hits = findTestSubject(component, 'discoverQueryHits'); expect(hits.text()).toBe('2'); }); - it('expect to render 1,899 hits if 1899 hits given', function() { + it('expect to render 1,899 hits if 1899 hits given', function () { component = mountWithIntl( ); @@ -61,7 +61,7 @@ describe('hits counter', function() { expect(hits.text()).toBe('1,899'); }); - it('should reset query', function() { + it('should reset query', function () { component = mountWithIntl(); findTestSubject(component, 'resetSavedSearch').simulate('click'); expect(props.onResetQuery).toHaveBeenCalled(); diff --git a/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx new file mode 100644 index 0000000000000..3321ac764a05b --- /dev/null +++ b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { ReactWrapper } from 'enzyme'; +import { LoadingSpinner } from './loading_spinner'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; + +describe('loading spinner', function () { + let component: ReactWrapper; + + it('LoadingSpinner renders a Searching text and a spinner', () => { + component = mountWithIntl(); + expect(findTestSubject(component, 'loadingSpinnerText').text()).toBe('Searching'); + expect(findTestSubject(component, 'loadingSpinner').length).toBe(1); + }); +}); diff --git a/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.tsx b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.tsx new file mode 100644 index 0000000000000..44b922bf0f708 --- /dev/null +++ b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.tsx @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { EuiLoadingSpinner, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; + +export function LoadingSpinner() { + return ( + + <> + +

+ +

+
+ + + +
+ ); +} + +export function createLoadingSpinnerDirective(reactDirective: any) { + return reactDirective(LoadingSpinner); +} diff --git a/src/plugins/discover/public/application/components/sidebar/change_indexpattern.tsx b/src/plugins/discover/public/application/components/sidebar/change_indexpattern.tsx index 9d304bfa9c27d..4a539b618f817 100644 --- a/src/plugins/discover/public/application/components/sidebar/change_indexpattern.tsx +++ b/src/plugins/discover/public/application/components/sidebar/change_indexpattern.tsx @@ -51,7 +51,7 @@ export function ChangeIndexPattern({ }) { const [isPopoverOpen, setPopoverIsOpen] = useState(false); - const createTrigger = function() { + const createTrigger = function () { const { label, title, ...rest } = trigger; return ( { + onChange={(choices) => { const choice = (choices.find(({ checked }) => checked) as unknown) as { value: string; }; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index 7372fab075ea3..8c527475b7480 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -92,18 +92,18 @@ function getComponent(selected = false, showDetails = false, useShortDots = fals return { comp, props }; } -describe('discover sidebar field', function() { - it('should allow selecting fields', function() { +describe('discover sidebar field', function () { + it('should allow selecting fields', function () { const { comp, props } = getComponent(); findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); expect(props.onAddField).toHaveBeenCalledWith('bytes'); }); - it('should allow deselecting fields', function() { + it('should allow deselecting fields', function () { const { comp, props } = getComponent(true); findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); expect(props.onRemoveField).toHaveBeenCalledWith('bytes'); }); - it('should trigger onShowDetails', function() { + it('should trigger onShowDetails', function () { const { comp, props } = getComponent(); findTestSubject(comp, 'field-bytes-showDetails').simulate('click'); expect(props.onShowDetails).toHaveBeenCalledWith(true, props.field); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index a533b798ad09f..99dad418c04bd 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -80,7 +80,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { defaultMessage: 'Type', }); const typeOptions = types - ? types.map(type => { + ? types.map((type) => { return { value: type, text: type }; }) : [{ value: 'any', text: 'any' }]; @@ -224,7 +224,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { legend={legend} options={toggleButtons(id)} idSelected={`${id}-${values[id]}`} - onChange={optionId => handleValueChange(id, optionId.replace(`${id}-`, ''))} + onChange={(optionId) => handleValueChange(id, optionId.replace(`${id}-`, ''))} buttonSize="compressed" isFullWidth data-test-subj={`${id}ButtonGroup`} @@ -257,7 +257,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { data-test-subj="fieldFilterSearchInput" compressed fullWidth - onChange={event => onChange('name', event.currentTarget.value)} + onChange={(event) => onChange('name', event.currentTarget.value)} placeholder={searchPlaceholder} value={value} /> diff --git a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx index 79417c07501a3..24e6f5993a0a5 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx @@ -53,18 +53,11 @@ const defaultProps = { }; function getIndexPatternPickerList(instance: ShallowWrapper) { - return instance - .find(ChangeIndexPattern) - .first() - .dive() - .find(EuiSelectable); + return instance.find(ChangeIndexPattern).first().dive().find(EuiSelectable); } function getIndexPatternPickerOptions(instance: ShallowWrapper) { - return getIndexPatternPickerList(instance) - .dive() - .find(EuiSelectableList) - .prop('options'); + return getIndexPatternPickerList(instance).dive().find(EuiSelectableList).prop('options'); } function selectIndexPatternPickerOption(instance: ShallowWrapper, selectedLabel: string) { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx index 3b01d5e7a9e62..3acdcb1e92091 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx @@ -46,7 +46,7 @@ export function DiscoverIndexPattern({ selectedIndexPattern, setIndexPattern, }: DiscoverIndexPatternProps) { - const options: IndexPatternRef[] = (indexPatternList || []).map(entity => ({ + const options: IndexPatternRef[] = (indexPatternList || []).map((entity) => ({ id: entity.id, title: entity.attributes!.title, })); @@ -76,8 +76,8 @@ export function DiscoverIndexPattern({ }} indexPatternId={selected.id} indexPatternRefs={options} - onChangeIndexPattern={id => { - const indexPattern = options.find(pattern => pattern.id === id); + onChangeIndexPattern={(id) => { + const indexPattern = options.find((pattern) => pattern.id === id); if (indexPattern) { setIndexPattern(id); setSelected(indexPattern); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx index c7512824d7d44..60011914e8f74 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx @@ -59,7 +59,7 @@ jest.mock('../../../kibana_services', () => ({ })); jest.mock('./lib/get_index_pattern_field_list', () => ({ - getIndexPatternFieldList: jest.fn(indexPattern => indexPattern.fields), + getIndexPatternFieldList: jest.fn((indexPattern) => indexPattern.fields), })); function getCompProps() { @@ -102,7 +102,7 @@ function getCompProps() { }; } -describe('discover sidebar', function() { +describe('discover sidebar', function () { let props: DiscoverSidebarProps; let comp: ReactWrapper; @@ -111,7 +111,7 @@ describe('discover sidebar', function() { comp = mountWithIntl(); }); - it('should have Selected Fields and Available Fields with Popular Fields sections', function() { + it('should have Selected Fields and Available Fields with Popular Fields sections', function () { const popular = findTestSubject(comp, 'fieldList-popular'); const selected = findTestSubject(comp, 'fieldList-selected'); const unpopular = findTestSubject(comp, 'fieldList-unpopular'); @@ -119,15 +119,15 @@ describe('discover sidebar', function() { expect(unpopular.children().length).toBe(7); expect(selected.children().length).toBe(1); }); - it('should allow selecting fields', function() { + it('should allow selecting fields', function () { findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); expect(props.onAddField).toHaveBeenCalledWith('bytes'); }); - it('should allow deselecting fields', function() { + it('should allow deselecting fields', function () { findTestSubject(comp, 'fieldToggle-extension').simulate('click'); expect(props.onRemoveField).toHaveBeenCalledWith('extension'); }); - it('should allow adding filters', function() { + it('should allow adding filters', function () { findTestSubject(comp, 'field-extension-showDetails').simulate('click'); findTestSubject(comp, 'plus-extension-gif').simulate('click'); expect(props.onAddFilter).toHaveBeenCalled(); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index 0d9aebe11ece6..5a319d30b2515 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -33,6 +33,7 @@ import { IIndexPatternFieldList, IndexPatternField, IndexPattern, + UI_SETTINGS, } from '../../../../../data/public'; import { AppState } from '../../angular/discover_state'; import { getDetails } from './lib/get_details'; @@ -133,7 +134,7 @@ export function DiscoverSidebar({ ); const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING); - const useShortDots = services.uiSettings.get('shortDots:enable'); + const useShortDots = services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const { selected: selectedFields, @@ -174,7 +175,7 @@ export function DiscoverSidebar({ o.attributes.title)} + indexPatternList={sortBy(indexPatternList, (o) => o.attributes.title)} />
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js index c2d225360d0ef..e055d644e1f91 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; function getFieldValues(hits, field) { const name = field.name; const flattenHit = field.indexPattern.flattenHit; - return _.map(hits, function(hit) { + return _.map(hits, function (hit) { return flattenHit(hit)[name]; }); } @@ -55,18 +55,13 @@ function getFieldValueCounts(params) { try { const groups = _groupValues(allValues, params); - counts = _.map( - _.sortBy(groups, 'count') - .reverse() - .slice(0, params.count), - function(bucket) { - return { - value: bucket.value, - count: bucket.count, - percent: ((bucket.count / (params.hits.length - missing)) * 100).toFixed(1), - }; - } - ); + counts = _.map(_.sortBy(groups, 'count').reverse().slice(0, params.count), function (bucket) { + return { + value: bucket.value, + count: bucket.count, + percent: ((bucket.count / (params.hits.length - missing)) * 100).toFixed(1), + }; + }); if (params.hits.length - missing === 0) { return { @@ -103,7 +98,7 @@ function _groupValues(allValues, params) { const groups = {}; let k; - allValues.forEach(function(value) { + allValues.forEach(function (value) { if (_.isObject(value) && !Array.isArray(value)) { throw new Error( i18n.translate( @@ -121,7 +116,7 @@ function _groupValues(allValues, params) { k = value == null ? undefined : [value]; } - _.each(k, function(key) { + _.each(k, function (key) { if (groups.hasOwnProperty(key)) { groups[key].count++; } else { diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts index fdfc536485579..875cbf4075aa2 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts @@ -31,8 +31,8 @@ import { fieldCalculator } from './field_calculator'; let indexPattern: IndexPattern; -describe('fieldCalculator', function() { - beforeEach(function() { +describe('fieldCalculator', function () { + beforeEach(function () { indexPattern = new StubIndexPattern( 'logstash-*', (cfg: any) => cfg, @@ -41,7 +41,7 @@ describe('fieldCalculator', function() { coreMock.createStart() ); }); - it('should have a _countMissing that counts nulls & undefineds in an array', function() { + it('should have a _countMissing that counts nulls & undefineds in an array', function () { const values = [ ['foo', 'bar'], 'foo', @@ -59,11 +59,11 @@ describe('fieldCalculator', function() { expect(fieldCalculator._countMissing(values)).toBe(5); }); - describe('_groupValues', function() { + describe('_groupValues', function () { let groups: Record; let params: any; let values: any; - beforeEach(function() { + beforeEach(function () { values = [ ['foo', 'bar'], 'foo', @@ -82,17 +82,17 @@ describe('fieldCalculator', function() { groups = fieldCalculator._groupValues(values, params); }); - it('should have a _groupValues that counts values', function() { + it('should have a _groupValues that counts values', function () { expect(groups).toBeInstanceOf(Object); }); - it('should throw an error if any value is a plain object', function() { - expect(function() { + it('should throw an error if any value is a plain object', function () { + expect(function () { fieldCalculator._groupValues([{}, true, false], params); }).toThrowError(); }); - it('should handle values with dots in them', function() { + it('should handle values with dots in them', function () { values = ['0', '0.........', '0.......,.....']; params = {}; groups = fieldCalculator._groupValues(values, params); @@ -101,36 +101,36 @@ describe('fieldCalculator', function() { expect(groups[values[2]].count).toBe(1); }); - it('should have a a key for value in the array when not grouping array terms', function() { + it('should have a a key for value in the array when not grouping array terms', function () { expect(_.keys(groups).length).toBe(3); expect(groups.foo).toBeInstanceOf(Object); expect(groups.bar).toBeInstanceOf(Object); expect(groups.baz).toBeInstanceOf(Object); }); - it('should count array terms independently', function() { + it('should count array terms independently', function () { expect(groups['foo,bar']).toBe(undefined); expect(groups.foo.count).toBe(5); expect(groups.bar.count).toBe(3); expect(groups.baz.count).toBe(1); }); - describe('grouped array terms', function() { - beforeEach(function() { + describe('grouped array terms', function () { + beforeEach(function () { params.grouped = true; groups = fieldCalculator._groupValues(values, params); }); - it('should group array terms when passed params.grouped', function() { + it('should group array terms when passed params.grouped', function () { expect(_.keys(groups).length).toBe(4); expect(groups['foo,bar']).toBeInstanceOf(Object); }); - it('should contain the original array as the value', function() { + it('should contain the original array as the value', function () { expect(groups['foo,bar'].value).toEqual(['foo', 'bar']); }); - it('should count the pairs separately from the values they contain', function() { + it('should count the pairs separately from the values they contain', function () { expect(groups['foo,bar'].count).toBe(2); expect(groups.foo.count).toBe(3); expect(groups.bar.count).toBe(1); @@ -138,32 +138,32 @@ describe('fieldCalculator', function() { }); }); - describe('getFieldValues', function() { + describe('getFieldValues', function () { let hits: any; - beforeEach(function() { + beforeEach(function () { hits = _.each(_.cloneDeep(realHits), indexPattern.flattenHit); }); - it('Should return an array of values for _source fields', function() { + it('Should return an array of values for _source fields', function () { const extensions = fieldCalculator.getFieldValues( hits, indexPattern.fields.getByName('extension') ); expect(extensions).toBeInstanceOf(Array); expect( - _.filter(extensions, function(v) { + _.filter(extensions, function (v) { return v === 'html'; }).length ).toBe(8); expect(_.uniq(_.clone(extensions)).sort()).toEqual(['gif', 'html', 'php', 'png']); }); - it('Should return an array of values for core meta fields', function() { + it('Should return an array of values for core meta fields', function () { const types = fieldCalculator.getFieldValues(hits, indexPattern.fields.getByName('_type')); expect(types).toBeInstanceOf(Array); expect( - _.filter(types, function(v) { + _.filter(types, function (v) { return v === 'apache'; }).length ).toBe(18); @@ -171,9 +171,9 @@ describe('fieldCalculator', function() { }); }); - describe('getFieldValueCounts', function() { + describe('getFieldValueCounts', function () { let params: { hits: any; field: any; count: number }; - beforeEach(function() { + beforeEach(function () { params = { hits: _.cloneDeep(realHits), field: indexPattern.fields.getByName('extension'), @@ -181,7 +181,7 @@ describe('fieldCalculator', function() { }; }); - it('counts the top 3 values', function() { + it('counts the top 3 values', function () { const extensions = fieldCalculator.getFieldValueCounts(params); expect(extensions).toBeInstanceOf(Object); expect(extensions.buckets).toBeInstanceOf(Array); @@ -190,7 +190,7 @@ describe('fieldCalculator', function() { expect(extensions.error).toBe(undefined); }); - it('fails to analyze geo and attachment types', function() { + it('fails to analyze geo and attachment types', function () { params.field = indexPattern.fields.getByName('point'); expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined); @@ -201,16 +201,16 @@ describe('fieldCalculator', function() { expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined); }); - it('fails to analyze fields that are in the mapping, but not the hits', function() { + it('fails to analyze fields that are in the mapping, but not the hits', function () { params.field = indexPattern.fields.getByName('ip'); expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined); }); - it('counts the total hits', function() { + it('counts the total hits', function () { expect(fieldCalculator.getFieldValueCounts(params).total).toBe(params.hits.length); }); - it('counts the hits the field exists in', function() { + it('counts the hits the field exists in', function () { params.field = indexPattern.fields.getByName('phpmemory'); expect(fieldCalculator.getFieldValueCounts(params).exists).toBe(5); }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts index 1351433e9dd0e..eb139f97c7b00 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts @@ -20,8 +20,8 @@ import { getDefaultFieldFilter, setFieldFilterProp, isFieldFiltered } from './field_filter'; import { IndexPatternField } from '../../../../../../data/public'; -describe('field_filter', function() { - it('getDefaultFieldFilter should return default filter state', function() { +describe('field_filter', function () { + it('getDefaultFieldFilter should return default filter state', function () { expect(getDefaultFieldFilter()).toMatchInlineSnapshot(` Object { "aggregatable": null, @@ -32,7 +32,7 @@ describe('field_filter', function() { } `); }); - it('setFieldFilterProp should return allow filter changes', function() { + it('setFieldFilterProp should return allow filter changes', function () { const state = getDefaultFieldFilter(); const targetState = { aggregatable: true, @@ -83,12 +83,12 @@ describe('field_filter', function() { { filter: { aggregatable: true }, result: ['extension'] }, { filter: { aggregatable: true, searchable: false }, result: [] }, { filter: { type: 'string' }, result: ['extension'] }, - ].forEach(test => { + ].forEach((test) => { const filtered = fieldList - .filter(field => + .filter((field) => isFieldFiltered(field, { ...defaultState, ...test.filter }, { bytes: 1, extension: 1 }) ) - .map(field => field.name); + .map((field) => field.name); expect(filtered).toEqual(test.result); }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts index 63e1b761f4dd0..0fcbe925e0798 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts @@ -31,7 +31,7 @@ export function getIndexPatternFieldList( const fieldNamesInDocs = Object.keys(fieldCounts); const fieldNamesInIndexPattern = map(indexPattern.fields, 'name'); - difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach(unknownFieldName => { + difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach((unknownFieldName) => { fieldSpecs.push({ name: String(unknownFieldName), type: 'unknown', diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_warnings.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_warnings.ts index 138e805405c89..c57ee197d856f 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/get_warnings.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/get_warnings.ts @@ -34,7 +34,7 @@ export function getWarnings(field: IndexPatternField) { } if (warnings.length > 1) { - warnings = warnings.map(function(warning, i) { + warnings = warnings.map(function (warning, i) { return (i > 0 ? '\n' : '') + (i + 1) + ' - ' + warning; }); } diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts index e83287a139dd0..d4670a1e76011 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts @@ -20,8 +20,8 @@ import { groupFields } from './group_fields'; import { getDefaultFieldFilter } from './field_filter'; -describe('group_fields', function() { - it('should group fields in selected, popular, unpopular group', function() { +describe('group_fields', function () { + it('should group fields in selected, popular, unpopular group', function () { const fields = [ { name: 'category', diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx index e4e5f00172371..fab4637d87ca7 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx @@ -45,9 +45,9 @@ export function groupFields( } const popular = fields - .filter(field => !columns.includes(field.name) && field.count) + .filter((field) => !columns.includes(field.name) && field.count) .sort((a: IndexPatternField, b: IndexPatternField) => (b.count || 0) - (a.count || 0)) - .map(field => field.name) + .map((field) => field.name) .slice(0, popularLimit); const compareFn = (a: IndexPatternField, b: IndexPatternField) => { diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 04cf5d80da585..0793072fd0cf4 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -58,11 +58,11 @@ const indexPattern = { ], metaFields: ['_index', '_score'], flattenHit: undefined, - formatHit: jest.fn(hit => hit._source), + formatHit: jest.fn((hit) => hit._source), } as IndexPattern; indexPattern.fields.getByName = (name: string) => { - return indexPattern.fields.find(field => field.name === name); + return indexPattern.fields.find((field) => field.name === name); }; indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields); @@ -146,7 +146,7 @@ describe('DocViewTable at Discover', () => { toggleColumnButton: true, underScoreWarning: false, }, - ].forEach(check => { + ].forEach((check) => { const rowComponent = findTestSubject(component, `tableDocViewRow-${check._property}`); it(`renders row for ${check._property}`, () => { @@ -158,7 +158,7 @@ describe('DocViewTable at Discover', () => { 'collapseBtn', 'toggleColumnButton', 'underscoreWarning', - ] as const).forEach(element => { + ] as const).forEach((element) => { const elementExist = check[element]; if (typeof elementExist === 'boolean') { @@ -172,7 +172,7 @@ describe('DocViewTable at Discover', () => { } }); - (['noMappingWarning'] as const).forEach(element => { + (['noMappingWarning'] as const).forEach((element) => { const elementExist = check[element]; if (typeof elementExist === 'boolean') { diff --git a/src/plugins/discover/public/application/components/table/table.tsx b/src/plugins/discover/public/application/components/table/table.tsx index 6789f753899f5..9b95f2fc6bd27 100644 --- a/src/plugins/discover/public/application/components/table/table.tsx +++ b/src/plugins/discover/public/application/components/table/table.tsx @@ -47,7 +47,7 @@ export function DocViewTable({ {Object.keys(flattened) .sort() - .map(field => { + .map((field) => { const valueRaw = flattened[field]; const value = trimAngularSpan(String(formatted[field])); @@ -104,15 +104,15 @@ export function DocViewTable({ // to the index pattern, but that has its own complications which you can read more about in the following // issue: https://github.com/elastic/kibana/issues/54957 const isNestedField = - !indexPattern.fields.find(patternField => patternField.name === field) && - !!indexPattern.fields.find(patternField => { + !indexPattern.fields.find((patternField) => patternField.name === field) && + !!indexPattern.fields.find((patternField) => { // We only want to match a full path segment const nestedRootRegex = new RegExp(escapeRegExp(field) + '(\\.|$)'); return nestedRootRegex.test(patternField.subType?.nested?.path ?? ''); }); const fieldType = isNestedField ? 'nested' - : indexPattern.fields.find(patternField => patternField.name === field)?.type; + : indexPattern.fields.find((patternField) => patternField.name === field)?.type; return ( typeof v === 'object' && v !== null); + return Array.isArray(value) && value.some((v) => typeof v === 'object' && v !== null); } /** diff --git a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx index 642774d6be202..964f94ca9d9b2 100644 --- a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx +++ b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx @@ -24,7 +24,7 @@ import { EuiIconTip } from '@elastic/eui'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -describe('timechart header', function() { +describe('timechart header', function () { let props: TimechartHeaderProps; let component: ReactWrapper; @@ -65,7 +65,7 @@ describe('timechart header', function() { expect(component.find(EuiIconTip).length).toBe(1); }); - it('expect to render the date range', function() { + it('expect to render the date range', function () { component = mountWithIntl(); const datetimeRangeText = findTestSubject(component, 'discoverIntervalDateRange'); expect(datetimeRangeText.text()).toBe( @@ -78,14 +78,14 @@ describe('timechart header', function() { const dropdown = findTestSubject(component, 'discoverIntervalSelect'); expect(dropdown.length).toBe(1); // @ts-ignore - const values = dropdown.find('option').map(option => option.prop('value')); + const values = dropdown.find('option').map((option) => option.prop('value')); expect(values).toEqual(['auto', 'ms', 's']); // @ts-ignore - const labels = dropdown.find('option').map(option => option.text()); + const labels = dropdown.find('option').map((option) => option.text()); expect(labels).toEqual(['Auto', 'Millisecond', 'Second']); }); - it('should change the interval', function() { + it('should change the interval', function () { component = mountWithIntl(); findTestSubject(component, 'discoverIntervalSelect').simulate('change', { target: { value: 'ms' }, diff --git a/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx b/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx index 077adcb6b006e..8789847058aff 100644 --- a/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx +++ b/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx @@ -147,9 +147,7 @@ export function TimechartHeader({ size="s" type="alert" /> - ) : ( - undefined - ) + ) : undefined } /> diff --git a/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap index 3204252c808ac..42cd8613b1de0 100644 --- a/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap +++ b/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -53,7 +53,7 @@ exports[`render 1`] = ` > { + onChoose={(id) => { window.location.assign(props.makeUrl(id)); props.onClose(); }} @@ -86,7 +87,9 @@ export function OpenSearchPanel(props) { { return { getServices: () => ({ core: { uiSettings: {}, savedObjects: {} }, + addBasePath: (path) => path, }), }; }); diff --git a/src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx b/src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx index b9932da0523f8..958b7b6f8705b 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx +++ b/src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx @@ -79,7 +79,7 @@ export function convertDirectiveToRenderFn( directive.controller, getInjector ); - cleanupFnPromise.catch(e => { + cleanupFnPromise.catch((e) => { rejected = true; render(, domNode); }); @@ -88,7 +88,7 @@ export function convertDirectiveToRenderFn( if (!rejected) { // for cleanup // http://roubenmeschian.com/rubo/?p=51 - cleanupFnPromise.then(cleanup => cleanup()); + cleanupFnPromise.then((cleanup) => cleanup()); } }; }; diff --git a/src/plugins/discover/public/application/doc_views/doc_views_registry.ts b/src/plugins/discover/public/application/doc_views/doc_views_registry.ts index cdc22155f4710..589ecd4efe0f5 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_registry.ts +++ b/src/plugins/discover/public/application/doc_views/doc_views_registry.ts @@ -53,7 +53,7 @@ export class DocViewsRegistry { */ getDocViewsSorted(hit: ElasticSearchHit) { return this.docViews - .filter(docView => docView.shouldShow(hit)) + .filter((docView) => docView.shouldShow(hit)) .sort((a, b) => (Number(a.order) > Number(b.order) ? 1 : -1)); } } diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index ed70c90eb64e6..77d1572b0a40c 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -212,7 +212,7 @@ export class SearchEmbeddable extends Embeddable this.pushContainerStateParamsToScope(searchScope); - searchScope.setSortOrder = sort => { + searchScope.setSortOrder = (sort) => { this.updateInput({ sort }); }; @@ -249,7 +249,7 @@ export class SearchEmbeddable extends Embeddable operator, indexPattern.id! ); - filters = filters.map(filter => ({ + filters = filters.map((filter) => ({ ...filter, $state: { store: esFilters.FilterStateStore.APP_STATE }, })); diff --git a/src/plugins/discover/public/application/helpers/get_index_pattern_id.ts b/src/plugins/discover/public/application/helpers/get_index_pattern_id.ts index b78c43b9a74ac..601f892e3c56a 100644 --- a/src/plugins/discover/public/application/helpers/get_index_pattern_id.ts +++ b/src/plugins/discover/public/application/helpers/get_index_pattern_id.ts @@ -26,7 +26,7 @@ export function findIndexPatternById( if (!Array.isArray(indexPatterns) || !id) { return; } - return indexPatterns.find(o => o.id === id); + return indexPatterns.find((o) => o.id === id); } /** diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts index d97bbef7aca25..2b4705645cfcc 100644 --- a/src/plugins/discover/public/get_inner_angular.ts +++ b/src/plugins/discover/public/get_inner_angular.ts @@ -59,6 +59,7 @@ import { } from '../../kibana_legacy/public'; import { createDiscoverSidebarDirective } from './application/components/sidebar'; import { createHitsCounterDirective } from '././application/components/hits_counter'; +import { createLoadingSpinnerDirective } from '././application/components/loading_spinner/loading_spinner'; import { createTimechartHeaderDirective } from './application/components/timechart_header'; import { DiscoverStartPlugins } from './plugin'; import { getScopedHistory } from './kibana_services'; @@ -126,7 +127,7 @@ export function initializeInnerAngularModule( 'discoverPromise', ]) .config(watchMultiDecorator) - .directive('icon', reactDirective => reactDirective(EuiIcon)) + .directive('icon', (reactDirective) => reactDirective(EuiIcon)) .directive('renderComplete', createRenderCompleteDirective) .service('debounce', ['$timeout', DebounceProviderTimeout]); } @@ -148,13 +149,14 @@ export function initializeInnerAngularModule( ]) .config(watchMultiDecorator) .run(registerListenEventListener) - .directive('icon', reactDirective => reactDirective(EuiIcon)) + .directive('icon', (reactDirective) => reactDirective(EuiIcon)) .directive('kbnAccessibleClick', KbnAccessibleClickProvider) .directive('collapsibleSidebar', CollapsibleSidebarProvider) .directive('fixedScroll', FixedScrollProvider) .directive('renderComplete', createRenderCompleteDirective) .directive('discoverSidebar', createDiscoverSidebarDirective) .directive('hitsCounter', createHitsCounterDirective) + .directive('loadingSpinner', createLoadingSpinnerDirective) .directive('timechartHeader', createTimechartHeaderDirective) .service('debounce', ['$timeout', DebounceProviderTimeout]); } @@ -189,8 +191,8 @@ function createLocalStorageModule() { .service('sessionStorage', createLocalStorageService('sessionStorage')); } -const createLocalStorageService = function(type: string) { - return function($window: any) { +const createLocalStorageService = function (type: string) { + return function ($window: any) { return new Storage($window[type]); }; }; @@ -200,7 +202,7 @@ function createElasticSearchModule(data: DataPublicPluginStart) { .module('discoverEs', []) // Elasticsearch client used for requesting data. Connects to the /elasticsearch proxy // have to be written as function expression, because it's not compiled in dev mode - .service('es', function() { + .service('es', function () { return data.search.__LEGACY.esClient; }); } diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 359d91325f064..4154fdfeb3ff4 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -27,3 +27,4 @@ export function plugin(initializerContext: PluginInitializerContext) { export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './saved_searches'; export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable'; +export { DISCOVER_APP_URL_GENERATOR } from './url_generator'; diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index c394fe2c11a71..e4314426bfce5 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -34,6 +34,9 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { savedSearchLoader: {} as any, + urlGenerator: { + createUrl: jest.fn(), + } as any, }; return startContract; }; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 5a031872913c0..091288e3e65aa 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -34,7 +34,7 @@ import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; import { ChartsPluginStart } from 'src/plugins/charts/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; -import { SharePluginStart } from 'src/plugins/share/public'; +import { SharePluginStart, SharePluginSetup, UrlGeneratorContract } from 'src/plugins/share/public'; import { VisualizationsStart, VisualizationsSetup } from 'src/plugins/visualizations/public'; import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; @@ -43,7 +43,7 @@ import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../d import { SavedObjectLoader } from '../../saved_objects/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; - +import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; import { DocViewTable } from './application/components/table/table'; @@ -59,6 +59,17 @@ import { import { createSavedSearchesLoader } from './saved_searches'; import { registerFeature } from './register_feature'; import { buildServices } from './build_services'; +import { + DiscoverUrlGeneratorState, + DISCOVER_APP_URL_GENERATOR, + DiscoverUrlGenerator, +} from './url_generator'; + +declare module '../../share/public' { + export interface UrlGeneratorStateMapping { + [DISCOVER_APP_URL_GENERATOR]: UrlGeneratorState; + } +} /** * @public @@ -76,12 +87,31 @@ export interface DiscoverSetup { export interface DiscoverStart { savedSearchLoader: SavedObjectLoader; + + /** + * `share` plugin URL generator for Discover app. Use it to generate links into + * Discover application, example: + * + * ```ts + * const url = await plugins.discover.urlGenerator.createUrl({ + * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', + * timeRange: { + * to: 'now', + * from: 'now-15m', + * mode: 'relative', + * }, + * }); + * ``` + */ + readonly urlGenerator: undefined | UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>; } /** * @internal */ export interface DiscoverSetupPlugins { + share?: SharePluginSetup; uiActions: UiActionsSetup; embeddable: EmbeddableSetup; kibanaLegacy: KibanaLegacySetup; @@ -122,6 +152,7 @@ export class DiscoverPlugin private stopUrlTracking: (() => void) | undefined = undefined; private servicesInitialized: boolean = false; private innerAngularInitialized: boolean = false; + private urlGenerator?: DiscoverStart['urlGenerator']; /** * why are those functions public? they are needed for some mocha tests @@ -131,6 +162,17 @@ export class DiscoverPlugin public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>; setup(core: CoreSetup, plugins: DiscoverSetupPlugins) { + const baseUrl = core.http.basePath.prepend('/app/discover'); + + if (plugins.share) { + this.urlGenerator = plugins.share.urlGenerators.registerUrlGenerator( + new DiscoverUrlGenerator({ + appBasePath: baseUrl, + useHash: core.uiSettings.get('state:storeInSessionStorage'), + }) + ); + } + this.docViewsRegistry = new DocViewsRegistry(); setDocViewsRegistry(this.docViewsRegistry); this.docViewsRegistry.addDocView({ @@ -158,7 +200,7 @@ export class DiscoverPlugin // so history is lazily created (when app is mounted) // this prevents redundant `#` when not in discover app getHistory: getScopedHistory, - baseUrl: core.http.basePath.prepend('/app/discover'), + baseUrl, defaultSubUrl: '#/', storageKey: `lastUrl:${core.http.basePath.get()}:discover`, navLinkUpdater$: this.appStateUpdater, @@ -188,7 +230,7 @@ export class DiscoverPlugin id: 'discover', title: 'Discover', updater$: this.appStateUpdater.asObservable(), - order: -1004, + order: 1000, euiIconType: 'discoverApp', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, @@ -266,6 +308,7 @@ export class DiscoverPlugin }; return { + urlGenerator: this.urlGenerator, savedSearchLoader: createSavedSearchesLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns: plugins.data.indexPatterns, diff --git a/src/plugins/discover/public/url_generator.test.ts b/src/plugins/discover/public/url_generator.test.ts new file mode 100644 index 0000000000000..cf9beb246fea2 --- /dev/null +++ b/src/plugins/discover/public/url_generator.test.ts @@ -0,0 +1,259 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DiscoverUrlGenerator } from './url_generator'; +import { hashedItemStore, getStatesFromKbnUrl } from '../../kibana_utils/public'; +// eslint-disable-next-line +import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; +import { FilterStateStore } from '../../data/common'; + +const appBasePath: string = 'xyz/app/discover'; +const indexPatternId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; +const savedSearchId: string = '571aaf70-4c88-11e8-b3d7-01146121b73d'; + +interface SetupParams { + useHash?: boolean; +} + +const setup = async ({ useHash = false }: SetupParams = {}) => { + const generator = new DiscoverUrlGenerator({ + appBasePath, + useHash, + }); + + return { + generator, + }; +}; + +beforeEach(() => { + // @ts-ignore + hashedItemStore.storage = mockStorage; +}); + +describe('Discover url generator', () => { + test('can create a link to Discover with no state and no saved search', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({}); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(url.startsWith(appBasePath)).toBe(true); + expect(_a).toEqual({}); + expect(_g).toEqual({}); + }); + + test('can create a link to a saved search in Discover', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ savedSearchId }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(url.startsWith(`${appBasePath}#/${savedSearchId}`)).toBe(true); + expect(_a).toEqual({}); + expect(_g).toEqual({}); + }); + + test('can specify specific index pattern', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + indexPatternId, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + index: indexPatternId, + }); + expect(_g).toEqual({}); + }); + + test('can specify specific time range', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + time: { + from: 'now-15m', + mode: 'relative', + to: 'now', + }, + }); + }); + + test('can specify query', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + query: { + language: 'kuery', + query: 'foo', + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + query: { + language: 'kuery', + query: 'foo', + }, + }); + expect(_g).toEqual({}); + }); + + test('can specify local and global filters', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + filters: [ + { + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + { + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.GLOBAL_STATE, + }, + }, + ], + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + }, + ], + }); + expect(_g).toEqual({ + filters: [ + { + $state: { + store: 'globalState', + }, + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + + test('can set refresh interval', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + refreshInterval: { + pause: false, + value: 666, + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + refreshInterval: { + pause: false, + value: 666, + }, + }); + }); + + test('can set time range', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + timeRange: { + from: 'now-3h', + to: 'now', + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + time: { + from: 'now-3h', + to: 'now', + }, + }); + }); + + describe('useHash property', () => { + describe('when default useHash is set to false', () => { + test('when using default, sets index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(true); + }); + + test('when enabling useHash, does not set index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + useHash: true, + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(false); + }); + }); + + describe('when default useHash is set to true', () => { + test('when using default, does not set index pattern ID in the generated URL', async () => { + const { generator } = await setup({ useHash: true }); + const url = await generator.createUrl({ + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(false); + }); + + test('when disabling useHash, sets index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + useHash: false, + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(true); + }); + }); + }); +}); diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts new file mode 100644 index 0000000000000..42d689050d5ad --- /dev/null +++ b/src/plugins/discover/public/url_generator.ts @@ -0,0 +1,114 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + TimeRange, + Filter, + Query, + esFilters, + QueryState, + RefreshInterval, +} from '../../data/public'; +import { setStateToKbnUrl } from '../../kibana_utils/public'; +import { UrlGeneratorsDefinition } from '../../share/public'; + +export const DISCOVER_APP_URL_GENERATOR = 'DISCOVER_APP_URL_GENERATOR'; + +export interface DiscoverUrlGeneratorState { + /** + * Optionally set saved search ID. + */ + savedSearchId?: string; + + /** + * Optionally set index pattern ID. + */ + indexPatternId?: string; + + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval; + + /** + * Optionally apply filers. + */ + 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; +} + +interface Params { + appBasePath: string; + useHash: boolean; +} + +export class DiscoverUrlGenerator + implements UrlGeneratorsDefinition { + constructor(private readonly params: Params) {} + + public readonly id = DISCOVER_APP_URL_GENERATOR; + + public readonly createUrl = async ({ + filters, + indexPatternId, + query, + refreshInterval, + savedSearchId, + timeRange, + useHash = this.params.useHash, + }: DiscoverUrlGeneratorState): Promise => { + const savedSearchPath = savedSearchId ? encodeURIComponent(savedSearchId) : ''; + const appState: { + query?: Query; + filters?: Filter[]; + index?: string; + } = {}; + const queryState: QueryState = {}; + + if (query) appState.query = query; + if (filters) appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); + if (indexPatternId) appState.index = indexPatternId; + + if (timeRange) queryState.time = timeRange; + if (filters) queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + let url = `${this.params.appBasePath}#/${savedSearchPath}`; + url = setStateToKbnUrl('_g', queryState, { useHash }, url); + url = setStateToKbnUrl('_a', appState, { useHash }, url); + + return url; + }; +} diff --git a/src/plugins/embeddable/public/components/panel_options_menu/index.tsx b/src/plugins/embeddable/public/components/panel_options_menu/index.tsx index e0796990e0c4e..7790646a88a68 100644 --- a/src/plugins/embeddable/public/components/panel_options_menu/index.tsx +++ b/src/plugins/embeddable/public/components/panel_options_menu/index.tsx @@ -45,7 +45,7 @@ export const PanelOptionsMenu: React.FC = ({ }, [close]); const handleContextMenuClick = () => { - setOpen(isOpen => !isOpen); + setOpen((isOpen) => !isOpen); }; const handlePopoverClose = () => { diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index ffbe75f66b581..8cf3d1a15883b 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -80,7 +80,7 @@ export abstract class Container< } public reload() { - Object.values(this.children).forEach(child => child.reload()); + Object.values(this.children).forEach((child) => child.reload()); } public async addNewEmbeddable< @@ -131,7 +131,7 @@ export abstract class Container< // to default back to inherited input. However, if the particular value is not part of the container, then // the caller may be trying to explicitly tell the child to clear out a given value, so in that case, we want // to pass it along. - keys.forEach(key => { + keys.forEach((key) => { if (explicitInput[key] === undefined && containerInput[key] !== undefined) { return; } @@ -149,7 +149,7 @@ export abstract class Container< public destroy() { super.destroy(); - Object.values(this.children).forEach(child => child.destroy()); + Object.values(this.children).forEach((child) => child.destroy()); this.subscription.unsubscribe(); } @@ -251,7 +251,7 @@ export abstract class Container< // Container input overrides defaults. const explicitInput: Partial = partial; - getKeys(defaults).forEach(key => { + getKeys(defaults).forEach((key) => { // @ts-ignore We know this key might not exist on inheritedInput. const inheritedValue = inheritedInput[key]; if (inheritedValue === undefined && explicitInput[key] === undefined) { @@ -330,7 +330,7 @@ export abstract class Container< private maybeUpdateChildren() { const allIds = Object.keys({ ...this.input.panels, ...this.output.embeddableLoaded }); - allIds.forEach(id => { + allIds.forEach((id) => { if (this.input.panels[id] !== undefined && this.output.embeddableLoaded[id] === undefined) { this.onPanelAdded(this.input.panels[id]); } else if ( diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx index b046376a304ae..e29e941e898fb 100644 --- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx @@ -31,7 +31,7 @@ import { // eslint-disable-next-line import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { mount } from 'enzyme'; -import { embeddablePluginMock } from '../../mocks'; +import { embeddablePluginMock, createEmbeddablePanelMock } from '../../mocks'; test('EmbeddableChildPanel renders an embeddable when it is done loading', async () => { const inspector = inspectorPluginMock.createStartContract(); @@ -58,18 +58,17 @@ test('EmbeddableChildPanel renders an embeddable when it is done loading', async expect(newEmbeddable.id).toBeDefined(); + const testPanel = createEmbeddablePanelMock({ + getAllEmbeddableFactories: start.getEmbeddableFactories, + getEmbeddableFactory, + inspector, + }); + const component = mount( Promise.resolve([])} - getAllEmbeddableFactories={start.getEmbeddableFactories} - getEmbeddableFactory={getEmbeddableFactory} - notifications={{} as any} - application={{} as any} - overlays={{} as any} - inspector={inspector} - SavedObjectFinder={() => null} + PanelComponent={testPanel} /> ); @@ -97,19 +96,9 @@ test(`EmbeddableChildPanel renders an error message if the factory doesn't exist { getEmbeddableFactory } as any ); + const testPanel = createEmbeddablePanelMock({ inspector }); const component = mount( - Promise.resolve([])} - getAllEmbeddableFactories={(() => []) as any} - getEmbeddableFactory={(() => undefined) as any} - notifications={{} as any} - overlays={{} as any} - application={{} as any} - inspector={inspector} - SavedObjectFinder={() => null} - /> + ); await nextTick(); diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx index 70628665e6e8c..be8ff2c95fe09 100644 --- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx +++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx @@ -22,12 +22,7 @@ import React from 'react'; import { EuiLoadingChart } from '@elastic/eui'; import { Subscription } from 'rxjs'; -import { CoreStart } from 'src/core/public'; -import { UiActionsService } from 'src/plugins/ui_actions/public'; - -import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { ErrorEmbeddable, IEmbeddable } from '../embeddables'; -import { EmbeddablePanel } from '../panel'; import { IContainer } from './i_container'; import { EmbeddableStart } from '../../plugin'; @@ -35,14 +30,7 @@ export interface EmbeddableChildPanelProps { embeddableId: string; className?: string; container: IContainer; - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - application: CoreStart['application']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + PanelComponent: EmbeddableStart['EmbeddablePanel']; } interface State { @@ -87,6 +75,7 @@ export class EmbeddableChildPanel extends React.Component ) : ( - + )}
); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx index a8ac5abe42a1d..340d851f3eedf 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx @@ -51,7 +51,7 @@ class OutputTestEmbeddable extends Embeddable { reload() {} } -test('Embeddable calls input subscribers when changed', async done => { +test('Embeddable calls input subscribers when changed', async (done) => { const hello = new ContactCardEmbeddable( { id: '123', firstName: 'Brienne', lastName: 'Tarth' }, { execAction: (() => null) as any } @@ -60,7 +60,7 @@ test('Embeddable calls input subscribers when changed', async done => { const subscription = hello .getInput$() .pipe(skip(1)) - .subscribe(input => { + .subscribe((input) => { expect(input.nameTitle).toEqual('Sir'); done(); subscription.unsubscribe(); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.tsx index 81052620fd08d..1ba9e12ec4d20 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.tsx @@ -62,7 +62,7 @@ export class EmbeddableFactoryRenderer extends React.Component { return; } - factory.create(this.props.input).then(embeddable => { + factory.create(this.props.input).then((embeddable) => { this.embeddable = embeddable; this.setState({ loading: false }); }); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 384297d8dee7d..ff9f466a8d553 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -64,7 +64,7 @@ setup.registerEmbeddableFactory(embeddableFactory.type, embeddableFactory); const start = doStart(); const getEmbeddableFactory = start.getEmbeddableFactory; -test('HelloWorldContainer initializes embeddables', async done => { +test('HelloWorldContainer initializes embeddables', async (done) => { const container = new HelloWorldContainer( { id: '123', diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 36ddfb49b0312..4fcf8b9f608f8 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -111,7 +111,7 @@ export class EmbeddablePanel extends React.Component { const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { - badges = badges.filter(badge => disabledActions.indexOf(badge.id) === -1); + badges = badges.filter((badge) => disabledActions.indexOf(badge.id) === -1); } this.setState({ @@ -127,7 +127,7 @@ export class EmbeddablePanel extends React.Component { const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { - notifications = notifications.filter(badge => disabledActions.indexOf(badge.id) === -1); + notifications = notifications.filter((badge) => disabledActions.indexOf(badge.id) === -1); } this.setState({ @@ -248,12 +248,12 @@ export class EmbeddablePanel extends React.Component { const createGetUserData = (overlays: OverlayStart) => async function getUserData(context: { embeddable: IEmbeddable }) { - return new Promise<{ title: string | undefined }>(resolve => { + return new Promise<{ title: string | undefined }>((resolve) => { const session = overlays.openModal( toMountPoint( { + updateTitle={(title) => { session.close(); resolve({ title }); }} diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx index 3894d6fbed382..34a176400dbb9 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx @@ -81,7 +81,7 @@ test('createNewEmbeddable() add embeddable to container', async () => { expect(Object.values(container.getInput().panels).length).toBe(0); component.instance().createNewEmbeddable(CONTACT_CARD_EMBEDDABLE); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); const ids = Object.keys(container.getInput().panels); const embeddableId = ids[0]; @@ -123,7 +123,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()' getFactory={getEmbeddableFactory} getAllFactories={start.getEmbeddableFactories} notifications={core.notifications} - SavedObjectFinder={props => } + SavedObjectFinder={(props) => } /> ) as ReactWrapper; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 4c23916675e8f..d3e100366f2df 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -95,7 +95,8 @@ export class AddPanelFlyout extends React.Component { public onAddPanel = async (savedObjectId: string, savedObjectType: string, name: string) => { const factoryForSavedObjectType = [...this.props.getAllFactories()].find( - factory => factory.savedObjectMetaData && factory.savedObjectMetaData.type === savedObjectType + (factory) => + factory.savedObjectMetaData && factory.savedObjectMetaData.type === savedObjectType ); if (!factoryForSavedObjectType) { throw new EmbeddableFactoryNotFoundError(savedObjectType); @@ -111,8 +112,10 @@ export class AddPanelFlyout extends React.Component { private getCreateMenuItems(): ReactElement[] { return [...this.props.getAllFactories()] - .filter(factory => factory.isEditable() && !factory.isContainerType && factory.canCreateNew()) - .map(factory => ( + .filter( + (factory) => factory.isEditable() && !factory.isContainerType && factory.canCreateNew() + ) + .map((factory) => ( { const SavedObjectFinder = this.props.SavedObjectFinder; const metaData = [...this.props.getAllFactories()] .filter( - embeddableFactory => + (embeddableFactory) => Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType ) .map(({ savedObjectMetaData }) => savedObjectMetaData as any); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx index 6275dbd4eaa45..a4c80e1d86a80 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx @@ -41,11 +41,9 @@ describe('SavedObjectFinderCreateNew', () => { const onClick = jest.fn(); for (let i = 0; i < 3; i++) { items.push( - {`item${i + 1}`} + {`item${ + i + 1 + }`} ); } @@ -69,11 +67,9 @@ describe('SavedObjectFinderCreateNew', () => { const onClick = jest.fn(); for (let i = 0; i < 3; i++) { items.push( - {`item${i + 1}`} + {`item${ + i + 1 + }`} ); } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts index 2f66d8eb0d619..6fddcbc84faf7 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts @@ -66,7 +66,7 @@ beforeEach(async () => { } }); -test('Updates the embeddable title when given', async done => { +test('Updates the embeddable title when given', async (done) => { const getUserData = () => Promise.resolve({ title: 'What is up?' }); const customizePanelAction = new CustomizePanelTitleAction(getUserData); expect(embeddable.getInput().title).toBeUndefined(); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx index 663fa50420ccc..b590f20092939 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx @@ -64,7 +64,7 @@ export class CustomizePanelModal extends Component { }; private onHideTitleToggle = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ hideTitle: !prevState.hideTitle, })); }; @@ -118,7 +118,7 @@ export class CustomizePanelModal extends Component { disabled={this.state.hideTitle} placeholder={this.props.embeddable.getOutput().defaultTitle} value={this.state.title || ''} - onChange={e => this.updateTitle(e.target.value)} + onChange={(e) => this.updateTitle(e.target.value)} aria-label={i18n.translate( 'embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel', { diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index bb2eb52f9df72..8ba7be7880a7b 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -45,7 +45,7 @@ export interface PanelHeaderProps { } function renderBadges(badges: Array>, embeddable: IEmbeddable) { - return badges.map(badge => ( + return badges.map((badge) => ( >, embeddable: IEmbeddable ) { - return notifications.map(notification => { + return notifications.map((notification) => { const context = { embeddable }; let badge = ( diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx index 5548c9a0596b4..b4c349600e8f6 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx @@ -137,11 +137,11 @@ export class PanelOptionsMenu extends React.Component { + .then((actionContextMenuPanel) => { if (!this.mounted) return; this.setState({ actionContextMenuPanel }); }) - .catch(error => console.error(error)); // eslint-disable-line no-console + .catch((error) => console.error(error)); // eslint-disable-line no-console }; this.setState(({ isPopoverOpen }) => ({ isPopoverOpen: !isPopoverOpen }), after); }; diff --git a/src/plugins/embeddable/public/lib/test_samples/actions/get_message_modal.tsx b/src/plugins/embeddable/public/lib/test_samples/actions/get_message_modal.tsx index 7b396ce1bd10a..bb859d7b8d0c1 100644 --- a/src/plugins/embeddable/public/lib/test_samples/actions/get_message_modal.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/actions/get_message_modal.tsx @@ -58,7 +58,7 @@ export class GetMessageModal extends Component { this.setState({ message: e.target.value })} + onChange={(e) => this.setState({ message: e.target.value })} /> diff --git a/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx b/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx index 222fe1f6ed870..04898550532df 100644 --- a/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx @@ -57,7 +57,7 @@ export function createSendMessageAction(overlays: CoreStart['overlays']) { toMountPoint( modal.close()} - onDone={message => { + onDone={(message) => { modal.close(); sendMessage(context, message); }} diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx index 078e21df0f0ce..b82cd9ca7cc31 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx @@ -42,7 +42,7 @@ export interface ContactCardEmbeddableOptions { function getFullName(input: ContactCardEmbeddableInput) { const { nameTitle, firstName, lastName } = input; - const nameParts = [nameTitle, firstName, lastName].filter(name => name !== undefined); + const nameParts = [nameTitle, firstName, lastName].filter((name) => name !== undefined); return nameParts.join(' '); } diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx index f977329562b9b..893b6b04e50bc 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx @@ -50,7 +50,7 @@ export class ContactCardEmbeddableFactory } public getExplicitInput = (): Promise> => { - return new Promise(resolve => { + return new Promise((resolve) => { const modalSession = this.overlays.openModal( toMountPoint( this.setState({ firstName: e.target.value })} + onChange={(e) => this.setState({ firstName: e.target.value })} /> @@ -68,7 +68,7 @@ export class ContactCardInitializer extends Component this.setState({ lastName: e.target.value })} + onChange={(e) => this.setState({ lastName: e.target.value })} /> diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx index 31e14a0af59d7..913c3a0b30826 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx @@ -19,9 +19,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { CoreStart } from 'src/core/public'; -import { UiActionsService } from 'src/plugins/ui_actions/public'; -import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { Container, ViewMode, ContainerInput } from '../..'; import { HelloWorldContainerComponent } from './hello_world_container_component'; import { EmbeddableStart } from '../../../plugin'; @@ -45,14 +42,8 @@ interface HelloWorldContainerInput extends ContainerInput { } interface HelloWorldContainerOptions { - getActions: UiActionsService['getTriggerCompatibleActions']; getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - application: CoreStart['application']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + panelComponent: EmbeddableStart['EmbeddablePanel']; } export class HelloWorldContainer extends Container { @@ -78,14 +69,7 @@ export class HelloWorldContainer extends Container , node diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx index 49e9c34d95c95..5fefa1fc90720 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx @@ -20,22 +20,12 @@ import React, { Component, RefObject } from 'react'; import { Subscription } from 'rxjs'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { CoreStart } from 'src/core/public'; -import { UiActionsService } from 'src/plugins/ui_actions/public'; -import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { IContainer, PanelState, EmbeddableChildPanel } from '../..'; import { EmbeddableStart } from '../../../plugin'; interface Props { container: IContainer; - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - application: CoreStart['application']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + panelComponent: EmbeddableStart['EmbeddablePanel']; } interface State { @@ -52,7 +42,7 @@ export class HelloWorldContainerComponent extends Component { constructor(props: Props) { super(props); - Object.values(this.props.container.getInput().panels).forEach(panelState => { + Object.values(this.props.container.getInput().panels).forEach((panelState) => { this.roots[panelState.explicitInput.id] = React.createRef(); }); @@ -102,20 +92,13 @@ export class HelloWorldContainerComponent extends Component { } private renderList() { - const list = Object.values(this.state.panels).map(panelState => { + const list = Object.values(this.state.panels).map((panelState) => { const item = ( ); diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts deleted file mode 100644 index f5487c381cfcb..0000000000000 --- a/src/plugins/embeddable/public/mocks.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { - EmbeddableStart, - EmbeddableSetup, - EmbeddableSetupDependencies, - EmbeddableStartDependencies, -} from '.'; -import { EmbeddablePublicPlugin } from './plugin'; -import { coreMock } from '../../../core/public/mocks'; - -// eslint-disable-next-line -import { inspectorPluginMock } from '../../inspector/public/mocks'; -// eslint-disable-next-line -import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; - -export type Setup = jest.Mocked; -export type Start = jest.Mocked; - -const createSetupContract = (): Setup => { - const setupContract: Setup = { - registerEmbeddableFactory: jest.fn(), - setCustomEmbeddableFactoryProvider: jest.fn(), - }; - return setupContract; -}; - -const createStartContract = (): Start => { - const startContract: Start = { - getEmbeddableFactories: jest.fn(), - getEmbeddableFactory: jest.fn(), - EmbeddablePanel: jest.fn(), - }; - return startContract; -}; - -const createInstance = (setupPlugins: Partial = {}) => { - const plugin = new EmbeddablePublicPlugin({} as any); - const setup = plugin.setup(coreMock.createSetup(), { - uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), - }); - const doStart = (startPlugins: Partial = {}) => - plugin.start(coreMock.createStart(), { - uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), - inspector: inspectorPluginMock.createStartContract(), - }); - return { - plugin, - setup, - doStart, - }; -}; - -export const embeddablePluginMock = { - createSetupContract, - createStartContract, - createInstance, -}; diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx new file mode 100644 index 0000000000000..9da0b7602c4f8 --- /dev/null +++ b/src/plugins/embeddable/public/mocks.tsx @@ -0,0 +1,116 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { + EmbeddableStart, + EmbeddableSetup, + EmbeddableSetupDependencies, + EmbeddableStartDependencies, + IEmbeddable, + EmbeddablePanel, +} from '.'; +import { EmbeddablePublicPlugin } from './plugin'; +import { coreMock } from '../../../core/public/mocks'; +import { UiActionsService } from './lib/ui_actions'; +import { CoreStart } from '../../../core/public'; +import { Start as InspectorStart } from '../../inspector/public'; + +// eslint-disable-next-line +import { inspectorPluginMock } from '../../inspector/public/mocks'; +// eslint-disable-next-line +import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +interface CreateEmbeddablePanelMockArgs { + getActions: UiActionsService['getTriggerCompatibleActions']; + getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; + overlays: CoreStart['overlays']; + notifications: CoreStart['notifications']; + application: CoreStart['application']; + inspector: InspectorStart; + SavedObjectFinder: React.ComponentType; +} + +export const createEmbeddablePanelMock = ({ + getActions, + getEmbeddableFactory, + getAllEmbeddableFactories, + overlays, + notifications, + application, + inspector, + SavedObjectFinder, +}: Partial) => { + return ({ embeddable }: { embeddable: IEmbeddable }) => ( + Promise.resolve([]))} + getAllEmbeddableFactories={getAllEmbeddableFactories || ((() => []) as any)} + getEmbeddableFactory={getEmbeddableFactory || ((() => undefined) as any)} + notifications={notifications || ({} as any)} + application={application || ({} as any)} + overlays={overlays || ({} as any)} + inspector={inspector || ({} as any)} + SavedObjectFinder={SavedObjectFinder || (() => null)} + /> + ); +}; + +const createSetupContract = (): Setup => { + const setupContract: Setup = { + registerEmbeddableFactory: jest.fn(), + setCustomEmbeddableFactoryProvider: jest.fn(), + }; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = { + getEmbeddableFactories: jest.fn(), + getEmbeddableFactory: jest.fn(), + EmbeddablePanel: jest.fn(), + }; + return startContract; +}; + +const createInstance = (setupPlugins: Partial = {}) => { + const plugin = new EmbeddablePublicPlugin({} as any); + const setup = plugin.setup(coreMock.createSetup(), { + uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), + }); + const doStart = (startPlugins: Partial = {}) => + plugin.start(coreMock.createStart(), { + uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), + inspector: inspectorPluginMock.createStartContract(), + }); + return { + plugin, + setup, + doStart, + }; +}; + +export const embeddablePluginMock = { + createSetupContract, + createStartContract, + createInstance, +}; diff --git a/src/plugins/embeddable/public/plugin.test.ts b/src/plugins/embeddable/public/plugin.test.ts index 804f3e2e8a7b4..e37d602ad8cac 100644 --- a/src/plugins/embeddable/public/plugin.test.ts +++ b/src/plugins/embeddable/public/plugin.test.ts @@ -42,7 +42,7 @@ test('can set custom embeddable factory provider', async () => { const coreStart = coreMock.createStart(); const { setup, doStart } = testPlugin(coreSetup, coreStart); - const customProvider: EmbeddableFactoryProvider = def => ({ + const customProvider: EmbeddableFactoryProvider = (def) => ({ ...defaultEmbeddableFactoryProvider(def), getDisplayName: () => 'Intercepted!', }); @@ -66,7 +66,7 @@ test('custom embeddable factory provider test for intercepting embeddable creati const { setup, doStart } = testPlugin(coreSetup, coreStart); let updateCount = 0; - const customProvider: EmbeddableFactoryProvider = def => { + const customProvider: EmbeddableFactoryProvider = (def) => { return { ...defaultEmbeddableFactoryProvider(def), create: async (input, parent) => { @@ -105,6 +105,6 @@ test('custom embeddable factory provider test for intercepting embeddable creati expect(updateCount).toEqual(2); embeddable!.destroy(); - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); expect(updateCount).toEqual(0); }); diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 36f49f2508e80..40a288545ef27 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -92,7 +92,7 @@ export class EmbeddablePublicPlugin implements Plugin { + this.embeddableFactoryDefinitions.forEach((def) => { this.embeddableFactories.set( def.type, this.customEmbeddableFactoryProvider @@ -166,7 +166,7 @@ export class EmbeddablePublicPlugin implements Plugin { - this.embeddableFactoryDefinitions.forEach(def => this.ensureFactoryExists(def.type)); + this.embeddableFactoryDefinitions.forEach((def) => this.ensureFactoryExists(def.type)); }; private ensureFactoryExists = (type: string) => { diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts index ebb76c743393b..ec92f334267f5 100644 --- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts +++ b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts @@ -31,8 +31,7 @@ import { FilterableEmbeddableInput, } from '../lib/test_samples'; // eslint-disable-next-line -import { inspectorPluginMock } from '../../../../plugins/inspector/public/mocks'; -import { esFilters } from '../../../../plugins/data/public'; +import { esFilters } from '../../../data/public'; test('ApplyFilterAction applies the filter to the root of the container tree', async () => { const { doStart, setup } = testPlugin(); @@ -95,26 +94,16 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a }); test('ApplyFilterAction is incompatible if the root container does not accept a filter as input', async () => { - const { doStart, coreStart, setup } = testPlugin(); - const inspector = inspectorPluginMock.createStartContract(); + const { doStart, setup } = testPlugin(); const factory = new FilterableEmbeddableFactory(); setup.registerEmbeddableFactory(factory.type, factory); const api = doStart(); const applyFilterAction = createFilterAction(); - const parent = new HelloWorldContainer( - { id: 'root', panels: {} }, - { - getActions: () => Promise.resolve([]), - getEmbeddableFactory: api.getEmbeddableFactory, - getAllEmbeddableFactories: api.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector, - SavedObjectFinder: () => null, - } - ); + + const parent = new HelloWorldContainer({ id: 'root', panels: {} }, { + getEmbeddableFactory: api.getEmbeddableFactory, + } as any); const embeddable = await parent.addNewEmbeddable< FilterableContainerInput, EmbeddableOutput, @@ -130,27 +119,17 @@ test('ApplyFilterAction is incompatible if the root container does not accept a }); test('trying to execute on incompatible context throws an error ', async () => { - const { doStart, coreStart, setup } = testPlugin(); - const inspector = inspectorPluginMock.createStartContract(); + const { doStart, setup } = testPlugin(); const factory = new FilterableEmbeddableFactory(); setup.registerEmbeddableFactory(factory.type, factory); const api = doStart(); const applyFilterAction = createFilterAction(); - const parent = new HelloWorldContainer( - { id: 'root', panels: {} }, - { - getActions: () => Promise.resolve([]), - getEmbeddableFactory: api.getEmbeddableFactory, - getAllEmbeddableFactories: api.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector, - SavedObjectFinder: () => null, - } - ); + + const parent = new HelloWorldContainer({ id: 'root', panels: {} }, { + getEmbeddableFactory: api.getEmbeddableFactory, + } as any); const embeddable = await parent.addNewEmbeddable< FilterableContainerInput, diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts index d2769e208ba42..490f0c00c7c4d 100644 --- a/src/plugins/embeddable/public/tests/container.test.ts +++ b/src/plugins/embeddable/public/tests/container.test.ts @@ -48,6 +48,7 @@ import { coreMock } from '../../../../core/public/mocks'; import { testPlugin } from './test_plugin'; import { of } from './helpers'; import { esFilters, Filter } from '../../../../plugins/data/public'; +import { createEmbeddablePanelMock } from '../mocks'; async function creatHelloWorldContainerAndEmbeddable( containerInput: ContainerInput = { id: 'hello', panels: {} }, @@ -68,15 +69,18 @@ async function creatHelloWorldContainerAndEmbeddable( const start = doStart(); - const container = new HelloWorldContainer(containerInput, { + const testPanel = createEmbeddablePanelMock({ getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, getAllEmbeddableFactories: start.getEmbeddableFactories, overlays: coreStart.overlays, notifications: coreStart.notifications, application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + }); + + const container = new HelloWorldContainer(containerInput, { + getEmbeddableFactory: start.getEmbeddableFactory, + panelComponent: testPanel, }); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -88,10 +92,10 @@ async function creatHelloWorldContainerAndEmbeddable( throw new Error('Error adding embeddable'); } - return { container, embeddable, coreSetup, coreStart, setup, start, uiActions }; + return { container, embeddable, coreSetup, coreStart, setup, start, uiActions, testPanel }; } -test('Container initializes embeddables', async done => { +test('Container initializes embeddables', async (done) => { const { container } = await creatHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -130,8 +134,9 @@ test('Container.addNewEmbeddable', async () => { expect(embeddableInContainer.id).toBe(embeddable.id); }); -test('Container.removeEmbeddable removes and cleans up', async done => { - const { start, coreStart, uiActions } = await creatHelloWorldContainerAndEmbeddable(); +test('Container.removeEmbeddable removes and cleans up', async (done) => { + const { start, testPanel } = await creatHelloWorldContainerAndEmbeddable(); + const container = new HelloWorldContainer( { id: 'hello', @@ -143,14 +148,8 @@ test('Container.removeEmbeddable removes and cleans up', async done => { }, }, { - getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, - getAllEmbeddableFactories: start.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); const embeddable = await container.addNewEmbeddable< @@ -294,7 +293,7 @@ test('Container view mode change propagates to children', async () => { expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); }); -test(`Container updates its state when a child's input is updated`, async done => { +test(`Container updates its state when a child's input is updated`, async (done) => { const { container, embeddable, @@ -323,15 +322,17 @@ test(`Container updates its state when a child's input is updated`, async done = // Make sure a brand new container built off the output of container also creates an embeddable // with "Dr.", not the default the embeddable was first added with. Makes sure changed input // is preserved with the container. - const containerClone = new HelloWorldContainer(container.getInput(), { + const testPanel = createEmbeddablePanelMock({ getActions: uiActions.getTriggerCompatibleActions, - getAllEmbeddableFactories: start.getEmbeddableFactories, getEmbeddableFactory: start.getEmbeddableFactory, - notifications: coreStart.notifications, + getAllEmbeddableFactories: start.getEmbeddableFactories, overlays: coreStart.overlays, + notifications: coreStart.notifications, application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + }); + const containerClone = new HelloWorldContainer(container.getInput(), { + getEmbeddableFactory: start.getEmbeddableFactory, + panelComponent: testPanel, }); const cloneSubscription = Rx.merge( containerClone.getOutput$(), @@ -381,7 +382,7 @@ test(`Derived container state passed to children`, async () => { subscription.unsubscribe(); }); -test(`Can subscribe to children embeddable updates`, async done => { +test(`Can subscribe to children embeddable updates`, async (done) => { const { embeddable } = await creatHelloWorldContainerAndEmbeddable( { id: 'hello container', @@ -404,7 +405,7 @@ test(`Can subscribe to children embeddable updates`, async done => { embeddable.updateInput({ nameTitle: 'Dr.' }); }); -test('Test nested reactions', async done => { +test('Test nested reactions', async (done) => { const { container, embeddable } = await creatHelloWorldContainerAndEmbeddable( { id: 'hello', panels: {}, viewMode: ViewMode.VIEW }, { @@ -472,7 +473,7 @@ test('Explicit embeddable input mapped to undefined will default to inherited', ]); }); -test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async done => { +test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async (done) => { const { container } = await creatHelloWorldContainerAndEmbeddable({ id: 'hello', panels: {} }); const embeddable = await container.addNewEmbeddable< @@ -523,7 +524,7 @@ test('Panel removed from input state', async () => { }; container.updateInput(newInput); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); expect(container.getChild(embeddable.id)).toBeUndefined(); expect(container.getOutput().embeddableLoaded[embeddable.id]).toBeUndefined(); @@ -554,7 +555,7 @@ test('Panel added to input state', async () => { ); container2.updateInput(container.getInput()); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); expect(container.getChild(embeddable.id)).toBeDefined(); expect(container.getOutput().embeddableLoaded[embeddable.id]).toBe(true); @@ -562,7 +563,7 @@ test('Panel added to input state', async () => { expect(container.getOutput().embeddableLoaded[embeddable2.id]).toBe(true); }); -test('Container changes made directly after adding a new embeddable are propagated', async done => { +test('Container changes made directly after adding a new embeddable are propagated', async (done) => { const coreSetup = coreMock.createSetup(); const coreStart = coreMock.createStart(); const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart); @@ -575,6 +576,14 @@ test('Container changes made directly after adding a new embeddable are propagat const start = doStart(); + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: start.getEmbeddableFactory, + getAllEmbeddableFactories: start.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); const container = new HelloWorldContainer( { id: 'hello', @@ -582,14 +591,8 @@ test('Container changes made directly after adding a new embeddable are propagat viewMode: ViewMode.EDIT, }, { - getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, - getAllEmbeddableFactories: start.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); @@ -618,7 +621,7 @@ test('Container changes made directly after adding a new embeddable are propagat container.updateInput({ viewMode: ViewMode.VIEW }); }); -test('container stores ErrorEmbeddables when a factory for a child cannot be found (so the panel can be removed)', async done => { +test('container stores ErrorEmbeddables when a factory for a child cannot be found (so the panel can be removed)', async (done) => { const { container } = await creatHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -639,7 +642,7 @@ test('container stores ErrorEmbeddables when a factory for a child cannot be fou }); }); -test('container stores ErrorEmbeddables when a saved object cannot be found', async done => { +test('container stores ErrorEmbeddables when a saved object cannot be found', async (done) => { const { container } = await creatHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -660,7 +663,7 @@ test('container stores ErrorEmbeddables when a saved object cannot be found', as }); }); -test('ErrorEmbeddables get updated when parent does', async done => { +test('ErrorEmbeddables get updated when parent does', async (done) => { const { container } = await creatHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -701,20 +704,22 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in coreMock.createStart() ); const start = doStart(); + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: start.getEmbeddableFactory, + getAllEmbeddableFactories: start.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); const container = new HelloWorldContainer( { id: 'hello', panels: {}, }, { - getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, - getAllEmbeddableFactories: start.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); @@ -723,7 +728,7 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in expect(error.message).toMatchInlineSnapshot(`"Panel not found"`); }); -test('untilEmbeddableLoaded() resolves if child is loaded in the container', async done => { +test('untilEmbeddableLoaded() resolves if child is loaded in the container', async (done) => { const { setup, doStart, coreStart, uiActions } = testPlugin( coreMock.createSetup(), coreMock.createStart() @@ -731,6 +736,14 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy const factory = new HelloWorldEmbeddableFactory(); setup.registerEmbeddableFactory(factory.type, factory); const start = doStart(); + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: start.getEmbeddableFactory, + getAllEmbeddableFactories: start.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); const container = new HelloWorldContainer( { id: 'hello', @@ -742,14 +755,8 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy }, }, { - getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, - getAllEmbeddableFactories: start.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); @@ -759,7 +766,7 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy done(); }); -test('untilEmbeddableLoaded resolves with undefined if child is subsequently removed', async done => { +test('untilEmbeddableLoaded resolves with undefined if child is subsequently removed', async (done) => { const { doStart, setup, coreStart, uiActions } = testPlugin( coreMock.createSetup(), coreMock.createStart() @@ -771,6 +778,14 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem setup.registerEmbeddableFactory(factory.type, factory); const start = doStart(); + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: start.getEmbeddableFactory, + getAllEmbeddableFactories: start.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); const container = new HelloWorldContainer( { id: 'hello', @@ -782,18 +797,12 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem }, }, { - getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, - getAllEmbeddableFactories: start.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); - container.untilEmbeddableLoaded('123').then(embed => { + container.untilEmbeddableLoaded('123').then((embed) => { expect(embed).toBeUndefined(); done(); }); @@ -801,7 +810,7 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem container.updateInput({ panels: {} }); }); -test('adding a panel then subsequently removing it before its loaded removes the panel', async done => { +test('adding a panel then subsequently removing it before its loaded removes the panel', async (done) => { const { doStart, coreStart, uiActions, setup } = testPlugin( coreMock.createSetup(), coreMock.createStart() @@ -812,6 +821,14 @@ test('adding a panel then subsequently removing it before its loaded removes the }); setup.registerEmbeddableFactory(factory.type, factory); const start = doStart(); + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: start.getEmbeddableFactory, + getAllEmbeddableFactories: start.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); const container = new HelloWorldContainer( { id: 'hello', @@ -823,14 +840,8 @@ test('adding a panel then subsequently removing it before its loaded removes the }, }, { - getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, - getAllEmbeddableFactories: start.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx index a9cb83504d958..311efae49f735 100644 --- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx +++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx @@ -37,6 +37,7 @@ import { testPlugin } from './test_plugin'; import { CustomizePanelModal } from '../lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal'; import { mount } from 'enzyme'; import { EmbeddableStart } from '../plugin'; +import { createEmbeddablePanelMock } from '../mocks'; let api: EmbeddableStart; let container: Container; @@ -55,17 +56,20 @@ beforeEach(async () => { setup.registerEmbeddableFactory(contactCardFactory.type, contactCardFactory); api = doStart(); + + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: api.getEmbeddableFactory, + getAllEmbeddableFactories: api.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); container = new HelloWorldContainer( { id: '123', panels: {} }, { - getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: api.getEmbeddableFactory, - getAllEmbeddableFactories: api.getEmbeddableFactories, - overlays: coreStart.overlays, - notifications: coreStart.notifications, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); const contactCardEmbeddable = await container.addNewEmbeddable< diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts index ef3c4b6f17e7f..d64ff94d71800 100644 --- a/src/plugins/embeddable/public/tests/explicit_input.test.ts +++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts @@ -36,6 +36,7 @@ import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world // eslint-disable-next-line import { coreMock } from '../../../../core/public/mocks'; import { esFilters, Filter } from '../../../../plugins/data/public'; +import { createEmbeddablePanelMock } from '../mocks'; const { setup, doStart, coreStart, uiActions } = testPlugin( coreMock.createSetup(), @@ -79,18 +80,20 @@ test('Explicit embeddable input mapped to undefined will default to inherited', ]); }); -test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async done => { +test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async (done) => { + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: start.getEmbeddableFactory, + getAllEmbeddableFactories: start.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); const container = new HelloWorldContainer( { id: 'hello', panels: {} }, { - getActions: uiActions.getTriggerCompatibleActions, - getAllEmbeddableFactories: start.getEmbeddableFactories, getEmbeddableFactory: start.getEmbeddableFactory, - notifications: coreStart.notifications, - overlays: coreStart.overlays, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); @@ -121,6 +124,14 @@ test('Explicit embeddable input mapped to undefined with no inherited value will // but before the embeddable factory returns the embeddable, that the `inheritedChildInput` and // embeddable input comparisons won't cause explicit input to be set when it shouldn't. test('Explicit input tests in async situations', (done: () => void) => { + const testPanel = createEmbeddablePanelMock({ + getActions: uiActions.getTriggerCompatibleActions, + getEmbeddableFactory: start.getEmbeddableFactory, + getAllEmbeddableFactories: start.getEmbeddableFactories, + overlays: coreStart.overlays, + notifications: coreStart.notifications, + application: coreStart.application, + }); const container = new HelloWorldContainer( { id: 'hello', @@ -132,14 +143,8 @@ test('Explicit input tests in async situations', (done: () => void) => { }, }, { - getActions: uiActions.getTriggerCompatibleActions, - getAllEmbeddableFactories: start.getEmbeddableFactories, getEmbeddableFactory: start.getEmbeddableFactory, - notifications: coreStart.notifications, - overlays: coreStart.overlays, - application: coreStart.application, - inspector: {} as any, - SavedObjectFinder: () => null, + panelComponent: testPanel, } ); diff --git a/src/plugins/embeddable/public/tests/helpers.ts b/src/plugins/embeddable/public/tests/helpers.ts index de15ef61c2c60..8bdfbc60e1a41 100644 --- a/src/plugins/embeddable/public/tests/helpers.ts +++ b/src/plugins/embeddable/public/tests/helpers.ts @@ -22,7 +22,7 @@ export const expectErrorAsync = (fn: (...args: unknown[]) => Promise): .then(() => { throw new Error('Expected an error throw.'); }) - .catch(error => { + .catch((error) => { if (error.message === 'Expected an error throw.') { throw error; } diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx index 8f4b2b976d141..35d8cdaa67fc4 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx @@ -43,7 +43,7 @@ const toArray = (value: string | string[]): string[] => export const WithPrivileges = ({ privileges: requiredPrivileges, children }: Props) => { const { isLoading, privileges } = useAuthorizationContext(); - const privilegesToArray: Privilege[] = toArray(requiredPrivileges).map(p => { + const privilegesToArray: Privilege[] = toArray(requiredPrivileges).map((p) => { const [section, privilege] = p.split('.'); if (!privilege) { // Oh! we forgot to use the dot "." notation. @@ -54,7 +54,7 @@ export const WithPrivileges = ({ privileges: requiredPrivileges, children }: Pro const hasPrivileges = isLoading ? false - : privilegesToArray.every(privilege => { + : privilegesToArray.every((privilege) => { const [section, requiredPrivilege] = privilege; if (!privileges.missingPrivileges[section]) { // if the section does not exist in our missingPriviledges, everything is OK diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/index.ts new file mode 100644 index 0000000000000..a9c6ea1e01d54 --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { useXJsonMode } from './use_xjson_mode'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/use_xjson_mode.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/use_xjson_mode.ts new file mode 100644 index 0000000000000..b783045492f05 --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/use_xjson_mode.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { XJsonLang } from '@kbn/monaco'; +import { useXJsonMode as useBaseXJsonMode } from '../xjson'; + +interface ReturnValue extends ReturnType { + XJsonLang: typeof XJsonLang; +} + +export const useXJsonMode = (json: Parameters[0]): ReturnValue => { + return { + ...useBaseXJsonMode(json), + XJsonLang, + }; +}; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/helpers/enzyme_helpers.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/helpers/enzyme_helpers.tsx deleted file mode 100644 index d2e13fe7622f9..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/helpers/enzyme_helpers.tsx +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Components using the react-intl module require access to the intl context. - * This is not available when mounting single components in Enzyme. - * These helper functions aim to address that and wrap a valid, - * intl context around them. - */ - -import { I18nProvider, InjectedIntl, intlShape } from '@kbn/i18n/react'; -import { mount, ReactWrapper, render, shallow } from 'enzyme'; -import React, { ReactElement, ValidationMap } from 'react'; -import { act as reactAct } from 'react-dom/test-utils'; - -// Use fake component to extract `intl` property to use in tests. -const { intl } = (mount( - -
-
-).find('IntlProvider') as ReactWrapper<{}, {}, import('react-intl').IntlProvider>) - .instance() - .getChildContext(); - -function getOptions(context = {}, childContextTypes = {}, props = {}) { - return { - context: { - ...context, - intl, - }, - childContextTypes: { - ...childContextTypes, - intl: intlShape, - }, - ...props, - }; -} - -/** - * When using React-Intl `injectIntl` on components, props.intl is required. - */ -function nodeWithIntlProp(node: ReactElement): ReactElement { - return React.cloneElement(node, { intl }); -} - -/** - * Creates the wrapper instance using shallow with provided intl object into context - * - * @param node The React element or cheerio wrapper - * @param options properties to pass into shallow wrapper - * @return The wrapper instance around the rendered output with intl object in context - */ -export function shallowWithIntl( - node: ReactElement, - { - context, - childContextTypes, - ...props - }: { - context?: any; - childContextTypes?: ValidationMap; - } = {} -) { - const options = getOptions(context, childContextTypes, props); - - return shallow(nodeWithIntlProp(node), options); -} - -/** - * Creates the wrapper instance using mount with provided intl object into context - * - * @param node The React element or cheerio wrapper - * @param options properties to pass into mount wrapper - * @return The wrapper instance around the rendered output with intl object in context - */ -export function mountWithIntl( - node: ReactElement, - { - context, - childContextTypes, - ...props - }: { - context?: any; - childContextTypes?: ValidationMap; - } = {} -) { - const options = getOptions(context, childContextTypes, props); - - return mount(nodeWithIntlProp(node), options); -} - -/** - * Creates the wrapper instance using render with provided intl object into context - * - * @param node The React element or cheerio wrapper - * @param options properties to pass into render wrapper - * @return The wrapper instance around the rendered output with intl object in context - */ -export function renderWithIntl( - node: ReactElement, - { - context, - childContextTypes, - ...props - }: { - context?: any; - childContextTypes?: ValidationMap; - } = {} -) { - const options = getOptions(context, childContextTypes, props); - - return render(nodeWithIntlProp(node), options); -} - -/** - * A wrapper object to provide access to the state of a hook under test and to - * enable interaction with that hook. - */ -interface ReactHookWrapper { - /* Ensures that async React operations have settled before and after the - * given actor callback is called. The actor callback arguments provide easy - * access to the last hook value and allow for updating the arguments passed - * to the hook body to trigger reevaluation. - */ - act: (actor: (lastHookValue: HookValue, setArgs: (args: Args) => void) => void) => void; - /* The enzyme wrapper around the test component. */ - component: ReactWrapper; - /* The most recent value return the by test harness of the hook. */ - getLastHookValue: () => HookValue; - /* The jest Mock function that receives the hook values for introspection. */ - hookValueCallback: jest.Mock; -} - -/** - * Allows for execution of hooks inside of a test component which records the - * returned values. - * - * @param body A function that calls the hook and returns data derived from it - * @param WrapperComponent A component that, if provided, will be wrapped - * around the test component. This can be useful to provide context values. - * @return {ReactHookWrapper} An object providing access to the hook state and - * functions to interact with it. - */ -export const mountHook = ( - body: (args: Args) => HookValue, - WrapperComponent?: React.ComponentType, - initialArgs: Args = {} as Args -): ReactHookWrapper => { - const hookValueCallback = jest.fn(); - let component!: ReactWrapper; - - const act: ReactHookWrapper['act'] = actor => { - reactAct(() => { - actor(getLastHookValue(), (args: Args) => component.setProps(args)); - component.update(); - }); - }; - - const getLastHookValue = () => { - const calls = hookValueCallback.mock.calls; - if (calls.length <= 0) { - throw Error('No recent hook value present.'); - } - return calls[calls.length - 1][0]; - }; - - const HookComponent = (props: Args) => { - hookValueCallback(body(props)); - return null; - }; - const TestComponent: React.FunctionComponent = args => - WrapperComponent ? ( - - - - ) : ( - - ); - - reactAct(() => { - component = mount(); - }); - - return { - act, - component, - getLastHookValue, - hookValueCallback, - }; -}; - -export const nextTick = () => new Promise(res => process.nextTick(res)); diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/helpers/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/helpers/index.ts deleted file mode 100644 index 4b80fb13e1c46..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/helpers/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { mountWithIntl } from './enzyme_helpers'; - -export { findTestSubject } from './find_test_subject'; - -export { WithStore } from './redux_helpers'; - -export { WithMemoryRouter, WithRoute, reactRouterMock } from './router_helpers'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/index.ts deleted file mode 100644 index bcf408cc5af5c..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './testbed'; -export * from './lib'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/lib/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/lib/index.ts deleted file mode 100644 index af810deaca5b0..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/lib/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { nextTick, getRandomString, getRandomNumber } from './utils'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/lib/utils.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/lib/utils.ts deleted file mode 100644 index bfde6b1fdd089..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/lib/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Chance from 'chance'; - -const chance = new Chance(); -const CHARS_POOL = 'abcdefghijklmnopqrstuvwxyz'; - -export const nextTick = (time = 0) => new Promise(resolve => setTimeout(resolve, time)); - -export const getRandomNumber = (range: { min: number; max: number } = { min: 1, max: 20 }) => - chance.integer(range); - -export const getRandomString = (options = {}) => - `${chance.string({ pool: CHARS_POOL, ...options })}-${Date.now()}`; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/testbed/types.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/testbed/types.ts deleted file mode 100644 index eb1e8a56f3f8f..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/test_utils_temp/testbed/types.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Store } from 'redux'; -import { ReactWrapper } from 'enzyme'; - -export type SetupFunc = (props?: any) => TestBed | Promise>; - -export interface EuiTableMetaData { - /** Array of rows of the table. Each row exposes its reactWrapper and its columns */ - rows: Array<{ - reactWrapper: ReactWrapper; - columns: Array<{ - reactWrapper: ReactWrapper; - value: string; - }>; - }>; - /** A 2 dimensional array of rows & columns containing - * the text content of each cell of the table */ - tableCellsValues: string[][]; -} - -export interface TestBed { - /** The comonent under test */ - component: ReactWrapper; - /** - * Pass it a `data-test-subj` and it will return true if it exists or false if it does not exist. - * - * @param testSubject The data test subject to look for (can be a nested path. e.g. "detailPanel.mySection"). - * @param count The number of times the subject needs to appear in order to return "true" - */ - exists: (testSubject: T, count?: number) => boolean; - /** - * Pass it a `data-test-subj` and it will return an Enzyme reactWrapper of the node. - * You can target a nested test subject by separating it with a dot ('.'); - * - * @param testSubject The data test subject to look for - * - * @example - * - ```ts - find('nameInput'); - // or more specific, - // "nameInput" is a child of "myForm" - find('myForm.nameInput'); - ``` - */ - find: (testSubject: T, reactWrapper?: ReactWrapper) => ReactWrapper; - /** - * Update the props of the mounted component - * - * @param updatedProps The updated prop object - */ - setProps: (updatedProps: any) => void; - /** - * Helper to wait until an element appears in the DOM as hooks updates cycles are tricky. - * Useful when loading a component that fetches a resource from the server - * and we need to wait for the data to be fetched (and bypass any "loading" state). - */ - waitFor: (testSubject: T, count?: number) => Promise; - form: { - /** - * Set the value of a form text input. - * - * In some cases, changing an input value triggers an HTTP request to validate - * the field. Even if we return immediately the response on the mock server we - * still need to wait until the next tick before the DOM updates. - * Setting isAsync to "true" takes care of that. - * - * @param input The form input. Can either be a data-test-subj or a reactWrapper (can be a nested path. e.g. "myForm.myInput"). - * @param value The value to set - * @param isAsync If set to true will return a Promise that resolves on the next "tick" - */ - setInputValue: ( - input: T | ReactWrapper, - value: string, - isAsync?: boolean - ) => Promise | void; - /** - * Select or unselect a form checkbox. - * - * @param dataTestSubject The test subject of the checkbox (can be a nested path. e.g. "myForm.mySelect"). - * @param isChecked Defines if the checkobx is active or not - */ - selectCheckBox: (checkboxTestSubject: T, isChecked?: boolean) => void; - /** - * Toggle the EuiSwitch - * - * @param switchTestSubject The test subject of the EuiSwitch (can be a nested path. e.g. "myForm.mySwitch"). - */ - toggleEuiSwitch: (switchTestSubject: T, isChecked?: boolean) => void; - /** - * The EUI ComboBox is a special input as it needs the ENTER key to be pressed - * in order to register the value set. This helpers automatically does that. - * - * @param comboBoxTestSubject The data test subject of the EuiComboBox (can be a nested path. e.g. "myForm.myComboBox"). - * @param value The value to set - */ - setComboBoxValue: (comboBoxTestSubject: T, value: string) => void; - /** - * Get a list of the form error messages that are visible in the DOM. - */ - getErrorsMessages: () => string[]; - }; - table: { - getMetaData: (tableTestSubject: T) => EuiTableMetaData; - }; - router: { - /** - * Navigate to another React router - */ - navigateTo: (url: string) => void; - }; -} - -export interface TestBedConfig { - /** The default props to pass to the mounted component. */ - defaultProps?: Record; - /** Configuration object for the react-router `MemoryRouter. */ - memoryRouter?: MemoryRouterConfig; - /** An optional redux store. You can also provide a function that returns a store. */ - store?: (() => Store) | Store | null; - /* Mount the component asynchronously. When using "hooked" components with _useEffect()_ calls, you need to set this to "true". */ - doMountAsync?: boolean; -} - -export interface MemoryRouterConfig { - /** Flag to add or not the `MemoryRouter`. If set to `false`, there won't be any router and the component won't be wrapped on a ``. */ - wrapComponent?: boolean; - /** The React Router **initial entries** setting ([see documentation](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/MemoryRouter.md)) */ - initialEntries?: string[]; - /** The React Router **initial index** setting ([see documentation](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/MemoryRouter.md)) */ - initialIndex?: number; - /** The route **path** for the mounted component (defaults to `"/"`) */ - componentRoutePath?: string; - /** A callBack that will be called with the React Router instance once mounted */ - onRouter?: (router: any) => void; -} - -/** - * Utility type: extracts returned type from a Promise. - */ -export type UnwrapPromise = T extends Promise ? P : T; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/index.ts new file mode 100644 index 0000000000000..a9c6ea1e01d54 --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { useXJsonMode } from './use_xjson_mode'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/use_xjson_mode.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/use_xjson_mode.ts new file mode 100644 index 0000000000000..7dcc7c9ed83bc --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/use_xjson_mode.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useState, Dispatch } from 'react'; +import { collapseLiteralStrings, expandLiteralStrings } from '../../public'; + +interface ReturnValue { + xJson: string; + setXJson: Dispatch; + convertToJson: typeof collapseLiteralStrings; +} + +export const useXJsonMode = (json: Record | string | null): ReturnValue => { + const [xJson, setXJson] = useState(() => + json === null + ? '' + : expandLiteralStrings(typeof json === 'string' ? json : JSON.stringify(json, null, 2)) + ); + + return { + xJson, + setXJson, + convertToJson: collapseLiteralStrings, + }; +}; diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_daily.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_daily.js index 4ac6b0b8a256a..f038766766fe0 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_daily.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_daily.js @@ -41,7 +41,7 @@ export const CronDaily = ({ minute, minuteOptions, hour, hourOptions, onChange } aria-label={i18n.translate('esUi.cronEditor.cronDaily.hourSelectLabel', { defaultMessage: 'Hour', })} - onChange={e => onChange({ hour: e.target.value })} + onChange={(e) => onChange({ hour: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronDaily.fieldHour.textAtLabel', { defaultMessage: 'At', @@ -57,7 +57,7 @@ export const CronDaily = ({ minute, minuteOptions, hour, hourOptions, onChange } aria-label={i18n.translate('esUi.cronEditor.cronDaily.minuteSelectLabel', { defaultMessage: 'Minute', })} - onChange={e => onChange({ minute: e.target.value })} + onChange={(e) => onChange({ minute: e.target.value })} fullWidth prepend=":" data-test-subj="cronFrequencyDailyMinuteSelect" diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.js index 576658882589f..18e9ffcb27c56 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.js @@ -53,27 +53,27 @@ function makeSequence(min, max) { return values; } -const MINUTE_OPTIONS = makeSequence(0, 59).map(value => ({ +const MINUTE_OPTIONS = makeSequence(0, 59).map((value) => ({ value: value.toString(), text: padLeft(value, 2, '0'), })); -const HOUR_OPTIONS = makeSequence(0, 23).map(value => ({ +const HOUR_OPTIONS = makeSequence(0, 23).map((value) => ({ value: value.toString(), text: padLeft(value, 2, '0'), })); -const DAY_OPTIONS = makeSequence(1, 7).map(value => ({ +const DAY_OPTIONS = makeSequence(1, 7).map((value) => ({ value: value.toString(), text: getDayName(value - 1), })); -const DATE_OPTIONS = makeSequence(1, 31).map(value => ({ +const DATE_OPTIONS = makeSequence(1, 31).map((value) => ({ value: value.toString(), text: getOrdinalValue(value), })); -const MONTH_OPTIONS = makeSequence(1, 12).map(value => ({ +const MONTH_OPTIONS = makeSequence(1, 12).map((value) => ({ value: value.toString(), text: getMonthName(value - 1), })); @@ -208,7 +208,7 @@ export class CronEditor extends Component { }; } - onChangeFrequency = frequency => { + onChangeFrequency = (frequency) => { const { onChange, fieldToPreferredValueMap } = this.props; // Update fields which aren't editable with acceptable baseline values. @@ -232,7 +232,7 @@ export class CronEditor extends Component { }); }; - onChangeFields = fields => { + onChangeFields = (fields) => { const { onChange, frequency, fieldToPreferredValueMap } = this.props; const editableFields = Object.keys(frequencyToFieldsMap[frequency]); @@ -354,7 +354,7 @@ export class CronEditor extends Component { this.onChangeFrequency(e.target.value)} + onChange={(e) => this.onChangeFrequency(e.target.value)} fullWidth prepend={i18n.translate('esUi.cronEditor.textEveryLabel', { defaultMessage: 'Every', diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js index 194fccf110174..a04e83195b97f 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js @@ -36,7 +36,7 @@ export const CronHourly = ({ minute, minuteOptions, onChange }) => ( onChange({ minute: e.target.value })} + onChange={(e) => onChange({ minute: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronHourly.fieldMinute.textAtLabel', { defaultMessage: 'At', diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_monthly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_monthly.js index f3ffc082a4c60..28057bd7d9293 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_monthly.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_monthly.js @@ -44,7 +44,7 @@ export const CronMonthly = ({ onChange({ date: e.target.value })} + onChange={(e) => onChange({ date: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronMonthly.textOnTheLabel', { defaultMessage: 'On the', @@ -68,7 +68,7 @@ export const CronMonthly = ({ aria-label={i18n.translate('esUi.cronEditor.cronMonthly.hourSelectLabel', { defaultMessage: 'Hour', })} - onChange={e => onChange({ hour: e.target.value })} + onChange={(e) => onChange({ hour: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronMonthly.fieldHour.textAtLabel', { defaultMessage: 'At', @@ -84,7 +84,7 @@ export const CronMonthly = ({ aria-label={i18n.translate('esUi.cronEditor.cronMonthly.minuteSelectLabel', { defaultMessage: 'Minute', })} - onChange={e => onChange({ minute: e.target.value })} + onChange={(e) => onChange({ minute: e.target.value })} fullWidth prepend=":" data-test-subj="cronFrequencyMonthlyMinuteSelect" diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_weekly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_weekly.js index b328b5cb958b4..c06eecbb381b3 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_weekly.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_weekly.js @@ -44,7 +44,7 @@ export const CronWeekly = ({ onChange({ day: e.target.value })} + onChange={(e) => onChange({ day: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronWeekly.textOnLabel', { defaultMessage: 'On', @@ -68,7 +68,7 @@ export const CronWeekly = ({ aria-label={i18n.translate('esUi.cronEditor.cronWeekly.hourSelectLabel', { defaultMessage: 'Hour', })} - onChange={e => onChange({ hour: e.target.value })} + onChange={(e) => onChange({ hour: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronWeekly.fieldHour.textAtLabel', { defaultMessage: 'At', @@ -81,7 +81,7 @@ export const CronWeekly = ({ onChange({ minute: e.target.value })} + onChange={(e) => onChange({ minute: e.target.value })} aria-label={i18n.translate('esUi.cronEditor.cronWeekly.minuteSelectLabel', { defaultMessage: 'Minute', })} diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_yearly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_yearly.js index 26a57756273bd..c3b9691750937 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_yearly.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_yearly.js @@ -46,7 +46,7 @@ export const CronYearly = ({ onChange({ month: e.target.value })} + onChange={(e) => onChange({ month: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronYearly.fieldMonth.textInLabel', { defaultMessage: 'In', @@ -65,7 +65,7 @@ export const CronYearly = ({ onChange({ date: e.target.value })} + onChange={(e) => onChange({ date: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronYearly.fieldDate.textOnTheLabel', { defaultMessage: 'On the', @@ -89,7 +89,7 @@ export const CronYearly = ({ aria-label={i18n.translate('esUi.cronEditor.cronYearly.hourSelectLabel', { defaultMessage: 'Hour', })} - onChange={e => onChange({ hour: e.target.value })} + onChange={(e) => onChange({ hour: e.target.value })} fullWidth prepend={i18n.translate('esUi.cronEditor.cronYearly.fieldHour.textAtLabel', { defaultMessage: 'At', @@ -105,7 +105,7 @@ export const CronYearly = ({ aria-label={i18n.translate('esUi.cronEditor.cronYearly.minuteSelectLabel', { defaultMessage: 'Minute', })} - onChange={e => onChange({ minute: e.target.value })} + onChange={(e) => onChange({ minute: e.target.value })} fullWidth prepend=":" data-test-subj="cronFrequencyYearlyMinuteSelect" diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts index 398cf531612ef..41f6a65a5b410 100644 --- a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts @@ -21,7 +21,7 @@ import ace from 'brace'; const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules'); const oop = ace.acequire('ace/lib/oop'); -export const ElasticsearchSqlHighlightRules = function(this: any) { +export const ElasticsearchSqlHighlightRules = function (this: any) { // See https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-commands.html const keywords = 'describe|between|in|like|not|and|or|desc|select|from|where|having|group|by|order' + diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts index 3663b92d92a6f..951cf5fa279b5 100644 --- a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts @@ -27,7 +27,7 @@ import { ScriptHighlightRules } from './script_highlight_rules'; const { JsonHighlightRules } = ace.acequire('ace/mode/json_highlight_rules'); const oop = ace.acequire('ace/lib/oop'); -const jsonRules = function(root: any) { +const jsonRules = function (root: any) { root = root ? root : 'json'; const rules: any = {}; const xJsonRules = [ diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts index 9fbfedba1d23f..8d4ebcfad9496 100644 --- a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts @@ -43,16 +43,16 @@ const XJsonMode: any = function XJsonMode(this: any) { oop.inherits(XJsonMode, JSONMode); // Then clobber `createWorker` method to install our worker source. Per ace's wiki: https://github.com/ajaxorg/ace/wiki/Syntax-validation -(XJsonMode.prototype as any).createWorker = function(session: ace.IEditSession) { +(XJsonMode.prototype as any).createWorker = function (session: ace.IEditSession) { const xJsonWorker = new WorkerClient(['ace'], workerModule, 'JsonWorker'); xJsonWorker.attachToDocument(session.getDocument()); - xJsonWorker.on('annotate', function(e: { data: any }) { + xJsonWorker.on('annotate', function (e: { data: any }) { session.setAnnotations(e.data); }); - xJsonWorker.on('terminate', function() { + xJsonWorker.on('terminate', function () { session.clearAnnotations(); }); diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts index 92c14ade791cd..419e80ad1608f 100644 --- a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts +++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts @@ -36,7 +36,7 @@ describe('JSON to XJSON conversion tools', () => { }); }); -_.each(collapsingTests.split(/^=+$/m), function(fixture) { +_.each(collapsingTests.split(/^=+$/m), function (fixture) { if (fixture.trim() === '') { return; } @@ -45,12 +45,12 @@ _.each(collapsingTests.split(/^=+$/m), function(fixture) { const expanded = fixture[1].trim(); const collapsed = fixture[2].trim(); - test('Literal collapse - ' + name, function() { + test('Literal collapse - ' + name, function () { expect(utils.collapseLiteralStrings(expanded)).toEqual(collapsed); }); }); -_.each(expandingTests.split(/^=+$/m), function(fixture) { +_.each(expandingTests.split(/^=+$/m), function (fixture) { if (fixture.trim() === '') { return; } @@ -59,7 +59,7 @@ _.each(expandingTests.split(/^=+$/m), function(fixture) { const collapsed = fixture[1].trim(); const expanded = fixture[2].trim(); - test('Literal expand - ' + name, function() { + test('Literal expand - ' + name, function () { expect(utils.expandLiteralStrings(collapsed)).toEqual(expanded); }); }); diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 7e5510d7c9c65..4ab791289dd88 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -47,6 +47,10 @@ export { expandLiteralStrings, } from './console_lang'; +import * as Monaco from './monaco'; + +export { Monaco }; + export { AuthorizationContext, AuthorizationProvider, diff --git a/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts b/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts index cf81a2abebabf..a6792543cd726 100644 --- a/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts +++ b/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts @@ -39,7 +39,7 @@ describe('Index name validation', () => { }); it('should not allow illegal characters', () => { - INDEX_ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { + INDEX_ILLEGAL_CHARACTERS_VISIBLE.forEach((char) => { const illegalCharacters = findIllegalCharactersInIndexName(`name${char}`); expect(illegalCharacters).toEqual([char]); }); diff --git a/src/plugins/es_ui_shared/public/monaco/index.ts b/src/plugins/es_ui_shared/public/monaco/index.ts new file mode 100644 index 0000000000000..23ba93e913234 --- /dev/null +++ b/src/plugins/es_ui_shared/public/monaco/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { useXJsonMode } from '../../__packages_do_not_import__/monaco'; diff --git a/src/plugins/es_ui_shared/public/request/request.test.js b/src/plugins/es_ui_shared/public/request/request.test.js index cc554b531d88a..190c32517eefe 100644 --- a/src/plugins/es_ui_shared/public/request/request.test.js +++ b/src/plugins/es_ui_shared/public/request/request.test.js @@ -31,11 +31,11 @@ const TestHook = ({ callback }) => { let element; -const testHook = callback => { +const testHook = (callback) => { element = mount(); }; -const wait = async wait => new Promise(resolve => setTimeout(resolve, wait || 1)); +const wait = async (wait) => new Promise((resolve) => setTimeout(resolve, wait || 1)); // FLAKY: // - https://github.com/elastic/kibana/issues/42561 diff --git a/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts b/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts index 2b0bf0c8a3a7c..3a093ac6869d0 100644 --- a/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts +++ b/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts @@ -16,23 +16,18 @@ * specific language governing permissions and limitations * under the License. */ - -import { useState } from 'react'; -import { XJsonMode, collapseLiteralStrings, expandLiteralStrings } from '../../../public'; +import { XJsonMode } from '../../../public'; +import { useXJsonMode as useBaseXJsonMode } from '../../../__packages_do_not_import__/xjson'; const xJsonMode = new XJsonMode(); -export const useXJsonMode = (json: Record | string | null) => { - const [xJson, setXJson] = useState(() => - json === null - ? '' - : expandLiteralStrings(typeof json === 'string' ? json : JSON.stringify(json, null, 2)) - ); +interface ReturnValue extends ReturnType { + xJsonMode: typeof xJsonMode; +} +export const useXJsonMode = (json: Parameters[0]): ReturnValue => { return { - xJson, - setXJson, + ...useBaseXJsonMode(json), xJsonMode, - convertToJson: collapseLiteralStrings, }; }; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx index a10da62fa6906..9fb804eb7fafa 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx @@ -70,7 +70,7 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, ...rest }: Props) => }; const onComboChange = (options: EuiComboBoxOptionOption[]) => { - field.setValue(options.map(option => option.label)); + field.setValue(options.map((option) => option.label)); }; const onSearchComboChange = (value: string) => { @@ -95,7 +95,7 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, ...rest }: Props) => placeholder={i18n.translate('esUi.forms.comboBoxField.placeHolderText', { defaultMessage: 'Type and then hit "ENTER"', })} - selectedOptions={(field.value as any[]).map(v => ({ label: v }))} + selectedOptions={(field.value as any[]).map((v) => ({ label: v }))} onCreateOption={onCreateComboOption} onChange={onComboChange} onSearchChange={onSearchComboChange} diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/json_editor_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/json_editor_field.tsx index 9cd5cb3d0f2b9..fd57e098cf806 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/json_editor_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/json_editor_field.tsx @@ -34,7 +34,7 @@ export const JsonEditorField = ({ field, ...rest }: Props) => { const { label, helpText, value, setValue } = field; const onJsonUpdate: OnJsonEditorUpdateHandler = useCallback( - updatedJson => { + (updatedJson) => { setValue(updatedJson.data.raw); }, [setValue] diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx index e77337e4ecf53..a33c8009802ee 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx @@ -45,7 +45,7 @@ export const MultiSelectField = ({ field, euiFieldProps = {}, ...rest }: Props) { + onChange={(options) => { field.setValue(options); }} options={field.value as any[]} diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/select_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/select_field.tsx index 6e6aeb4de18fe..c22394435be83 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/select_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/select_field.tsx @@ -50,7 +50,7 @@ export const SelectField = ({ field, euiFieldProps, ...rest }: Props) => { { + onChange={(e) => { field.setValue(e.target.value); }} options={[]} diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx index 14a468613ec56..46e2d663eb324 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx @@ -48,7 +48,7 @@ export const SuperSelectField = ({ field, euiFieldProps = { options: [] }, ...re { + onChange={(value) => { field.setValue(value); }} isInvalid={isInvalid} diff --git a/src/plugins/es_ui_shared/static/forms/helpers/de_serializers.ts b/src/plugins/es_ui_shared/static/forms/helpers/de_serializers.ts index 274aa82b31834..a3ee2b8b84189 100644 --- a/src/plugins/es_ui_shared/static/forms/helpers/de_serializers.ts +++ b/src/plugins/es_ui_shared/static/forms/helpers/de_serializers.ts @@ -25,13 +25,13 @@ export const multiSelectComponent: Record = { // This deSerializer takes the previously selected options and map them // against the default select options values. selectedValueToOptions(selectOptions) { - return defaultFormValue => { + return (defaultFormValue) => { // If there are no default form value, it means that no previous value has been selected. if (!defaultFormValue) { return selectOptions; } - return (selectOptions as EuiSelectableOption[]).map(option => ({ + return (selectOptions as EuiSelectableOption[]).map((option) => ({ ...option, checked: (defaultFormValue as string[]).includes(option.label) ? 'on' : undefined, })); diff --git a/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts b/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts index bae6b4c2652ca..98287f6bac35d 100644 --- a/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts +++ b/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts @@ -46,7 +46,7 @@ export const multiSelectComponent: Record> = { * @param value The Eui Selectable options array */ optionsToSelectedValue(options: EuiSelectableOption[]): string[] { - return options.filter(option => option.checked === 'on').map(option => option.label); + return options.filter((option) => option.checked === 'on').map((option) => option.label); }, }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx index 8b11b619ea8e0..3e4ce4a412b3b 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx @@ -38,7 +38,7 @@ describe('', () => { - {formData => { + {(formData) => { onFormData(formData); return null; }} @@ -106,7 +106,7 @@ describe('', () => { - {formData => { + {(formData) => { onFormData(formData); return null; }} @@ -145,7 +145,7 @@ describe('', () => { - {formData => { + {(formData) => { onFormData(formData); return null; }} diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts index ddf2212490476..4c4a7f0642022 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts @@ -42,7 +42,7 @@ export const FormDataProvider = React.memo(({ children, pathsToWatch }: Props) = ? (pathsToWatch as string[]) : ([pathsToWatch] as string[]); - if (valuesToWatchArray.some(value => previousRawData.current[value] !== raw[value])) { + if (valuesToWatchArray.some((value) => previousRawData.current[value] !== raw[value])) { previousRawData.current = raw; setFormData(raw); } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts index 3f71f83c55694..1605c09f575f6 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts @@ -93,15 +93,15 @@ export const UseArray = ({ ); const addItem = () => { - setItems(previousItems => { + setItems((previousItems) => { const itemIndex = previousItems.length; return [...previousItems, getNewItemAtIndex(itemIndex)]; }); }; const removeItem = (id: number) => { - setItems(previousItems => { - const updatedItems = previousItems.filter(item => item.id !== id); + setItems((previousItems) => { + const updatedItems = previousItems.filter((item) => item.id !== id); return updatePaths(updatedItems); }); }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx index 136f3e7ad5688..589879f37900e 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx @@ -107,7 +107,7 @@ export const UseField = React.memo(UseFieldComp) as typeof UseFieldComp; * @param partialProps Partial props to apply to all instances */ export function getUseField(partialProps: Partial>) { - return function(props: Partial>) { + return function (props: Partial>) { const componentProps = { ...partialProps, ...props } as Props; return {...componentProps} />; }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.tsx index a81af924eb3bd..d69527e36249b 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.tsx @@ -41,7 +41,7 @@ export const UseMultiFields = ({ fields, children }: Props) => { const { id } = fieldsArray[index]; return ( - {field => { + {(field) => { hookFields[id] = field; return index === fieldsArray.length - 1 ? children(hookFields) : renderField(index + 1); }} diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx index 5dcd076b41533..a7b8713a23a74 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx @@ -32,7 +32,7 @@ export const FormProvider = ({ children, form }: Props) => ( {children} ); -export const useFormContext = function() { +export const useFormContext = function () { const context = useContext(FormContext) as FormHook; if (context === undefined) { throw new Error('useFormContext must be used within a '); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index 3814bbe62e120..9800af2398927 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -79,8 +79,8 @@ export const useField = ( ? (validationTypeToFilterOut as string[]) : ([validationTypeToFilterOut] as string[]); - return _errors.filter(error => - validationTypeToArray.every(_type => error.validationType !== _type) + return _errors.filter((error) => + validationTypeToArray.every((_type) => error.validationType !== _type) ); }; @@ -275,7 +275,7 @@ export const useField = ( // -- API // ---------------------------------- const clearErrors: FieldHook['clearErrors'] = (validationType = VALIDATION_TYPES.FIELD) => { - setErrors(previousErrors => filterErrors(previousErrors, validationType)); + setErrors((previousErrors) => filterErrors(previousErrors, validationType)); }; /** @@ -331,7 +331,7 @@ export const useField = ( * * @param newValue The new value to assign to the field */ - const setValue: FieldHook['setValue'] = newValue => { + const setValue: FieldHook['setValue'] = (newValue) => { if (isPristine) { setPristine(false); } @@ -340,8 +340,8 @@ export const useField = ( setStateValue(formattedValue); }; - const _setErrors: FieldHook['setErrors'] = _errors => { - setErrors(_errors.map(error => ({ validationType: VALIDATION_TYPES.FIELD, ...error }))); + const _setErrors: FieldHook['setErrors'] = (_errors) => { + setErrors(_errors.map((error) => ({ validationType: VALIDATION_TYPES.FIELD, ...error }))); }; /** @@ -349,7 +349,7 @@ export const useField = ( * * @param event Form input change event */ - const onChange: FieldHook['onChange'] = event => { + const onChange: FieldHook['onChange'] = (event) => { const newValue = {}.hasOwnProperty.call(event!.target, 'checked') ? event.target.checked : event.target.value; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index f539f306db700..f9286d99cbf80 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -73,7 +73,7 @@ export function useForm( useEffect(() => { return () => { - formUpdateSubscribers.current.forEach(subscription => subscription.unsubscribe()); + formUpdateSubscribers.current.forEach((subscription) => subscription.unsubscribe()); formUpdateSubscribers.current = []; isUnmounted.current = true; }; @@ -116,7 +116,7 @@ export function useForm( ) => { if (getDataOptions.unflatten) { const nonEmptyFields = stripEmptyFields(fieldsRefs.current); - const fieldsValue = mapFormFields(nonEmptyFields, field => field.__serializeOutput()); + const fieldsValue = mapFormFields(nonEmptyFields, (field) => field.__serializeOutput()); return serializer(unflattenObject(fieldsValue)) as T; } @@ -147,7 +147,7 @@ export function useForm( const updateFormValidity = () => { const fieldsArray = fieldsToArray(); - const areAllFieldsValidated = fieldsArray.every(field => field.isValidated); + const areAllFieldsValidated = fieldsArray.every((field) => field.isValidated); if (!areAllFieldsValidated) { // If *not* all the fiels have been validated, the validity of the form is unknown, thus still "undefined" @@ -160,10 +160,10 @@ export function useForm( return isFormValid; }; - const validateFields: FormHook['__validateFields'] = async fieldNames => { + const validateFields: FormHook['__validateFields'] = async (fieldNames) => { const fieldsToValidate = fieldNames - .map(name => fieldsRefs.current[name]) - .filter(field => field !== undefined); + .map((name) => fieldsRefs.current[name]) + .filter((field) => field !== undefined); if (fieldsToValidate.length === 0) { // Nothing to validate @@ -171,7 +171,7 @@ export function useForm( } const formData = getFormData({ unflatten: false }); - await Promise.all(fieldsToValidate.map(field => field.validate({ formData }))); + await Promise.all(fieldsToValidate.map((field) => field.validate({ formData }))); const isFormValid = updateFormValidity(); const areFieldsValid = fieldsToValidate.every(isFieldValid); @@ -181,7 +181,7 @@ export function useForm( const validateAllFields = async (): Promise => { const fieldsArray = fieldsToArray(); - const fieldsToValidate = fieldsArray.filter(field => !field.isValidated); + const fieldsToValidate = fieldsArray.filter((field) => !field.isValidated); let isFormValid: boolean | undefined = isValid; @@ -197,12 +197,12 @@ export function useForm( return isFormValid; } - ({ isFormValid } = await validateFields(fieldsToValidate.map(field => field.path))); + ({ isFormValid } = await validateFields(fieldsToValidate.map((field) => field.path))); return isFormValid!; }; - const addField: FormHook['__addField'] = field => { + const addField: FormHook['__addField'] = (field) => { fieldsRefs.current[field.path] = field; if (!{}.hasOwnProperty.call(getFormData$().value, field.path)) { @@ -211,11 +211,11 @@ export function useForm( } }; - const removeField: FormHook['__removeField'] = _fieldNames => { + const removeField: FormHook['__removeField'] = (_fieldNames) => { const fieldNames = Array.isArray(_fieldNames) ? _fieldNames : [_fieldNames]; const currentFormData = { ...getFormData$().value } as FormData; - fieldNames.forEach(name => { + fieldNames.forEach((name) => { delete fieldsRefs.current[name]; delete currentFormData[name]; }); @@ -245,16 +245,16 @@ export function useForm( const getFields: FormHook['getFields'] = () => fieldsRefs.current; - const getFieldDefaultValue: FormHook['getFieldDefaultValue'] = fieldName => + const getFieldDefaultValue: FormHook['getFieldDefaultValue'] = (fieldName) => get(defaultValueDeserialized, fieldName); - const readFieldConfigFromSchema: FormHook['__readFieldConfigFromSchema'] = fieldName => { + const readFieldConfigFromSchema: FormHook['__readFieldConfigFromSchema'] = (fieldName) => { const config = (get(schema ? schema : {}, fieldName) as FieldConfig) || {}; return config; }; - const submitForm: FormHook['submit'] = async e => { + const submitForm: FormHook['submit'] = async (e) => { if (e) { e.preventDefault(); } @@ -278,8 +278,8 @@ export function useForm( return { data: formData, isValid: isFormValid! }; }; - const subscribe: FormHook['subscribe'] = handler => { - const subscription = getFormData$().subscribe(raw => { + const subscribe: FormHook['subscribe'] = (handler) => { + const subscription = getFormData$().subscribe((raw) => { if (!isUnmounted.current) { handler({ isValid, data: { raw, format: getFormData }, validate: validateAllFields }); } @@ -290,7 +290,7 @@ export function useForm( return { unsubscribe() { formUpdateSubscribers.current = formUpdateSubscribers.current.filter( - sub => sub !== subscription + (sub) => sub !== subscription ); return subscription.unsubscribe(); }, diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts index c805a9ca40e63..8f516c9c8a46f 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts @@ -49,7 +49,7 @@ export class Subject { next(value: T) { if (value !== this.value) { this.value = value; - this.callbacks.forEach(fn => fn(value)); + this.callbacks.forEach((fn) => fn(value)); } } } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts index f54a648fb2fb9..f9f718b8b2f33 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts @@ -17,8 +17,6 @@ * under the License. */ -export { - registerTestBed, - getRandomString, - TestBed, -} from '../../../__packages_do_not_import__/test_utils_temp'; +export { registerTestBed, TestBed } from '../../../../../test_utils/public/testbed'; + +export { getRandomString } from '../../../../../test_utils/public/helpers'; diff --git a/src/plugins/expressions/common/ast/types.ts b/src/plugins/expressions/common/ast/types.ts index 0b505f117a580..d5039d0adb318 100644 --- a/src/plugins/expressions/common/ast/types.ts +++ b/src/plugins/expressions/common/ast/types.ts @@ -18,7 +18,7 @@ */ import { ExpressionValue, ExpressionValueError } from '../expression_types'; -import { ExpressionFunction } from '../../public'; +import { ExpressionFunction } from '../../common'; export type ExpressionAstNode = | ExpressionAstExpression diff --git a/src/plugins/expressions/common/execution/container.ts b/src/plugins/expressions/common/execution/container.ts index d6271869134d1..6302c0adb550b 100644 --- a/src/plugins/expressions/common/execution/container.ts +++ b/src/plugins/expressions/common/execution/container.ts @@ -66,16 +66,16 @@ export interface ExecutionPureTransitions { } export const executionPureTransitions: ExecutionPureTransitions = { - start: state => () => ({ + start: (state) => () => ({ ...state, state: 'pending', }), - setResult: state => result => ({ + setResult: (state) => (result) => ({ ...state, state: 'result', result, }), - setError: state => error => ({ + setError: (state) => (error) => ({ ...state, state: 'error', error, diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index 4776204a8ab2f..2e83d16dd778e 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -20,7 +20,7 @@ import { Execution } from './execution'; import { parseExpression, ExpressionAstExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; -import { ExpressionFunctionDefinition } from '../../public'; +import { ExpressionFunctionDefinition } from '../../common'; import { ExecutionContract } from './execution_contract'; beforeAll(() => { @@ -264,9 +264,9 @@ describe('Execution', () => { expect(execution.state.get().result).toBe(undefined); execution.start(null); expect(execution.state.get().result).toBe(undefined); - await new Promise(r => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 1)); expect(execution.state.get().result).toBe(undefined); - await new Promise(r => setTimeout(r, 11)); + await new Promise((r) => setTimeout(r, 11)); expect(execution.state.get().result).toBe(null); }); }); diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 6ee12d97a6422..7bfb14b8bfa1c 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -193,16 +193,16 @@ export class Execution< const { resolve, reject } = this.firstResultFuture; const chainPromise = this.invokeChain(this.state.get().ast.chain, input); - this.race(chainPromise).then(resolve, error => { + this.race(chainPromise).then(resolve, (error) => { if (this.abortController.signal.aborted) resolve(createAbortErrorValue()); else reject(error); }); this.firstResultFuture.promise.then( - result => { + (result) => { this.state.transitions.setResult(result); }, - error => { + (error) => { this.state.transitions.setError(error); } ); @@ -396,7 +396,7 @@ export class Execution< // Actually resolve unless the argument definition says not to const resolvedArgValues = await Promise.all( - argNames.map(argName => { + argNames.map((argName) => { const interpretFns = resolveArgFns[argName]; if (!argDefs[argName].resolve) return interpretFns; return Promise.all(interpretFns.map((fn: any) => fn())); diff --git a/src/plugins/expressions/common/executor/container.ts b/src/plugins/expressions/common/executor/container.ts index c9c1ab34e7ac3..fea58ec6294de 100644 --- a/src/plugins/expressions/common/executor/container.ts +++ b/src/plugins/expressions/common/executor/container.ts @@ -43,9 +43,9 @@ export interface ExecutorPureTransitions { } export const pureTransitions: ExecutorPureTransitions = { - addFunction: state => fn => ({ ...state, functions: { ...state.functions, [fn.name]: fn } }), - addType: state => type => ({ ...state, types: { ...state.types, [type.name]: type } }), - extendContext: state => extraContext => ({ + addFunction: (state) => (fn) => ({ ...state, functions: { ...state.functions, [fn.name]: fn } }), + addType: (state) => (type) => ({ ...state, types: { ...state.types, [type.name]: type } }), + extendContext: (state) => (extraContext) => ({ ...state, context: { ...state.context, ...extraContext }, }), @@ -58,8 +58,8 @@ export interface ExecutorPureSelectors { } export const pureSelectors: ExecutorPureSelectors = { - getFunction: state => id => state.functions[id] || null, - getType: state => id => state.types[id] || null, + getFunction: (state) => (id) => state.functions[id] || null, + getType: (state) => (id) => state.types[id] || null, getContext: ({ context }) => () => context, }; diff --git a/src/plugins/expressions/common/executor/executor.test.ts b/src/plugins/expressions/common/executor/executor.test.ts index 4e43cedd18157..81845401d32e4 100644 --- a/src/plugins/expressions/common/executor/executor.test.ts +++ b/src/plugins/expressions/common/executor/executor.test.ts @@ -51,7 +51,7 @@ describe('Executor', () => { for (const type of expressionTypes.typeSpecs) executor.registerType(type); const types = executor.getTypes(); expect(Object.keys(types).sort()).toEqual( - expressionTypes.typeSpecs.map(spec => spec.name).sort() + expressionTypes.typeSpecs.map((spec) => spec.name).sort() ); }); }); @@ -81,7 +81,7 @@ describe('Executor', () => { executor.registerFunction(functionDefinition); const functions = executor.getFunctions(); expect(Object.keys(functions).sort()).toEqual( - expressionFunctions.functionSpecs.map(spec => spec.name).sort() + expressionFunctions.functionSpecs.map((spec) => spec.name).sort() ); }); }); diff --git a/src/plugins/expressions/common/expression_functions/specs/font.ts b/src/plugins/expressions/common/expression_functions/specs/font.ts index 3e305998a0157..c8016bfacc710 100644 --- a/src/plugins/expressions/common/expression_functions/specs/font.ts +++ b/src/plugins/expressions/common/expression_functions/specs/font.ts @@ -26,14 +26,14 @@ const dashify = (str: string) => { return str .trim() .replace(/([a-z])([A-Z])/g, '$1-$2') - .replace(/\W/g, m => (/[À-ž]/.test(m) ? m : '-')) + .replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-')) .replace(/^-+|-+$/g, '') .toLowerCase(); }; const inlineStyle = (obj: Record) => { if (!obj) return ''; - const styles = Object.keys(obj).map(key => { + const styles = Object.keys(obj).map((key) => { const prop = dashify(key); const line = prop.concat(':').concat(String(obj[key])); return line; @@ -123,7 +123,7 @@ export const font: ExpressionFunctionDefinition<'font', null, Arguments, Style> values: { list: Object.values(FontWeight) .slice(0, -1) - .map(weight => `\`"${weight}"\``) + .map((weight) => `\`"${weight}"\``) .join(', '), end: `\`"${Object.values(FontWeight).slice(-1)[0]}"\``, }, diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts index bc721a772d50f..016208aefdfc6 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts @@ -26,7 +26,7 @@ import { ExecutionContext } from '../../../execution/types'; * overriding with any provided args. */ export const functionWrapper = (spec: AnyExpressionFunctionDefinition) => { - const defaultArgs = mapValues(spec.args, argSpec => argSpec.default); + const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default); return ( context: object | null, args: Record = {}, diff --git a/src/plugins/expressions/common/expression_types/expression_type.test.ts b/src/plugins/expressions/common/expression_types/expression_type.test.ts index a692ec9501cc5..b94d9a305121f 100644 --- a/src/plugins/expressions/common/expression_types/expression_type.test.ts +++ b/src/plugins/expressions/common/expression_types/expression_type.test.ts @@ -25,8 +25,8 @@ export const boolean: ExpressionTypeDefinition<'boolean', boolean> = { name: 'boolean', from: { null: () => false, - number: n => Boolean(n), - string: s => Boolean(s), + number: (n) => Boolean(n), + string: (s) => Boolean(s), }, to: { render: (value): ExpressionValueRender<{ text: string }> => { diff --git a/src/plugins/expressions/common/expression_types/specs/boolean.ts b/src/plugins/expressions/common/expression_types/specs/boolean.ts index fee4608418406..d730f95d7c423 100644 --- a/src/plugins/expressions/common/expression_types/specs/boolean.ts +++ b/src/plugins/expressions/common/expression_types/specs/boolean.ts @@ -27,8 +27,8 @@ export const boolean: ExpressionTypeDefinition<'boolean', boolean> = { name, from: { null: () => false, - number: n => Boolean(n), - string: s => Boolean(s), + number: (n) => Boolean(n), + string: (s) => Boolean(s), }, to: { render: (value): ExpressionValueRender<{ text: string }> => { diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts index 92254a3d02438..c113765f8e7e7 100644 --- a/src/plugins/expressions/common/expression_types/specs/datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts @@ -72,7 +72,7 @@ interface RenderedDatatable { export const datatable: ExpressionTypeDefinition = { name, - validate: table => { + validate: (table) => { // TODO: Check columns types. Only string, boolean, number, date, allowed for now. if (!table.columns) { throw new Error('datatable must have a columns array, even if it is empty'); @@ -82,20 +82,20 @@ export const datatable: ExpressionTypeDefinition { + serialize: (table) => { const { columns, rows } = table; return { ...table, - rows: rows.map(row => { - return columns.map(column => row[column.name]); + rows: rows.map((row) => { + return columns.map((column) => row[column.name]); }), }; }, - deserialize: table => { + deserialize: (table) => { const { columns, rows } = table; return { ...table, - rows: rows.map(row => { + rows: rows.map((row) => { return zipObject(map(columns, 'name'), row); }), }; @@ -127,8 +127,8 @@ export const datatable: ExpressionTypeDefinition { const validFields = ['x', 'y', 'color', 'size', 'text']; - const columns = table.columns.filter(column => validFields.includes(column.name)); - const rows = table.rows.map(row => pick(row, validFields)); + const columns = table.columns.filter((column) => validFields.includes(column.name)); + const rows = table.rows.map((row) => pick(row, validFields)); return { type: 'pointseries', columns: columns.reduce>((acc, column) => { diff --git a/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts b/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts index 7742594d751de..7f2f3c37c587c 100644 --- a/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts @@ -53,7 +53,7 @@ export const kibanaDatatable = { return { type: name, rows: context.rows, - columns: context.columns.map(column => { + columns: context.columns.map((column) => { return { id: column.name, name: column.name, diff --git a/src/plugins/expressions/common/expression_types/specs/num.ts b/src/plugins/expressions/common/expression_types/specs/num.ts index 99b3bc3419173..191e617fdc858 100644 --- a/src/plugins/expressions/common/expression_types/specs/num.ts +++ b/src/plugins/expressions/common/expression_types/specs/num.ts @@ -36,11 +36,11 @@ export const num: ExpressionTypeDefinition<'num', ExpressionValueNum> = { type: 'num', value: 0, }), - boolean: b => ({ + boolean: (b) => ({ type: 'num', value: Number(b), }), - string: n => { + string: (n) => { const value = Number(n); if (Number.isNaN(value)) { throw new Error( @@ -57,7 +57,7 @@ export const num: ExpressionTypeDefinition<'num', ExpressionValueNum> = { value, }; }, - '*': value => ({ + '*': (value) => ({ type: 'num', value: Number(value), }), diff --git a/src/plugins/expressions/common/expression_types/specs/number.ts b/src/plugins/expressions/common/expression_types/specs/number.ts index f346ae837adb4..10986659c7848 100644 --- a/src/plugins/expressions/common/expression_types/specs/number.ts +++ b/src/plugins/expressions/common/expression_types/specs/number.ts @@ -28,8 +28,8 @@ export const number: ExpressionTypeDefinition = { name, from: { null: () => 0, - boolean: b => Number(b), - string: n => { + boolean: (b) => Number(b), + string: (n) => { const value = Number(n); if (Number.isNaN(value)) { throw new Error( diff --git a/src/plugins/expressions/common/expression_types/specs/shape.ts b/src/plugins/expressions/common/expression_types/specs/shape.ts index 315838043cb49..80ac67c84c3c0 100644 --- a/src/plugins/expressions/common/expression_types/specs/shape.ts +++ b/src/plugins/expressions/common/expression_types/specs/shape.ts @@ -25,7 +25,7 @@ const name = 'shape'; export const shape: ExpressionTypeDefinition> = { name: 'shape', to: { - render: input => { + render: (input) => { return { type: 'render', as: name, diff --git a/src/plugins/expressions/common/expression_types/specs/string.ts b/src/plugins/expressions/common/expression_types/specs/string.ts index d46f0e5f6b7c2..46f460891c2fb 100644 --- a/src/plugins/expressions/common/expression_types/specs/string.ts +++ b/src/plugins/expressions/common/expression_types/specs/string.ts @@ -27,8 +27,8 @@ export const string: ExpressionTypeDefinition = { name, from: { null: () => '', - boolean: b => String(b), - number: n => String(n), + boolean: (b) => String(b), + number: (n) => String(n), }, to: { render: (text: T): ExpressionValueRender<{ text: T }> => { diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts index e9ff6e0698560..f269c0ea19934 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts @@ -30,7 +30,7 @@ export const sleep: ExpressionFunctionDefinition<'sleep', any, { time: number }, }, help: '', fn: async (input, args, context) => { - await new Promise(r => setTimeout(r, args.time)); + await new Promise((r) => setTimeout(r, args.time)); return input; }, }; diff --git a/src/plugins/expressions/common/util/create_error.ts b/src/plugins/expressions/common/util/create_error.ts index bc27b0eda4959..876e7dfec799c 100644 --- a/src/plugins/expressions/common/util/create_error.ts +++ b/src/plugins/expressions/common/util/create_error.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ExpressionValueError } from '../../public'; +import { ExpressionValueError } from '../../common'; type ErrorLike = Partial>; diff --git a/src/plugins/expressions/common/util/get_by_alias.ts b/src/plugins/expressions/common/util/get_by_alias.ts index 6868abb5da923..35a98871afabc 100644 --- a/src/plugins/expressions/common/util/get_by_alias.ts +++ b/src/plugins/expressions/common/util/get_by_alias.ts @@ -30,7 +30,7 @@ export function getByAlias( return Object.values(node).find(({ name, aliases }) => { if (!name) return false; if (name.toLowerCase() === lowerCaseName) return true; - return (aliases || []).some(alias => { + return (aliases || []).some((alias) => { return alias.toLowerCase() === lowerCaseName; }); }); diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 418ff6fdf8614..9428d7db1d9d0 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -56,7 +56,7 @@ export class ExpressionLoader { // as loading$ could emit straight away in the constructor // and we want to notify subscribers about it, but all subscriptions will happen later this.loading$ = this.loadingSubject.asObservable().pipe( - filter(_ => _ === true), + filter((_) => _ === true), map(() => void 0) ); @@ -67,14 +67,14 @@ export class ExpressionLoader { this.update$ = this.renderHandler.update$; this.events$ = this.renderHandler.events$; - this.update$.subscribe(value => { + this.update$.subscribe((value) => { if (value) { const { newExpression, newParams } = value; this.update(newExpression, newParams); } }); - this.data$.subscribe(data => { + this.data$.subscribe((data) => { this.render(data); }); diff --git a/src/plugins/expressions/public/mocks.tsx b/src/plugins/expressions/public/mocks.tsx index b8f2f693e9c77..3a5ece271c4ee 100644 --- a/src/plugins/expressions/public/mocks.tsx +++ b/src/plugins/expressions/public/mocks.tsx @@ -75,7 +75,7 @@ const createStartContract = (): Start => { getType: jest.fn(), getTypes: jest.fn(), loader: jest.fn(), - ReactExpressionRenderer: jest.fn(props => <>), + ReactExpressionRenderer: jest.fn((props) => <>), render: jest.fn(), run: jest.fn(), }; diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index 720c3b701d504..ec60fbdf44c3a 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -145,7 +145,7 @@ export class ExpressionsPublicPlugin // For every sever-side function, register a client-side // function that matches its definition, but which simply // calls the server-side function endpoint. - Object.keys(serverFunctionList).forEach(functionName => { + Object.keys(serverFunctionList).forEach((functionName) => { if (expressionsSetup.getFunction(functionName)) { return; } diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index caa9bc68dffb8..7c1711f056d69 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -88,6 +88,31 @@ describe('ExpressionRenderer', () => { expect(instance.find(EuiProgress)).toHaveLength(0); }); + it('updates the expression loader when refresh subject emits', () => { + const refreshSubject = new Subject(); + const loaderUpdate = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: loaderUpdate, + destroy: jest.fn(), + }; + }); + + const instance = mount(); + + act(() => { + refreshSubject.next(); + }); + + expect(loaderUpdate).toHaveBeenCalled(); + + instance.unmount(); + }); + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); @@ -111,7 +136,7 @@ describe('ExpressionRenderer', () => { const instance = mount(
{message}
} + renderError={(message) =>
{message}
} /> ); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index 9e237d36ef627..bf716a3b9b1e8 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -19,7 +19,7 @@ import React, { useRef, useEffect, useState, useLayoutEffect } from 'react'; import classNames from 'classnames'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; @@ -38,6 +38,10 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; + /** + * An observable which can be used to re-run the expression without destroying the component + */ + reload$?: Observable; } export type ReactExpressionRendererType = React.ComponentType; @@ -63,6 +67,7 @@ export const ReactExpressionRenderer = ({ renderError, expression, onEvent, + reload$, ...expressionLoaderOptions }: ReactExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); @@ -104,7 +109,7 @@ export const ReactExpressionRenderer = ({ }); if (onEvent) { subs.push( - expressionLoaderRef.current.events$.subscribe(event => { + expressionLoaderRef.current.events$.subscribe((event) => { onEvent(event); }) ); @@ -112,11 +117,11 @@ export const ReactExpressionRenderer = ({ subs.push( expressionLoaderRef.current.loading$.subscribe(() => { hasHandledErrorRef.current = false; - setState(prevState => ({ ...prevState, isLoading: true })); + setState((prevState) => ({ ...prevState, isLoading: true })); }), expressionLoaderRef.current.render$ .pipe(filter(() => !hasHandledErrorRef.current)) - .subscribe(item => { + .subscribe((item) => { setState(() => ({ ...defaultState, isEmpty: false, @@ -125,7 +130,7 @@ export const ReactExpressionRenderer = ({ ); return () => { - subs.forEach(s => s.unsubscribe()); + subs.forEach((s) => s.unsubscribe()); if (expressionLoaderRef.current) { expressionLoaderRef.current.destroy(); expressionLoaderRef.current = null; @@ -135,6 +140,15 @@ export const ReactExpressionRenderer = ({ }; }, [hasCustomRenderErrorHandler, onEvent]); + useEffect(() => { + const subscription = reload$?.subscribe(() => { + if (expressionLoaderRef.current) { + expressionLoaderRef.current.update(expression, expressionLoaderOptions); + } + }); + return () => subscription?.unsubscribe(); + }, [reload$, expression, ...Object.values(expressionLoaderOptions)]); + // Re-fetch data automatically when the inputs change useShallowCompareEffect( () => { diff --git a/src/plugins/expressions/public/render.test.ts b/src/plugins/expressions/public/render.test.ts index b9601f6d1e920..4e79d0d03ace1 100644 --- a/src/plugins/expressions/public/render.test.ts +++ b/src/plugins/expressions/public/render.test.ts @@ -128,8 +128,8 @@ describe('ExpressionRenderHandler', () => { it('sends a next observable once rendering is complete', () => { const expressionRenderHandler = new ExpressionRenderHandler(element); expect.assertions(1); - return new Promise(resolve => { - expressionRenderHandler.render$.subscribe(renderCount => { + return new Promise((resolve) => { + expressionRenderHandler.render$.subscribe((renderCount) => { expect(renderCount).toBe(1); resolve(); }); diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index c8a4022a01131..0d88b3d27f658 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -68,7 +68,7 @@ export class ExpressionRenderHandler { this.onRenderError = onRenderError || defaultRenderErrorHandler; this.renderSubject = new Rx.BehaviorSubject(null as any | null); - this.render$ = this.renderSubject.asObservable().pipe(filter(_ => _ !== null)) as Observable< + this.render$ = this.renderSubject.asObservable().pipe(filter((_) => _ !== null)) as Observable< any >; @@ -86,10 +86,10 @@ export class ExpressionRenderHandler { reload: () => { this.updateSubject.next(null); }, - update: params => { + update: (params) => { this.updateSubject.next(params); }, - event: data => { + event: (data) => { this.eventsSubject.next(data); }, }; diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts index 1487f9f6734e9..4dd9419e59e2d 100644 --- a/src/plugins/expressions/server/legacy.ts +++ b/src/plugins/expressions/server/legacy.ts @@ -114,7 +114,7 @@ export const createLegacyServerEndpoints = ( * Register an endpoint that executes a batch of functions, and streams the * results back using ND-JSON. */ - plugins.bfetch.addBatchProcessingRoute(`/api/interpreter/fns`, request => { + plugins.bfetch.addBatchProcessingRoute(`/api/interpreter/fns`, (request) => { return { onBatchItem: async (fnCall: any) => { const [coreStart] = await core.getStartServices(); diff --git a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap index 924de9bbd0994..2545bbcb5114d 100644 --- a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap @@ -202,17 +202,17 @@ exports[`apmUiEnabled 1`] = ` } textAlign="left" - title="SIEM" + title="Security" titleSize="xs" /> @@ -277,7 +277,7 @@ exports[`apmUiEnabled 1`] = ` /> } textAlign="left" - title="SIEM" + title="Security" titleSize="xs" /> @@ -543,7 +543,7 @@ exports[`isNewKibanaInstance 1`] = ` /> } textAlign="left" - title="SIEM" + title="Security" titleSize="xs" /> @@ -876,7 +876,7 @@ exports[`mlEnabled 1`] = ` /> } textAlign="left" - title="SIEM" + title="Security" titleSize="xs" /> @@ -1142,7 +1142,7 @@ exports[`render 1`] = ` /> { const basePath = getServices().getBasePath(); + const renderCards = () => { const apmData = { title: intl.formatMessage({ @@ -79,11 +80,11 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { }; const siemData = { title: intl.formatMessage({ - id: 'home.addData.siem.nameTitle', - defaultMessage: 'SIEM', + id: 'home.addData.securitySolution.nameTitle', + defaultMessage: 'Security', }), description: intl.formatMessage({ - id: 'home.addData.siem.nameDescription', + id: 'home.addData.securitySolution.nameDescription', defaultMessage: 'Centralize security events for interactive investigation in ready-to-go visualizations.', }), @@ -220,11 +221,11 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { footer={ @@ -296,7 +297,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { { +const isOtherCategory = (directory) => { return ( directory.category !== FeatureCatalogueCategory.DATA && directory.category !== FeatureCatalogueCategory.ADMIN @@ -81,7 +81,7 @@ export class FeatureDirectory extends React.Component { }; } - onSelectedTabChanged = id => { + onSelectedTabChanged = (id) => { this.setState({ selectedTabId: id, }); @@ -102,7 +102,7 @@ export class FeatureDirectory extends React.Component { renderDirectories = () => { return this.props.directories - .filter(directory => { + .filter((directory) => { if (this.state.selectedTabId === ALL_TAB_ID) { return true; } @@ -111,7 +111,7 @@ export class FeatureDirectory extends React.Component { } return this.state.selectedTabId === directory.category; }) - .map(directory => { + .map((directory) => { return ( { + renderDirectories = (category) => { const { addBasePath, directories } = this.props; return directories - .filter(directory => { + .filter((directory) => { return directory.showOnHomePage && directory.category === category; }) - .map(directory => { + .map((directory) => { return ( { decrement: sinon.mock(), }, localStorage: { - getItem: sinon.spy(path => { + getItem: sinon.spy((path) => { expect(path).toEqual('home:welcome:show'); return 'false'; }), @@ -77,11 +77,11 @@ describe('home', () => { const component = shallow(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); return component; } diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index bbbb75cd664f7..648915b6dae0c 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -51,7 +51,7 @@ export function HomeApp({ directories }) { const mlEnabled = environment.ml; const apmUiEnabled = environment.apmUi; - const renderTutorialDirectory = props => { + const renderTutorialDirectory = (props) => { return ( { + const renderTutorial = (props) => { return ( { +const createRecentlyAccessed = (length) => { const recentlyAccessed = []; let i = 0; while (recentlyAccessed.length < length) { diff --git a/src/plugins/home/public/application/components/sample_data_set_cards.js b/src/plugins/home/public/application/components/sample_data_set_cards.js index 404c82676c1c3..255fc57054083 100644 --- a/src/plugins/home/public/application/components/sample_data_set_cards.js +++ b/src/plugins/home/public/application/components/sample_data_set_cards.js @@ -82,12 +82,12 @@ export class SampleDataSetCards extends React.Component { }); }; - install = async id => { - const targetSampleDataSet = this.state.sampleDataSets.find(sampleDataSet => { + install = async (id) => { + const targetSampleDataSet = this.state.sampleDataSets.find((sampleDataSet) => { return sampleDataSet.id === id; }); - this.setState(prevState => ({ + this.setState((prevState) => ({ processingStatus: { ...prevState.processingStatus, [id]: true }, })); @@ -95,7 +95,7 @@ export class SampleDataSetCards extends React.Component { await installSampleDataSet(id, targetSampleDataSet.defaultIndex); } catch (fetchError) { if (this._isMounted) { - this.setState(prevState => ({ + this.setState((prevState) => ({ processingStatus: { ...prevState.processingStatus, [id]: false }, })); } @@ -110,9 +110,9 @@ export class SampleDataSetCards extends React.Component { } if (this._isMounted) { - this.setState(prevState => ({ + this.setState((prevState) => ({ processingStatus: { ...prevState.processingStatus, [id]: false }, - sampleDataSets: prevState.sampleDataSets.map(sampleDataSet => { + sampleDataSets: prevState.sampleDataSets.map((sampleDataSet) => { if (sampleDataSet.id === id) { sampleDataSet.status = INSTALLED_STATUS; } @@ -130,12 +130,12 @@ export class SampleDataSetCards extends React.Component { }); }; - uninstall = async id => { - const targetSampleDataSet = this.state.sampleDataSets.find(sampleDataSet => { + uninstall = async (id) => { + const targetSampleDataSet = this.state.sampleDataSets.find((sampleDataSet) => { return sampleDataSet.id === id; }); - this.setState(prevState => ({ + this.setState((prevState) => ({ processingStatus: { ...prevState.processingStatus, [id]: true }, })); @@ -143,7 +143,7 @@ export class SampleDataSetCards extends React.Component { await uninstallSampleDataSet(id, targetSampleDataSet.defaultIndex); } catch (fetchError) { if (this._isMounted) { - this.setState(prevState => ({ + this.setState((prevState) => ({ processingStatus: { ...prevState.processingStatus, [id]: false }, })); } @@ -158,9 +158,9 @@ export class SampleDataSetCards extends React.Component { } if (this._isMounted) { - this.setState(prevState => ({ + this.setState((prevState) => ({ processingStatus: { ...prevState.processingStatus, [id]: false }, - sampleDataSets: prevState.sampleDataSets.map(sampleDataSet => { + sampleDataSets: prevState.sampleDataSets.map((sampleDataSet) => { if (sampleDataSet.id === id) { sampleDataSet.status = UNINSTALLED_STATUS; } @@ -178,7 +178,7 @@ export class SampleDataSetCards extends React.Component { }); }; - lightOrDarkImage = sampleDataSet => { + lightOrDarkImage = (sampleDataSet) => { return getServices().uiSettings.get('theme:darkMode') && sampleDataSet.darkPreviewImagePath ? sampleDataSet.darkPreviewImagePath : sampleDataSet.previewImagePath; @@ -187,7 +187,7 @@ export class SampleDataSetCards extends React.Component { render() { return ( - {this.state.sampleDataSets.map(sampleDataSet => { + {this.state.sampleDataSets.map((sampleDataSet) => { return ( { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; diff --git a/src/plugins/home/public/application/components/sample_data_view_data_button.test.js b/src/plugins/home/public/application/components/sample_data_view_data_button.test.js index f594ec1264c94..933a58c29ecb4 100644 --- a/src/plugins/home/public/application/components/sample_data_view_data_button.test.js +++ b/src/plugins/home/public/application/components/sample_data_view_data_button.test.js @@ -24,7 +24,7 @@ import { SampleDataViewDataButton } from './sample_data_view_data_button'; jest.mock('../kibana_services', () => ({ getServices: () => ({ - addBasePath: path => `root${path}`, + addBasePath: (path) => `root${path}`, }), })); diff --git a/src/plugins/home/public/application/components/tutorial/instruction.js b/src/plugins/home/public/application/components/tutorial/instruction.js index a44fb26bffbbb..e4b4e34a95662 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction.js +++ b/src/plugins/home/public/application/components/tutorial/instruction.js @@ -52,13 +52,13 @@ export function Instruction({ commands, paramValues, textPost, textPre, replaceT let commandBlock; if (commands) { const cmdText = commands - .map(cmd => { + .map((cmd) => { return replaceTemplateStrings(cmd, paramValues); }) .join('\n'); copyButton = ( - {copy => ( + {(copy) => ( { + this.tabs = props.instructionVariants.map((variant) => { return { id: variant.id, name: getDisplayText(variant.id), @@ -60,10 +60,10 @@ class InstructionSetUi extends React.Component { } handleToggleVisibility = () => { - this.setState(prevState => ({ isParamFormVisible: !prevState.isParamFormVisible })); + this.setState((prevState) => ({ isParamFormVisible: !prevState.isParamFormVisible })); }; - onSelectedTabChanged = id => { + onSelectedTabChanged = (id) => { this.setState({ selectedTabId: id, }); @@ -182,7 +182,7 @@ class InstructionSetUi extends React.Component { } renderInstructions = () => { - const instructionVariant = this.props.instructionVariants.find(variant => { + const instructionVariant = this.props.instructionVariants.find((variant) => { return variant.id === this.state.selectedTabId; }); if (!instructionVariant) { diff --git a/src/plugins/home/public/application/components/tutorial/number_parameter.js b/src/plugins/home/public/application/components/tutorial/number_parameter.js index b3962385b332a..68475f9082bbf 100644 --- a/src/plugins/home/public/application/components/tutorial/number_parameter.js +++ b/src/plugins/home/public/application/components/tutorial/number_parameter.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; export function NumberParameter({ id, label, value, setParameter }) { - const handleChange = evt => { + const handleChange = (evt) => { setParameter(id, parseFloat(evt.target.value)); }; diff --git a/src/plugins/home/public/application/components/tutorial/parameter_form.js b/src/plugins/home/public/application/components/tutorial/parameter_form.js index 35db68abee427..99159ae12b16f 100644 --- a/src/plugins/home/public/application/components/tutorial/parameter_form.js +++ b/src/plugins/home/public/application/components/tutorial/parameter_form.js @@ -25,7 +25,7 @@ import { EuiPanel } from '@elastic/eui'; export class ParameterForm extends React.Component { renderInputs = () => { - return this.props.params.map(param => { + return this.props.params.map((param) => { switch (param.type) { case 'number': return ( diff --git a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js index bf69c419f6464..790c6d9c2574e 100644 --- a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js +++ b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js @@ -90,11 +90,11 @@ class SavedObjectsInstallerUi extends React.Component { return; } - const errors = resp.savedObjects.filter(savedObject => { + const errors = resp.savedObjects.filter((savedObject) => { return Boolean(savedObject.error); }); - const overwriteErrors = errors.filter(savedObject => { + const overwriteErrors = errors.filter((savedObject) => { return savedObject.error.statusCode === 409; }); if (overwriteErrors.length > 0) { diff --git a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js index b3f42436c48c2..6cc02184fbc16 100644 --- a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js +++ b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js @@ -52,7 +52,7 @@ describe('bulkCreate', () => { findTestSubject(component, 'loadSavedObjects').simulate('click'); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -73,7 +73,7 @@ describe('bulkCreate', () => { findTestSubject(component, 'loadSavedObjects').simulate('click'); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); diff --git a/src/plugins/home/public/application/components/tutorial/string_parameter.js b/src/plugins/home/public/application/components/tutorial/string_parameter.js index 0973737136cff..a99e39c298ecf 100644 --- a/src/plugins/home/public/application/components/tutorial/string_parameter.js +++ b/src/plugins/home/public/application/components/tutorial/string_parameter.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; export function StringParameter({ id, label, value, setParameter }) { - const handleChange = evt => { + const handleChange = (evt) => { setParameter(id, evt.target.value); }; diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.js b/src/plugins/home/public/application/components/tutorial/tutorial.js index 762d7fde252a1..576f732278b8e 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.js @@ -147,7 +147,7 @@ class TutorialUi extends React.Component { const paramValues = {}; if (instructions.params) { - instructions.params.forEach(param => { + instructions.params.forEach((param) => { paramValues[param.id] = param.defaultValue; }); } @@ -162,7 +162,7 @@ class TutorialUi extends React.Component { }); }; - setVisibleInstructions = instructionsType => { + setVisibleInstructions = (instructionsType) => { this.setState( { visibleInstructions: instructionsType, @@ -172,21 +172,21 @@ class TutorialUi extends React.Component { }; setParameter = (paramId, newValue) => { - this.setState(previousState => { + this.setState((previousState) => { const paramValues = _.cloneDeep(previousState.paramValues); paramValues[paramId] = newValue; return { paramValues: paramValues }; }); }; - checkInstructionSetStatus = async instructionSetIndex => { + checkInstructionSetStatus = async (instructionSetIndex) => { const instructionSet = this.getInstructionSets()[instructionSetIndex]; const esHitsCheckConfig = _.get(instructionSet, `statusCheck.esHitsCheck`); if (esHitsCheckConfig) { const statusCheckState = await this.fetchEsHitsStatus(esHitsCheckConfig); - this.setState(prevState => ({ + this.setState((prevState) => ({ statusCheckStates: { ...prevState.statusCheckStates, [instructionSetIndex]: statusCheckState, @@ -200,7 +200,7 @@ class TutorialUi extends React.Component { * @param esHitsCheckConfig * @return {Promise} */ - fetchEsHitsStatus = async esHitsCheckConfig => { + fetchEsHitsStatus = async (esHitsCheckConfig) => { const searchHeader = JSON.stringify({ index: esHitsCheckConfig.index }); const searchBody = JSON.stringify({ query: esHitsCheckConfig.query, size: 1 }); const response = await fetch(this.props.addBasePath('/elasticsearch/_msearch'), { @@ -260,9 +260,9 @@ class TutorialUi extends React.Component { } }; - onStatusCheck = instructionSetIndex => { + onStatusCheck = (instructionSetIndex) => { this.setState( - prevState => ({ + (prevState) => ({ statusCheckStates: { ...prevState.statusCheckStates, [instructionSetIndex]: StatusCheckStates.FETCHING, @@ -272,7 +272,7 @@ class TutorialUi extends React.Component { ); }; - renderInstructionSets = instructions => { + renderInstructionSets = (instructions) => { let offset = 1; return instructions.instructionSets.map((instructionSet, index) => { const currentOffset = offset; @@ -320,7 +320,7 @@ class TutorialUi extends React.Component { label = this.state.tutorial.artifacts.application.label; url = this.props.addBasePath(this.state.tutorial.artifacts.application.path); } else if (_.has(this.state, 'tutorial.artifacts.dashboards')) { - const overviewDashboard = this.state.tutorial.artifacts.dashboards.find(dashboard => { + const overviewDashboard = this.state.tutorial.artifacts.dashboards.find((dashboard) => { return dashboard.isOverview; }); if (overviewDashboard) { diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.test.js b/src/plugins/home/public/application/components/tutorial/tutorial.test.js index 41d83d7562f6e..23b0dc50018c1 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.test.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.test.js @@ -67,10 +67,10 @@ const loadTutorialPromise = Promise.resolve(tutorial); const getTutorial = () => { return loadTutorialPromise; }; -const addBasePath = path => { +const addBasePath = (path) => { return `BASE_PATH/${path}`; }; -const replaceTemplateStrings = text => { +const replaceTemplateStrings = (text) => { return text; }; @@ -130,11 +130,7 @@ describe('isCloudEnabled is false', () => { ); await loadTutorialPromise; component.update(); - component - .find('button#onPremElasticCloud') - .closest('div') - .find('input') - .simulate('change'); + component.find('button#onPremElasticCloud').closest('div').find('input').simulate('change'); component.update(); expect(component.state('visibleInstructions')).toBe('onPremElasticCloud'); }); diff --git a/src/plugins/home/public/application/components/tutorial_directory.js b/src/plugins/home/public/application/components/tutorial_directory.js index 18007931a194a..774b23af11ac8 100644 --- a/src/plugins/home/public/application/components/tutorial_directory.js +++ b/src/plugins/home/public/application/components/tutorial_directory.js @@ -75,10 +75,10 @@ class TutorialDirectoryUi extends React.Component { }), }, { - id: 'siem', + id: 'security', name: this.props.intl.formatMessage({ - id: 'home.tutorial.tabs.siemTitle', - defaultMessage: 'SIEM', + id: 'home.tutorial.tabs.securitySolutionTitle', + defaultMessage: 'Security', }), }, { @@ -93,7 +93,7 @@ class TutorialDirectoryUi extends React.Component { let openTab = ALL_TAB_ID; if ( props.openTab && - this.tabs.some(tab => { + this.tabs.some((tab) => { return tab.id === props.openTab; }) ) { @@ -126,7 +126,7 @@ class TutorialDirectoryUi extends React.Component { return; } - let tutorialCards = tutorialConfigs.map(tutorialConfig => { + let tutorialCards = tutorialConfigs.map((tutorialConfig) => { // add base path to SVG based icons let icon = tutorialConfig.euiIconType; if (icon && icon.includes('/')) { @@ -161,7 +161,7 @@ class TutorialDirectoryUi extends React.Component { }); if (this.props.isCloudEnabled) { - tutorialCards = tutorialCards.filter(tutorial => { + tutorialCards = tutorialCards.filter((tutorial) => { return _.has(tutorial, 'elasticCloud'); }); } @@ -176,7 +176,7 @@ class TutorialDirectoryUi extends React.Component { }); } - onSelectedTabChanged = id => { + onSelectedTabChanged = (id) => { this.setState({ selectedTabId: id, }); @@ -202,13 +202,13 @@ class TutorialDirectoryUi extends React.Component { return ( {this.state.tutorialCards - .filter(tutorial => { + .filter((tutorial) => { return ( this.state.selectedTabId === ALL_TAB_ID || this.state.selectedTabId === tutorial.category ); }) - .map(tutorial => { + .map((tutorial) => { return ( { id="home.dataManagementDisableCollection" defaultMessage=" To stop collection, " /> - + { id="home.dataManagementEnableCollection" defaultMessage=" To start collection, " /> - + { + const tutorial = tutorials.find((tutorial) => { return tutorial.id === id; }); diff --git a/src/plugins/home/public/assets/azure_logs/screenshot.png b/src/plugins/home/public/assets/azure_logs/screenshot.png new file mode 100644 index 0000000000000..32c5a7202d883 Binary files /dev/null and b/src/plugins/home/public/assets/azure_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/azure_metrics/screenshot.png b/src/plugins/home/public/assets/azure_metrics/screenshot.png new file mode 100644 index 0000000000000..22136049b494a Binary files /dev/null and b/src/plugins/home/public/assets/azure_metrics/screenshot.png differ diff --git a/src/plugins/home/public/assets/iis_metrics/screenshot.png b/src/plugins/home/public/assets/iis_metrics/screenshot.png new file mode 100644 index 0000000000000..35e04c49b43f0 Binary files /dev/null and b/src/plugins/home/public/assets/iis_metrics/screenshot.png differ diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index f5ad5e321d274..d05fce652bd40 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -128,7 +128,7 @@ export class HomePublicPlugin window.location.hash === '' ) { // ...wait for the app to mount initially and then... - currentAppId$.pipe(first()).subscribe(appId => { + currentAppId$.pipe(first()).subscribe((appId) => { if (appId === 'home') { // ...navigate to default app set by `kibana.defaultAppId`. // This doesn't do anything as along as the default settings are kept. diff --git a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts index 187a75b376d64..c70fc0464b131 100644 --- a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts +++ b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts @@ -73,7 +73,7 @@ export class FeatureCatalogueRegistry { } const capabilities = this.capabilities; return [...this.features.values()] - .filter(entry => capabilities.catalogue[entry.id] !== false) + .filter((entry) => capabilities.catalogue[entry.id] !== false) .sort(compareByKey('title')); } } diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts index b0cc2e2db3cc9..c4fb636cc9f8f 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts @@ -31,7 +31,7 @@ const ecommerceDescription = i18n.translate('home.sampleData.ecommerceSpecDescri }); const initialAppLinks = [] as AppLinkSchema[]; -export const ecommerceSpecProvider = function(): SampleDatasetSchema { +export const ecommerceSpecProvider = function (): SampleDatasetSchema { return { id: 'ecommerce', name: ecommerceName, diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts index fc3cb6094b5ea..0d5b4a8816a03 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts @@ -31,7 +31,7 @@ const flightsDescription = i18n.translate('home.sampleData.flightsSpecDescriptio }); const initialAppLinks = [] as AppLinkSchema[]; -export const flightsSpecProvider = function(): SampleDatasetSchema { +export const flightsSpecProvider = function (): SampleDatasetSchema { return { id: 'flights', name: flightsName, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts index d8f205dff24e8..900b9ab82f47f 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts @@ -31,7 +31,7 @@ const logsDescription = i18n.translate('home.sampleData.logsSpecDescription', { }); const initialAppLinks = [] as AppLinkSchema[]; -export const logsSpecProvider = function(): SampleDatasetSchema { +export const logsSpecProvider = function (): SampleDatasetSchema { return { id: 'logs', name: logsName, diff --git a/src/plugins/home/server/services/sample_data/lib/create_index_name.ts b/src/plugins/home/server/services/sample_data/lib/create_index_name.ts index 9aecef405d7ce..e8901a3f1bd3e 100644 --- a/src/plugins/home/server/services/sample_data/lib/create_index_name.ts +++ b/src/plugins/home/server/services/sample_data/lib/create_index_name.ts @@ -17,7 +17,7 @@ * under the License. */ -export const createIndexName = function(sampleDataSetId: string, dataIndexId: string): string { +export const createIndexName = function (sampleDataSetId: string, dataIndexId: string): string { // Sample data schema was updated to support multiple indices in 6.5. // This if statement ensures that sample data sets that used a single index prior to the schema change // have the same index name to avoid orphaned indices when uninstalling. diff --git a/src/plugins/home/server/services/sample_data/lib/load_data.ts b/src/plugins/home/server/services/sample_data/lib/load_data.ts index 481ed8da93dba..4dc55b9685302 100644 --- a/src/plugins/home/server/services/sample_data/lib/load_data.ts +++ b/src/plugins/home/server/services/sample_data/lib/load_data.ts @@ -52,7 +52,7 @@ export function loadData(path: any, bulkInsert: (docs: any[]) => Promise) reject(err); }; - lineStream.on('line', async line => { + lineStream.on('line', async (line) => { if (line.length === 0 || line.charAt(0) === '#') { return; } diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts index e905cb0637b7b..b8077a255ecf6 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts @@ -31,9 +31,7 @@ const dataIndexSchema = Joi.object({ fields: Joi.object().required(), // times fields that will be updated relative to now when data is installed - timeFields: Joi.array() - .items(Joi.string()) - .required(), + timeFields: Joi.array().items(Joi.string()).required(), // Reference to now in your test data set. // When data is installed, timestamps are converted to the present time. @@ -41,9 +39,7 @@ const dataIndexSchema = Joi.object({ // For example: // sample data set: timestamp: 2018-01-01T00:00:00Z, currentTimeMarker: 2018-01-01T12:00:00Z // installed data set: timestamp: 2018-04-18T20:33:14Z, currentTimeMarker: 2018-04-19T08:33:14Z - currentTimeMarker: Joi.string() - .isoDate() - .required(), + currentTimeMarker: Joi.string().isoDate().required(), // Set to true to move timestamp to current week, preserving day of week and time of day // Relative distance from timestamp to currentTimeMarker will not remain the same @@ -67,19 +63,13 @@ export const sampleDataSchema = { // saved object id of main dashboard for sample data set overviewDashboard: Joi.string().required(), - appLinks: Joi.array() - .items(appLinkSchema) - .default([]), + appLinks: Joi.array().items(appLinkSchema).default([]), // saved object id of default index-pattern for sample data set defaultIndex: Joi.string().required(), // Kibana saved objects (index patter, visualizations, dashboard, ...) // Should provide a nice demo of Kibana's functionality with the sample data set - savedObjects: Joi.array() - .items(Joi.object()) - .required(), - dataIndices: Joi.array() - .items(dataIndexSchema) - .required(), + savedObjects: Joi.array().items(Joi.object()).required(), + dataIndices: Joi.array().items(dataIndexSchema).required(), }; diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts index e2c5ce6883230..2d1a53fbb09dc 100644 --- a/src/plugins/home/server/services/sample_data/routes/install.ts +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -61,7 +61,7 @@ const insertDataIntoIndex = ( bulk.push(insertCmd); bulk.push(updateTimestamps(doc)); }); - const resp = await context.core.elasticsearch.adminClient.callAsCurrentUser('bulk', { + const resp = await context.core.elasticsearch.legacy.client.callAsCurrentUser('bulk', { body: bulk, }); if (resp.errors) { @@ -110,7 +110,7 @@ export function createInstallRoute( // clean up any old installation of dataset try { - await context.core.elasticsearch.dataClient.callAsCurrentUser('indices.delete', { + await context.core.elasticsearch.legacy.client.callAsCurrentUser('indices.delete', { index, }); } catch (err) { @@ -125,7 +125,7 @@ export function createInstallRoute( mappings: { properties: dataIndexConfig.fields }, }, }; - await context.core.elasticsearch.dataClient.callAsCurrentUser( + await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.create', createIndexParams ); @@ -162,7 +162,7 @@ export function createInstallRoute( logger.warn(errMsg); return res.internalError({ body: errMsg }); } - const errors = createResults.saved_objects.filter(savedObjectCreateResult => { + const errors = createResults.saved_objects.filter((savedObjectCreateResult) => { return Boolean(savedObjectCreateResult.error); }); if (errors.length > 0) { diff --git a/src/plugins/home/server/services/sample_data/routes/list.ts b/src/plugins/home/server/services/sample_data/routes/list.ts index 37ebab1c168d2..770b3116b74f1 100644 --- a/src/plugins/home/server/services/sample_data/routes/list.ts +++ b/src/plugins/home/server/services/sample_data/routes/list.ts @@ -27,7 +27,7 @@ const UNKNOWN = 'unknown'; export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSchema[]) => { router.get({ path: '/api/sample_data', validate: false }, async (context, req, res) => { - const registeredSampleDatasets = sampleDatasets.map(sampleDataset => { + const registeredSampleDatasets = sampleDatasets.map((sampleDataset) => { return { id: sampleDataset.id, name: sampleDataset.name, @@ -42,12 +42,12 @@ export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSc statusMsg: sampleDataset.statusMsg, }; }); - const isInstalledPromises = registeredSampleDatasets.map(async sampleDataset => { + const isInstalledPromises = registeredSampleDatasets.map(async (sampleDataset) => { for (let i = 0; i < sampleDataset.dataIndices.length; i++) { const dataIndexConfig = sampleDataset.dataIndices[i]; const index = createIndexName(sampleDataset.id, dataIndexConfig.id); try { - const indexExists = await context.core.elasticsearch.dataClient.callAsCurrentUser( + const indexExists = await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.exists', { index } ); @@ -56,9 +56,12 @@ export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSc return; } - const { count } = await context.core.elasticsearch.dataClient.callAsCurrentUser('count', { - index, - }); + const { count } = await context.core.elasticsearch.legacy.client.callAsCurrentUser( + 'count', + { + index, + } + ); if (count === 0) { sampleDataset.status = NOT_INSTALLED; return; diff --git a/src/plugins/home/server/services/sample_data/routes/uninstall.ts b/src/plugins/home/server/services/sample_data/routes/uninstall.ts index 64fb2b8b3a547..9bb260460b38a 100644 --- a/src/plugins/home/server/services/sample_data/routes/uninstall.ts +++ b/src/plugins/home/server/services/sample_data/routes/uninstall.ts @@ -39,7 +39,9 @@ export function createUninstallRoute( { core: { elasticsearch: { - dataClient: { callAsCurrentUser }, + legacy: { + client: { callAsCurrentUser }, + }, }, savedObjects: { client: savedObjectsClient }, }, diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts index b6d04c5c0b6a3..356c886436413 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -97,7 +97,7 @@ export class SampleDataRegistry { getSampleDatasets: () => this.sampleDatasets, addSavedObjectsToSampleDataset: (id: string, savedObjects: SavedObject[]) => { - const sampleDataset = this.sampleDatasets.find(dataset => { + const sampleDataset = this.sampleDatasets.find((dataset) => { return dataset.id === id; }); @@ -109,7 +109,7 @@ export class SampleDataRegistry { }, addAppLinksToSampleDataset: (id: string, appLinks: AppLinkSchema[]) => { - const sampleDataset = this.sampleDatasets.find(dataset => { + const sampleDataset = this.sampleDatasets.find((dataset) => { return dataset.id === id; }); @@ -130,14 +130,14 @@ export class SampleDataRegistry { embeddableType, embeddableConfig, }: SampleDatasetDashboardPanel) => { - const sampleDataset = this.sampleDatasets.find(dataset => { + const sampleDataset = this.sampleDatasets.find((dataset) => { return dataset.id === sampleDataId; }); if (!sampleDataset) { throw new Error(`Unable to find sample dataset with id: ${sampleDataId}`); } - const dashboard = sampleDataset.savedObjects.find(savedObject => { + const dashboard = sampleDataset.savedObjects.find((savedObject) => { return savedObject.id === dashboardId && savedObject.type === 'dashboard'; }) as SavedObject<{ panelsJSON: string }>; if (!dashboard) { diff --git a/src/plugins/home/server/services/sample_data/usage/usage.ts b/src/plugins/home/server/services/sample_data/usage/usage.ts index 59599a1bee68f..ba67906febf1a 100644 --- a/src/plugins/home/server/services/sample_data/usage/usage.ts +++ b/src/plugins/home/server/services/sample_data/usage/usage.ts @@ -37,7 +37,7 @@ export function usage( logger.warn(`saved objects repository incrementCounter encountered an error: ${err}`); }; - const internalRepositoryPromise = savedObjects.then(so => so.createInternalRepository()); + const internalRepositoryPromise = savedObjects.then((so) => so.createInternalRepository()); return { addInstall: async (dataSet: string) => { diff --git a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts index eb001ac96cceb..32e5483b8b070 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts @@ -26,7 +26,7 @@ const PARAM_TYPES = { const TUTORIAL_CATEGORY = { LOGGING: 'logging', - SIEM: 'siem', + SECURITY_SOLUTION: 'security solution', METRICS: 'metrics', OTHER: 'other', }; @@ -47,9 +47,7 @@ const artifactsSchema = Joi.object({ documentationUrl: Joi.string().required(), }), // Kibana dashboards created by this product. - dashboards: Joi.array() - .items(dashboardSchema) - .required(), + dashboards: Joi.array().items(dashboardSchema).required(), application: Joi.object({ path: Joi.string().required(), label: Joi.string().required(), @@ -63,9 +61,7 @@ const statusCheckSchema = Joi.object({ success: Joi.string(), error: Joi.string(), esHitsCheck: Joi.object({ - index: Joi.alternatives() - .try(Joi.string(), Joi.array().items(Joi.string())) - .required(), + index: Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())).required(), query: Joi.object().required(), }).required(), }); @@ -79,9 +75,7 @@ const instructionSchema = Joi.object({ const instructionVariantSchema = Joi.object({ id: Joi.string().required(), - instructions: Joi.array() - .items(instructionSchema) - .required(), + instructions: Joi.array().items(instructionSchema).required(), }); const instructionSetSchema = Joi.object({ @@ -92,9 +86,7 @@ const instructionSetSchema = Joi.object({ iconType: Joi.string(), }), // Variants (OSes, languages, etc.) for which tutorial instructions are specified. - instructionVariants: Joi.array() - .items(instructionVariantSchema) - .required(), + instructionVariants: Joi.array().items(instructionVariantSchema).required(), statusCheck: statusCheckSchema, }); @@ -104,15 +96,11 @@ const paramSchema = Joi.object({ .regex(/^[a-zA-Z_]+$/) .required(), label: Joi.string().required(), - type: Joi.string() - .valid(Object.values(PARAM_TYPES)) - .required(), + type: Joi.string().valid(Object.values(PARAM_TYPES)).required(), }); const instructionsSchema = Joi.object({ - instructionSets: Joi.array() - .items(instructionSetSchema) - .required(), + instructionSets: Joi.array().items(instructionSetSchema).required(), params: Joi.array().items(paramSchema), }); @@ -120,9 +108,7 @@ export const tutorialSchema = { id: Joi.string() .regex(/^[a-zA-Z0-9-]+$/) .required(), - category: Joi.string() - .valid(Object.values(TUTORIAL_CATEGORY)) - .required(), + category: Joi.string().valid(Object.values(TUTORIAL_CATEGORY)).required(), name: Joi.string().required(), isBeta: Joi.boolean().default(false), shortDescription: Joi.string().required(), diff --git a/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts b/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts index 80849513a3fad..3325240147640 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts @@ -22,7 +22,7 @@ import { KibanaRequest } from 'src/core/server'; /** @public */ export enum TutorialsCategory { LOGGING = 'logging', - SIEM = 'siem', + SECURITY_SOLUTION = 'security', METRICS = 'metrics', OTHER = 'other', } diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index ed28e42dbcf99..2122251743382 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -45,7 +45,7 @@ export class TutorialsRegistry { ); return res.ok({ - body: this.tutorialProviders.map(tutorialProvider => { + body: this.tutorialProviders.map((tutorialProvider) => { return tutorialProvider(scopedContext); // All the tutorialProviders need to be refactored so that they don't need the server. }), }); @@ -65,7 +65,7 @@ export class TutorialsRegistry { unregisterTutorial: (specProvider: TutorialProvider) => { this.tutorialProviders = this.tutorialProviders.filter( - provider => provider !== specProvider + (provider) => provider !== specProvider ); }, diff --git a/src/plugins/home/server/tutorials/auditbeat/index.ts b/src/plugins/home/server/tutorials/auditbeat/index.ts index dadbf913d5ed5..214fda5a7cc53 100644 --- a/src/plugins/home/server/tutorials/auditbeat/index.ts +++ b/src/plugins/home/server/tutorials/auditbeat/index.ts @@ -36,7 +36,7 @@ export function auditbeatSpecProvider(context: TutorialContext): TutorialSchema name: i18n.translate('home.tutorials.auditbeat.nameTitle', { defaultMessage: 'Auditbeat', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.auditbeat.shortDescription', { defaultMessage: 'Collect audit data from your hosts.', }), @@ -53,9 +53,9 @@ processes, users, logins, sockets information, file accesses, and more. \ artifacts: { dashboards: [], application: { - path: '/app/siem', + path: '/app/security', label: i18n.translate('home.tutorials.auditbeat.artifacts.dashboards.linkLabel', { - defaultMessage: 'SIEM App', + defaultMessage: 'Security App', }), }, exportedFields: { diff --git a/src/plugins/home/server/tutorials/azure_logs/index.ts b/src/plugins/home/server/tutorials/azure_logs/index.ts new file mode 100644 index 0000000000000..06aef411775f1 --- /dev/null +++ b/src/plugins/home/server/tutorials/azure_logs/index.ts @@ -0,0 +1,74 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function azureLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'azure'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'azureLogs', + name: i18n.translate('home.tutorials.azureLogs.nameTitle', { + defaultMessage: 'Azure logs', + }), + isBeta: true, + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.azureLogs.shortDescription', { + defaultMessage: 'Collects Azure activity and audit related logs.', + }), + longDescription: i18n.translate('home.tutorials.azureLogs.longDescription', { + defaultMessage: + 'The `azure` Filebeat module collects Azure activity and audit related logs. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-azure.html', + }, + }), + euiIconType: 'logoAzure', + artifacts: { + dashboards: [ + { + id: '41e84340-ec20-11e9-90ec-112a988266d5', + linkLabel: i18n.translate('home.tutorials.azureLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Azure logs dashboard', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-azure.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/azure_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/azure_metrics/index.ts b/src/plugins/home/server/tutorials/azure_metrics/index.ts index ddf70fcc837b2..c11b3ac0139ba 100644 --- a/src/plugins/home/server/tutorials/azure_metrics/index.ts +++ b/src/plugins/home/server/tutorials/azure_metrics/index.ts @@ -36,7 +36,7 @@ export function azureMetricsSpecProvider(context: TutorialContext): TutorialSche name: i18n.translate('home.tutorials.azureMetrics.nameTitle', { defaultMessage: 'Azure metrics', }), - isBeta: true, + isBeta: false, category: TutorialsCategory.METRICS, shortDescription: i18n.translate('home.tutorials.azureMetrics.shortDescription', { defaultMessage: 'Fetch Azure Monitor metrics.', @@ -51,18 +51,21 @@ export function azureMetricsSpecProvider(context: TutorialContext): TutorialSche }), euiIconType: 'logoAzure', artifacts: { - application: { - label: i18n.translate('home.tutorials.azureMetrics.artifacts.application.label', { - defaultMessage: 'Discover', - }), - path: '/app/discover#/', - }, - dashboards: [], + dashboards: [ + { + id: 'eb3f05f0-ea9a-11e9-90ec-112a988266d5', + linkLabel: i18n.translate('home.tutorials.azureMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Azure metrics dashboard', + }), + isOverview: true, + }, + ], exportedFields: { documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-azure.html', }, }, completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/azure_metrics/screenshot.png', onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), diff --git a/src/plugins/home/server/tutorials/cisco_logs/index.ts b/src/plugins/home/server/tutorials/cisco_logs/index.ts index 4514b61570b07..2322f503b80ce 100644 --- a/src/plugins/home/server/tutorials/cisco_logs/index.ts +++ b/src/plugins/home/server/tutorials/cisco_logs/index.ts @@ -37,7 +37,7 @@ export function ciscoLogsSpecProvider(context: TutorialContext): TutorialSchema name: i18n.translate('home.tutorials.ciscoLogs.nameTitle', { defaultMessage: 'Cisco', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.ciscoLogs.shortDescription', { defaultMessage: 'Collect and parse logs received from Cisco ASA firewalls.', }), @@ -54,9 +54,9 @@ supports the "asa" fileset for Cisco ASA firewall logs received over syslog or r artifacts: { dashboards: [], application: { - path: '/app/siem', + path: '/app/security', label: i18n.translate('home.tutorials.ciscoLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'SIEM App', + defaultMessage: 'Security App', }), }, exportedFields: { diff --git a/src/plugins/home/server/tutorials/coredns_logs/index.ts b/src/plugins/home/server/tutorials/coredns_logs/index.ts index 1c62366251661..4304fb7acb907 100644 --- a/src/plugins/home/server/tutorials/coredns_logs/index.ts +++ b/src/plugins/home/server/tutorials/coredns_logs/index.ts @@ -37,7 +37,7 @@ export function corednsLogsSpecProvider(context: TutorialContext): TutorialSchem name: i18n.translate('home.tutorials.corednsLogs.nameTitle', { defaultMessage: 'CoreDNS logs', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.corednsLogs.shortDescription', { defaultMessage: 'Collect the logs created by Coredns.', }), diff --git a/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts b/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts index 3d88cce36d752..a9b9c33d61bdf 100644 --- a/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts +++ b/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts @@ -37,7 +37,7 @@ export function envoyproxyLogsSpecProvider(context: TutorialContext): TutorialSc name: i18n.translate('home.tutorials.envoyproxyLogs.nameTitle', { defaultMessage: 'Envoyproxy', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.envoyproxyLogs.shortDescription', { defaultMessage: 'Collect and parse logs received from the Envoy proxy.', }), @@ -54,9 +54,9 @@ It supports both standalone deployment and Envoy proxy deployment in Kubernetes. artifacts: { dashboards: [], application: { - path: '/app/siem', + path: '/app/security', label: i18n.translate('home.tutorials.envoyproxyLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'SIEM App', + defaultMessage: 'Security App', }), }, exportedFields: { diff --git a/src/plugins/home/server/tutorials/iis_metrics/index.ts b/src/plugins/home/server/tutorials/iis_metrics/index.ts new file mode 100644 index 0000000000000..46621677a67ce --- /dev/null +++ b/src/plugins/home/server/tutorials/iis_metrics/index.ts @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function iisMetricsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'iis'; + return { + id: 'iisMetrics', + name: i18n.translate('home.tutorials.iisMetrics.nameTitle', { + defaultMessage: 'IIS Metrics', + }), + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.iisMetrics.shortDescription', { + defaultMessage: 'Collect IIS server related metrics.', + }), + longDescription: i18n.translate('home.tutorials.iisMetrics.longDescription', { + defaultMessage: + 'The `iis` Metricbeat module collects metrics from IIS server and the application pools and websites running. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-iis.html', + }, + }), + isBeta: true, + euiIconType: '/plugins/home/assets/logos/iis.svg', + artifacts: { + dashboards: [ + { + id: 'ebc23240-8572-11ea-91bc-ab084c7ec0e7', + linkLabel: i18n.translate('home.tutorials.iisMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'IIS metrics dashboard', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-iis.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/iis_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName), + }; +} diff --git a/src/plugins/home/server/tutorials/iptables_logs/index.ts b/src/plugins/home/server/tutorials/iptables_logs/index.ts index e72e0ef300e04..fd84894dae850 100644 --- a/src/plugins/home/server/tutorials/iptables_logs/index.ts +++ b/src/plugins/home/server/tutorials/iptables_logs/index.ts @@ -37,7 +37,7 @@ export function iptablesLogsSpecProvider(context: TutorialContext): TutorialSche name: i18n.translate('home.tutorials.iptablesLogs.nameTitle', { defaultMessage: 'Iptables / Ubiquiti', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.iptablesLogs.shortDescription', { defaultMessage: 'Collect and parse iptables and ip6tables logs or from Ubiqiti firewalls.', }), @@ -56,9 +56,9 @@ number and the action performed on the traffic (allow/deny).. \ artifacts: { dashboards: [], application: { - path: '/app/siem', + path: '/app/security', label: i18n.translate('home.tutorials.iptablesLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'SIEM App', + defaultMessage: 'Security App', }), }, exportedFields: { diff --git a/src/plugins/home/server/tutorials/netflow/index.ts b/src/plugins/home/server/tutorials/netflow/index.ts index 7c6fcadcff625..ec0aa8953b146 100644 --- a/src/plugins/home/server/tutorials/netflow/index.ts +++ b/src/plugins/home/server/tutorials/netflow/index.ts @@ -28,7 +28,7 @@ export function netflowSpecProvider() { return { id: 'netflow', name: 'Netflow', - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.netflow.tutorialShortDescription', { defaultMessage: 'Collect Netflow records sent by a Netflow exporter.', }), diff --git a/src/plugins/home/server/tutorials/osquery_logs/index.ts b/src/plugins/home/server/tutorials/osquery_logs/index.ts index 34a1b9e7f619d..8781d6201a771 100644 --- a/src/plugins/home/server/tutorials/osquery_logs/index.ts +++ b/src/plugins/home/server/tutorials/osquery_logs/index.ts @@ -37,7 +37,7 @@ export function osqueryLogsSpecProvider(context: TutorialContext): TutorialSchem name: i18n.translate('home.tutorials.osqueryLogs.nameTitle', { defaultMessage: 'Osquery logs', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.osqueryLogs.shortDescription', { defaultMessage: 'Collect the result logs created by osqueryd.', }), diff --git a/src/plugins/home/server/tutorials/register.ts b/src/plugins/home/server/tutorials/register.ts index 1eec15069f87e..d13cce1c22784 100644 --- a/src/plugins/home/server/tutorials/register.ts +++ b/src/plugins/home/server/tutorials/register.ts @@ -89,6 +89,8 @@ import { statsdMetricsSpecProvider } from './statsd_metrics'; import { redisenterpriseMetricsSpecProvider } from './redisenterprise_metrics'; import { openmetricsMetricsSpecProvider } from './openmetrics_metrics'; import { oracleMetricsSpecProvider } from './oracle_metrics'; +import { iisMetricsSpecProvider } from './iis_metrics'; +import { azureLogsSpecProvider } from './azure_logs'; export const builtInTutorials = [ systemLogsSpecProvider, @@ -164,4 +166,6 @@ export const builtInTutorials = [ redisenterpriseMetricsSpecProvider, openmetricsMetricsSpecProvider, oracleMetricsSpecProvider, + iisMetricsSpecProvider, + azureLogsSpecProvider, ]; diff --git a/src/plugins/home/server/tutorials/suricata_logs/index.ts b/src/plugins/home/server/tutorials/suricata_logs/index.ts index c02cb05889ebb..6bcfc1d43a250 100644 --- a/src/plugins/home/server/tutorials/suricata_logs/index.ts +++ b/src/plugins/home/server/tutorials/suricata_logs/index.ts @@ -37,7 +37,7 @@ export function suricataLogsSpecProvider(context: TutorialContext): TutorialSche name: i18n.translate('home.tutorials.suricataLogs.nameTitle', { defaultMessage: 'Suricata logs', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.suricataLogs.shortDescription', { defaultMessage: 'Collect the result logs created by Suricata IDS/IPS/NSM.', }), diff --git a/src/plugins/home/server/tutorials/windows_event_logs/index.ts b/src/plugins/home/server/tutorials/windows_event_logs/index.ts index 5349bedb21279..c2ea9ff3015e4 100644 --- a/src/plugins/home/server/tutorials/windows_event_logs/index.ts +++ b/src/plugins/home/server/tutorials/windows_event_logs/index.ts @@ -36,7 +36,7 @@ export function windowsEventLogsSpecProvider(context: TutorialContext): Tutorial defaultMessage: 'Windows Event Log', }), isBeta: false, - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.windowsEventLogs.shortDescription', { defaultMessage: 'Fetch logs from the Windows Event Log.', }), diff --git a/src/plugins/home/server/tutorials/zeek_logs/index.ts b/src/plugins/home/server/tutorials/zeek_logs/index.ts index 4bd54c96481b6..c273a93b1b0d5 100644 --- a/src/plugins/home/server/tutorials/zeek_logs/index.ts +++ b/src/plugins/home/server/tutorials/zeek_logs/index.ts @@ -37,7 +37,7 @@ export function zeekLogsSpecProvider(context: TutorialContext): TutorialSchema { name: i18n.translate('home.tutorials.zeekLogs.nameTitle', { defaultMessage: 'Zeek logs', }), - category: TutorialsCategory.SIEM, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.zeekLogs.shortDescription', { defaultMessage: 'Collect the logs created by Zeek/Bro.', }), diff --git a/src/plugins/index_pattern_management/public/components/breadcrumbs.ts b/src/plugins/index_pattern_management/public/components/breadcrumbs.ts index 6ffb929150b82..e62c46baa2c22 100644 --- a/src/plugins/index_pattern_management/public/components/breadcrumbs.ts +++ b/src/plugins/index_pattern_management/public/components/breadcrumbs.ts @@ -26,7 +26,7 @@ export function getListBreadcrumbs() { text: i18n.translate('indexPatternManagement.indexPatterns.listBreadcrumb', { defaultMessage: 'Index patterns', }), - href: `#/management/kibana/indexPatterns/`, + href: `/`, }, ]; } @@ -38,7 +38,7 @@ export function getCreateBreadcrumbs() { text: i18n.translate('indexPatternManagement.indexPatterns.createBreadcrumb', { defaultMessage: 'Create index pattern', }), - href: `#/management/kibana/indexPatterns/create`, + href: `/create`, }, ]; } @@ -48,7 +48,7 @@ export function getEditBreadcrumbs(indexPattern: IndexPattern) { ...getListBreadcrumbs(), { text: indexPattern.title, - href: `#/management/kibana/indexPatterns/patterns/${indexPattern.id}`, + href: `/patterns/${indexPattern.id}`, }, ]; } diff --git a/src/plugins/index_pattern_management/public/components/create_button/create_button.tsx b/src/plugins/index_pattern_management/public/components/create_button/create_button.tsx index d8a44675d80a0..cd21712ca28ed 100644 --- a/src/plugins/index_pattern_management/public/components/create_button/create_button.tsx +++ b/src/plugins/index_pattern_management/public/components/create_button/create_button.tsx @@ -98,7 +98,7 @@ export class CreateButton extends Component { anchorPosition="downLeft" > { + items={options.map((option) => { return (
{ it('should render normally', () => { - const component = shallow( {}} prependBasePath={x => x} />); + const component = shallow( {}} prependBasePath={(x) => x} />); expect(component).toMatchSnapshot(); }); @@ -35,7 +35,7 @@ describe('EmptyState', () => { const onRefreshHandler = sinon.stub(); const component = shallow( - x} /> + x} /> ); component.find('[data-test-subj="refreshIndicesButton"]').simulate('click'); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap index bdfdd2e7ad0a6..81ca3e644d3ce 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap @@ -1,122 +1,235 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Header should render a different name, prompt, and beta tag if provided 1`] = ` -
- -

- Create test index pattern - - -

-
- - + Test prompt +
+ } +> +
+ +

+ Create test index pattern + + + + Beta + + +

+
+ - -

- +

- - -

- - - - -
- Test prompt + +
+

+ + + + + Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations. + + + + +

+
+
+
+ +
+
+ +
+ +
+ Test prompt +
+ +
+
- -
+
`; exports[`Header should render normally 1`] = ` -
- -

- Create test index pattern -

-
- - +
+ +

+ Create test index pattern +

+
+ - -

- +

- - -

- - - - -
+ +
+

+ + + + + Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations. + + + + +

+
+
+
+
+
+ + +
+ +
+
`; exports[`Header should render without including system indices 1`] = ` -
- -

- Create test index pattern -

-
- - +
+ +

+ Create test index pattern +

+
+ - -

- +

- - -

- - - - -
+ +
+

+ + + + + Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations. + + + + +

+
+
+
+
+
+ + +
+ +
+ `; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx index d70746dd930e4..d12e0401380b9 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx @@ -19,46 +19,65 @@ import React from 'react'; import { Header } from '../header'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { mount } from 'enzyme'; +import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; +import { mockManagementPlugin } from '../../../../mocks'; describe('Header', () => { const indexPatternName = 'test index pattern'; + const mockedContext = mockManagementPlugin.createIndexPatternManagmentContext(); + it('should render normally', () => { - const component = shallowWithI18nProvider( + const component = mount(
{}} - changeTitle={() => {}} - /> + />, + { + wrappingComponent: KibanaContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } ); expect(component).toMatchSnapshot(); }); it('should render without including system indices', () => { - const component = shallowWithI18nProvider( + const component = mount(
{}} - changeTitle={() => {}} - /> + />, + { + wrappingComponent: KibanaContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } ); expect(component).toMatchSnapshot(); }); it('should render a different name, prompt, and beta tag if provided', () => { - const component = shallowWithI18nProvider( + const component = mount(
{}} prompt={
Test prompt
} indexPatternName={indexPatternName} isBeta={true} - changeTitle={() => {}} - /> + />, + { + wrappingComponent: KibanaContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } ); expect(component).toMatchSnapshot(); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx index 1be33e3edc3bc..35c6e67d0ea0e 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx @@ -32,6 +32,8 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useKibana } from '../../../../../../../plugins/kibana_react/public'; +import { IndexPatternManagmentContext } from '../../../../types'; export const Header = ({ prompt, @@ -40,7 +42,6 @@ export const Header = ({ isIncludingSystemIndices, onChangeIncludingSystemIndices, isBeta = false, - changeTitle, }: { prompt?: React.ReactNode; indexPatternName: string; @@ -48,8 +49,8 @@ export const Header = ({ isIncludingSystemIndices: boolean; onChangeIncludingSystemIndices: () => void; isBeta?: boolean; - changeTitle: (title: string) => void; }) => { + const changeTitle = useKibana().services.chrome.docTitle.change; const createIndexPatternHeader = i18n.translate( 'indexPatternManagement.createIndexPatternHeader', { diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx index e9eff57e17788..c590d2a7ddfe2 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx @@ -114,7 +114,7 @@ export class IndicesList extends React.Component ); - const items = PER_PAGE_INCREMENTS.map(increment => { + const items = PER_PAGE_INCREMENTS.map((increment) => { return ( ({ ensureMinimumTime: async (promises: Array>) => @@ -53,40 +52,43 @@ const allIndices = [ const goToNextStep = () => {}; -const savedObjectClient = coreMock.createStart().savedObjects.client; -savedObjectClient.find = () => - new Promise>(() => ({ savedObjects: [] })); - -const uiSettings = coreMock.createSetup().uiSettings; -uiSettings.get.mockReturnValue(''); - -const createComponent = (props?: Record) => { - return shallowWithI18nProvider( - - ); -}; +const mockContext = mockManagementPlugin.createIndexPatternManagmentContext(); + +mockContext.savedObjects.client.find = async () => + Promise.resolve(({ savedObjects: [] } as unknown) as SavedObjectsFindResponsePublic); +mockContext.uiSettings.get.mockReturnValue(''); describe('StepIndexPattern', () => { it('renders the loading state', () => { - const component = createComponent(); + const component = createComponentWithContext( + StepIndexPattern, + { + allIndices, + isIncludingSystemIndices: false, + goToNextStep, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext + ); component.setState({ isLoadingIndices: true }); expect(component.find('[data-test-subj="createIndexPatternStep1Loading"]')).toMatchSnapshot(); }); it('renders indices which match the initial query', async () => { - const component = createComponent({ initialQuery: 'kibana' }); + const component = createComponentWithContext( + StepIndexPattern, + { + allIndices, + isIncludingSystemIndices: false, + goToNextStep, + indexPatternCreationType: mockIndexPatternCreationType, + initialQuery: 'kibana', + }, + mockContext + ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected await component.update(); @@ -96,12 +98,21 @@ describe('StepIndexPattern', () => { }); it('renders errors when input is invalid', async () => { - const component = createComponent(); + const component = createComponentWithContext( + StepIndexPattern, + { + allIndices, + isIncludingSystemIndices: false, + goToNextStep, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext + ); const instance = component.instance() as StepIndexPattern; instance.onQueryChanged({ target: { value: '?' } } as React.ChangeEvent); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); expect({ @@ -110,12 +121,21 @@ describe('StepIndexPattern', () => { }); it('renders matching indices when input is valid', async () => { - const component = createComponent(); + const component = createComponentWithContext( + StepIndexPattern, + { + allIndices, + isIncludingSystemIndices: false, + goToNextStep, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext + ); const instance = component.instance() as StepIndexPattern; instance.onQueryChanged({ target: { value: 'k' } } as React.ChangeEvent); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -125,24 +145,51 @@ describe('StepIndexPattern', () => { }); it('appends a wildcard automatically to queries', async () => { - const component = createComponent(); + const component = createComponentWithContext( + StepIndexPattern, + { + allIndices, + isIncludingSystemIndices: false, + goToNextStep, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext + ); const instance = component.instance() as StepIndexPattern; instance.onQueryChanged({ target: { value: 'k' } } as React.ChangeEvent); expect(component.state('query')).toBe('k*'); }); it('disables the next step if the index pattern exists', async () => { - const component = createComponent(); + const component = createComponentWithContext( + StepIndexPattern, + { + allIndices, + isIncludingSystemIndices: false, + goToNextStep, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext + ); component.setState({ indexPatternExists: true }); expect(component.find(Header).prop('isNextStepDisabled')).toBe(true); }); it('ensures the response of the latest request is persisted', async () => { - const component = createComponent(); + const component = createComponentWithContext( + StepIndexPattern, + { + allIndices, + isIncludingSystemIndices: false, + goToNextStep, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext + ); const instance = component.instance() as StepIndexPattern; instance.onQueryChanged({ target: { value: 'e' } } as React.ChangeEvent); instance.lastQuery = 'k'; - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Honesty, the state would match the result of the `k` query but // it's hard to mock this in tests but if remove our fix @@ -155,7 +202,7 @@ describe('StepIndexPattern', () => { // Provide `es` so we do not auto append * and enter our other code flow instance.onQueryChanged({ target: { value: 'es' } } as React.ChangeEvent); instance.lastQuery = 'k'; - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); expect(component.state('exactMatchedIndices')).toEqual([]); }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index 5c8fa92d355a3..b6205a8731dfa 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -23,10 +23,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { indexPatterns, - DataPublicPluginStart, IndexPatternAttributes, + UI_SETTINGS, } from '../../../../../../../plugins/data/public'; -import { SavedObjectsClientContract, IUiSettingsClient } from '../../../../../../../core/public'; import { MAX_SEARCH_SIZE } from '../../constants'; import { getIndices, @@ -39,18 +38,17 @@ import { LoadingIndices } from './components/loading_indices'; import { StatusMessage } from './components/status_message'; import { IndicesList } from './components/indices_list'; import { Header } from './components/header'; +import { context as contextType } from '../../../../../../kibana_react/public'; import { IndexPatternCreationConfig } from '../../../../../../../plugins/index_pattern_management/public'; import { MatchedIndex } from '../../types'; +import { IndexPatternManagmentContextValue } from '../../../../types'; interface StepIndexPatternProps { allIndices: MatchedIndex[]; isIncludingSystemIndices: boolean; - esService: DataPublicPluginStart['search']['__LEGACY']['esClient']; - savedObjectsClient: SavedObjectsClientContract; indexPatternCreationType: IndexPatternCreationConfig; goToNextStep: (query: string) => void; initialQuery?: string; - uiSettings: IUiSettingsClient; } interface StepIndexPatternState { @@ -66,6 +64,10 @@ interface StepIndexPatternState { } export class StepIndexPattern extends Component { + static contextType = contextType; + + public readonly context!: IndexPatternManagmentContextValue; + state = { partialMatchedIndices: [], exactMatchedIndices: [], @@ -80,11 +82,12 @@ export class StepIndexPattern extends Component { - const { savedObjects } = await this.props.savedObjectsClient.find({ + const { savedObjects } = await this.context.services.savedObjects.client.find< + IndexPatternAttributes + >({ type: 'index-pattern', fields: ['title'], perPage: 10000, }); - const existingIndexPatterns = savedObjects.map(obj => + const existingIndexPatterns = savedObjects.map((obj) => obj && obj.attributes ? obj.attributes.title : '' ) as string[]; @@ -111,7 +116,7 @@ export class StepIndexPattern extends Component { - const { esService, indexPatternCreationType } = this.props; + const { indexPatternCreationType } = this.props; const { existingIndexPatterns } = this.state; if ((existingIndexPatterns as string[]).includes(query)) { @@ -123,7 +128,12 @@ export class StepIndexPattern extends Component {}; +const mockContext = mockManagementPlugin.createIndexPatternManagmentContext(); const fields = [ { name: '@timestamp', type: 'date', }, ]; -const indexPatternsService = { +mockContext.data.indexPatterns = { make: () => ({ fieldsFetcher: { fetchForWildcard: jest.fn().mockReturnValue(Promise.resolve(fields)), @@ -55,28 +57,30 @@ const indexPatternsService = { describe('StepTimeField', () => { it('should render normally', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); expect(component).toMatchSnapshot(); }); it('should render timeFields', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ @@ -90,14 +94,15 @@ describe('StepTimeField', () => { }); it('should render a selected timeField', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ @@ -113,14 +118,15 @@ describe('StepTimeField', () => { }); it('should ensure disabled time field options work properly', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ @@ -146,14 +152,15 @@ describe('StepTimeField', () => { }); it('should disable the action button if an invalid time field is selected', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ @@ -172,14 +179,15 @@ describe('StepTimeField', () => { }); it('should enable the action button if the user decides to not select a time field', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ @@ -198,14 +206,15 @@ describe('StepTimeField', () => { }); it('should render advanced options', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ showingAdvancedOptions: true }); @@ -214,14 +223,15 @@ describe('StepTimeField', () => { }); it('should render advanced options with an index pattern id', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ @@ -233,14 +243,15 @@ describe('StepTimeField', () => { }); it('should render a loading state when creating the index pattern', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ isCreating: true }); @@ -249,14 +260,15 @@ describe('StepTimeField', () => { }); it('should render any error message', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ error: 'foobar' }); @@ -265,14 +277,15 @@ describe('StepTimeField', () => { }); it('should render "Custom index pattern ID already exists" when error is "Conflict"', () => { - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern: noop, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); component.setState({ error: 'Conflict' }); @@ -284,14 +297,15 @@ describe('StepTimeField', () => { const createIndexPattern = async () => { throw new Error('foobar'); }; - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); await (component.instance() as StepTimeField).createIndexPattern(); @@ -305,14 +319,15 @@ describe('StepTimeField', () => { it('should call createIndexPattern with undefined time field when no time filter chosen', async () => { const createIndexPattern = jest.fn(); - const component = shallowWithI18nProvider( - + const component = createComponentWithContext( + StepTimeField, + { + indexPattern: 'ki*', + goToPreviousStep: noop, + createIndexPattern, + indexPatternCreationType: mockIndexPatternCreationType, + }, + mockContext ); await (component.instance() as StepTimeField).fetchTimeFields(); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx index d22b503937290..98ce22cd14227 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx @@ -34,12 +34,12 @@ import { Header } from './components/header'; import { TimeField } from './components/time_field'; import { AdvancedOptions } from './components/advanced_options'; import { ActionButtons } from './components/action_buttons'; +import { context } from '../../../../../../kibana_react/public'; +import { IndexPatternManagmentContextValue } from '../../../../types'; import { IndexPatternCreationConfig } from '../../../..'; -import { DataPublicPluginStart } from '../../../../../../data/public'; interface StepTimeFieldProps { indexPattern: string; - indexPatternsService: DataPublicPluginStart['indexPatterns']; goToPreviousStep: () => void; createIndexPattern: (selectedTimeField: string | undefined, indexPatternId: string) => void; indexPatternCreationType: IndexPatternCreationConfig; @@ -65,6 +65,10 @@ interface TimeFieldConfig { } export class StepTimeField extends Component { + static contextType = context; + + public readonly context!: IndexPatternManagmentContextValue; + state = { error: '', timeFields: [], @@ -96,10 +100,10 @@ export class StepTimeField extends Component { - const { indexPatternsService, indexPattern: pattern } = this.props; + const { indexPattern: pattern } = this.props; const { getFetchForWildcardOptions } = this.props.indexPatternCreationType; - const indexPattern = await indexPatternsService.make(); + const indexPattern = await this.context.services.data.indexPatterns.make(); indexPattern.title = pattern; this.setState({ isFetchingTimeFields: true }); @@ -133,7 +137,7 @@ export class StepTimeField extends Component { - this.setState(state => ({ + this.setState((state) => ({ isAdvancedOptionsVisible: !state.isAdvancedOptionsVisible, })); }; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx index f526aa35c2afe..b14cd526d7e27 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx @@ -17,18 +17,11 @@ * under the License. */ -import React from 'react'; -import { shallow } from 'enzyme'; - -import { - CreateIndexPatternWizard, - CreateIndexPatternWizardProps, -} from './create_index_pattern_wizard'; -import { coreMock } from '../../../../../core/public/mocks'; -import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; +import { CreateIndexPatternWizard } from './create_index_pattern_wizard'; import { IndexPattern } from '../../../../../plugins/data/public'; -import { SavedObjectsClient } from '../../../../../core/public'; -import { IndexPatternCreationConfig } from '../../service/creation'; +import { mockManagementPlugin } from '../../mocks'; +import { IndexPatternCreationConfig } from '../../'; +import { createComponentWithContext } from '../test_utils'; jest.mock('./components/step_index_pattern', () => ({ StepIndexPattern: 'StepIndexPattern' })); jest.mock('./components/step_time_field', () => ({ StepTimeField: 'StepTimeField' })); @@ -40,30 +33,6 @@ jest.mock('./lib/get_indices', () => ({ return [{ name: 'kibana' }]; }, })); - -const { savedObjects, overlays, uiSettings, chrome, http } = coreMock.createStart(); -const { indexPatterns, search } = dataPluginMock.createStartContract(); - -const mockIndexPatternCreationType = new IndexPatternCreationConfig({ - type: 'default', - name: 'name', -}); - -const services = ({ - indexPatternCreation: { - getType: jest.fn(() => mockIndexPatternCreationType), - }, - es: search.__LEGACY.esClient, - indexPatterns, - savedObjectsClient: savedObjects.client as SavedObjectsClient, - uiSettings, - changeUrl: jest.fn(), - openConfirm: overlays.openConfirm, - setBreadcrumbs: jest.fn(), - docTitle: chrome.docTitle, - prependBasePath: http.basePath.prepend, -} as unknown) as CreateIndexPatternWizardProps['services']; - const routeComponentPropsMock = { history: { push: jest.fn(), @@ -71,19 +40,30 @@ const routeComponentPropsMock = { location: {} as any, match: {} as any, }; +const mockContext = mockManagementPlugin.createIndexPatternManagmentContext(); +mockContext.indexPatternManagementStart.creation.getType = () => { + return new IndexPatternCreationConfig({ + type: 'default', + name: 'name', + }); +}; describe('CreateIndexPatternWizard', () => { test(`defaults to the loading state`, () => { - const component = shallow( - + const component = createComponentWithContext( + CreateIndexPatternWizard, + { ...routeComponentPropsMock }, + mockContext ); expect(component).toMatchSnapshot(); }); test('renders the empty state when there are no indices', async () => { - const component = shallow( - + const component = createComponentWithContext( + CreateIndexPatternWizard, + { ...routeComponentPropsMock }, + mockContext ); component.setState({ @@ -97,8 +77,10 @@ describe('CreateIndexPatternWizard', () => { }); test('renders when there are no indices but there are remote clusters', async () => { - const component = shallow( - + const component = createComponentWithContext( + CreateIndexPatternWizard, + { ...routeComponentPropsMock }, + mockContext ); component.setState({ @@ -112,8 +94,10 @@ describe('CreateIndexPatternWizard', () => { }); test('shows system indices even if there are no other indices if the include system indices is toggled', async () => { - const component = shallow( - + const component = createComponentWithContext( + CreateIndexPatternWizard, + { ...routeComponentPropsMock }, + mockContext ); component.setState({ @@ -127,8 +111,10 @@ describe('CreateIndexPatternWizard', () => { }); test('renders index pattern step when there are indices', async () => { - const component = shallow( - + const component = createComponentWithContext( + CreateIndexPatternWizard, + { ...routeComponentPropsMock }, + mockContext ); component.setState({ @@ -141,8 +127,10 @@ describe('CreateIndexPatternWizard', () => { }); test('renders time field step when step is set to 2', async () => { - const component = shallow( - + const component = createComponentWithContext( + CreateIndexPatternWizard, + { ...routeComponentPropsMock }, + mockContext ); component.setState({ @@ -158,7 +146,7 @@ describe('CreateIndexPatternWizard', () => { test('invokes the provided services when creating an index pattern', async () => { const create = jest.fn().mockImplementation(() => 'id'); const clear = jest.fn(); - services.indexPatterns.clearCache = clear; + mockContext.data.indexPatterns.clearCache = clear; const indexPattern = ({ id: '1', title: 'my-fake-index-pattern', @@ -166,17 +154,19 @@ describe('CreateIndexPatternWizard', () => { fields: [], create, } as unknown) as IndexPattern; - services.indexPatterns.make = async () => { + mockContext.data.indexPatterns.make = async () => { return indexPattern; }; - const component = shallow( - + const component = createComponentWithContext( + CreateIndexPatternWizard, + { ...routeComponentPropsMock }, + mockContext ); component.setState({ indexPattern: 'foo' }); - await component.instance().createIndexPattern(undefined, 'id'); - expect(services.uiSettings.get).toBeCalled(); + await (component.instance() as CreateIndexPatternWizard).createIndexPattern(undefined, 'id'); + expect(mockContext.uiSettings.get).toBeCalled(); expect(create).toBeCalled(); expect(clear).toBeCalledWith('id'); expect(routeComponentPropsMock.history.push).toBeCalledWith(`/patterns/id`); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx index 62bf47546e103..111be41cfc53a 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx @@ -23,42 +23,20 @@ import { EuiGlobalToastList, EuiGlobalToastListToast, EuiPanel } from '@elastic/ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { withRouter, RouteComponentProps } from 'react-router-dom'; - -import { - SavedObjectsClientContract, - IUiSettingsClient, - OverlayStart, - ChromeDocTitle, - IBasePath, -} from 'src/core/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { ManagementAppMountParams } from '../../../../management/public'; import { StepIndexPattern } from './components/step_index_pattern'; import { StepTimeField } from './components/step_time_field'; import { Header } from './components/header'; import { LoadingState } from './components/loading_state'; import { EmptyState } from './components/empty_state'; +import { context as contextType } from '../../../../kibana_react/public'; import { getCreateBreadcrumbs } from '../breadcrumbs'; import { MAX_SEARCH_SIZE } from './constants'; import { ensureMinimumTime, getIndices } from './lib'; -import { IndexPatternCreationConfig, IndexPatternManagementStart } from '../..'; +import { IndexPatternCreationConfig } from '../..'; +import { IndexPatternManagmentContextValue } from '../../types'; import { MatchedIndex } from './types'; -export interface CreateIndexPatternWizardProps extends RouteComponentProps { - services: { - indexPatternCreation: IndexPatternManagementStart['creation']; - es: DataPublicPluginStart['search']['__LEGACY']['esClient']; - indexPatterns: DataPublicPluginStart['indexPatterns']; - savedObjectsClient: SavedObjectsClientContract; - uiSettings: IUiSettingsClient; - openConfirm: OverlayStart['openConfirm']; - setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; - docTitle: ChromeDocTitle; - prependBasePath: IBasePath['prepend']; - }; -} - interface CreateIndexPatternWizardState { step: number; indexPattern: string; @@ -71,19 +49,19 @@ interface CreateIndexPatternWizardState { } export class CreateIndexPatternWizard extends Component< - CreateIndexPatternWizardProps, + RouteComponentProps, CreateIndexPatternWizardState > { - constructor(props: CreateIndexPatternWizardProps) { - super(props); - const { - services: { indexPatternCreation, setBreadcrumbs }, - location, - } = props; + static contextType = contextType; - setBreadcrumbs(getCreateBreadcrumbs()); + public readonly context!: IndexPatternManagmentContextValue; - const type = new URLSearchParams(location.search).get('type') || undefined; + constructor(props: RouteComponentProps, context: IndexPatternManagmentContextValue) { + super(props, context); + + context.services.setBreadcrumbs(getCreateBreadcrumbs()); + + const type = new URLSearchParams(props.location.search).get('type') || undefined; this.state = { step: 1, @@ -93,7 +71,7 @@ export class CreateIndexPatternWizard extends Component< isInitiallyLoadingIndices: true, isIncludingSystemIndices: false, toasts: [], - indexPatternCreationType: indexPatternCreation.getType(type), + indexPatternCreationType: context.services.indexPatternManagementStart.creation.getType(type), }; } @@ -109,7 +87,7 @@ export class CreateIndexPatternWizard extends Component< try { return await asyncFn; } catch (errors) { - this.setState(prevState => ({ + this.setState((prevState) => ({ toasts: prevState.toasts.concat([ { title: errorMsg, @@ -124,8 +102,6 @@ export class CreateIndexPatternWizard extends Component< }; fetchData = async () => { - const { services } = this.props; - this.setState({ allIndices: [], isInitiallyLoadingIndices: true, @@ -149,7 +125,12 @@ export class CreateIndexPatternWizard extends Component< // query local and remote indices, updating state independently ensureMinimumTime( this.catchAndWarn( - getIndices(services.es, this.state.indexPatternCreationType, `*`, MAX_SEARCH_SIZE), + getIndices( + this.context.services.data.search.__LEGACY.esClient, + this.state.indexPatternCreationType, + `*`, + MAX_SEARCH_SIZE + ), [], indicesFailMsg ) @@ -160,7 +141,12 @@ export class CreateIndexPatternWizard extends Component< this.catchAndWarn( // if we get an error from remote cluster query, supply fallback value that allows user entry. // ['a'] is fallback value - getIndices(services.es, this.state.indexPatternCreationType, `*:*`, 1), + getIndices( + this.context.services.data.search.__LEGACY.esClient, + this.state.indexPatternCreationType, + `*:*`, + 1 + ), ['a'], clustersFailMsg ).then((remoteIndices: string[] | MatchedIndex[]) => @@ -169,10 +155,10 @@ export class CreateIndexPatternWizard extends Component< }; createIndexPattern = async (timeFieldName: string | undefined, indexPatternId: string) => { - const { services, history } = this.props; + const { history } = this.props; const { indexPattern } = this.state; - const emptyPattern = await services.indexPatterns.make(); + const emptyPattern = await this.context.services.data.indexPatterns.make(); Object.assign(emptyPattern, { id: indexPatternId, @@ -191,7 +177,7 @@ export class CreateIndexPatternWizard extends Component< } ); - const isConfirmed = await services.openConfirm(confirmMessage, { + const isConfirmed = await this.context.services.overlays.openConfirm(confirmMessage, { confirmButtonText: i18n.translate( 'indexPatternManagement.indexPattern.goToPatternButtonLabel', { @@ -207,11 +193,11 @@ export class CreateIndexPatternWizard extends Component< } } - if (!services.uiSettings.get('defaultIndex')) { - await services.uiSettings.set('defaultIndex', createdId); + if (!this.context.services.uiSettings.get('defaultIndex')) { + await this.context.services.uiSettings.set('defaultIndex', createdId); } - services.indexPatterns.clearCache(createdId); + this.context.services.data.indexPatterns.clearCache(createdId); history.push(`/patterns/${createdId}`); }; @@ -224,14 +210,13 @@ export class CreateIndexPatternWizard extends Component< }; onChangeIncludingSystemIndices = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isIncludingSystemIndices: !prevState.isIncludingSystemIndices, })); }; renderHeader() { const { isIncludingSystemIndices } = this.state; - const { services } = this.props; return (
); } @@ -265,13 +249,13 @@ export class CreateIndexPatternWizard extends Component< return ( ); } if (step === 1) { - const { services, location } = this.props; + const { location } = this.props; const initialQuery = new URLSearchParams(location.search).get('id') || undefined; return ( @@ -279,21 +263,16 @@ export class CreateIndexPatternWizard extends Component< allIndices={allIndices} initialQuery={indexPattern || initialQuery} isIncludingSystemIndices={isIncludingSystemIndices} - esService={services.es} - savedObjectsClient={services.savedObjectsClient} indexPatternCreationType={this.state.indexPatternCreationType} goToNextStep={this.goToTimeFieldStep} - uiSettings={services.uiSettings} /> ); } if (step === 2) { - const { services } = this.props; return ( { - this.setState(prevState => ({ - toasts: prevState.toasts.filter(toast => toast.id !== id), + this.setState((prevState) => ({ + toasts: prevState.toasts.filter((toast) => toast.id !== id), })); }; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_illegal_characters.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_illegal_characters.ts index ca4fc8122903c..48c708b016aef 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_illegal_characters.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_illegal_characters.ts @@ -18,5 +18,5 @@ */ export function containsIllegalCharacters(pattern: string, illegalCharacters: string[]) { - return illegalCharacters.some(char => pattern.includes(char)); + return illegalCharacters.some((char) => pattern.includes(char)); } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.test.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.test.ts index e5fcfe056923a..a38f3cbd8fe81 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.test.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.test.ts @@ -20,25 +20,25 @@ import { ensureMinimumTime } from './ensure_minimum_time'; describe('ensureMinimumTime', () => { - it('resolves single promise', async done => { - const promiseA = new Promise(resolve => resolve('a')); + it('resolves single promise', async (done) => { + const promiseA = new Promise((resolve) => resolve('a')); const a = await ensureMinimumTime(promiseA, 0); expect(a).toBe('a'); done(); }); - it('resolves multiple promises', async done => { - const promiseA = new Promise(resolve => resolve('a')); - const promiseB = new Promise(resolve => resolve('b')); + it('resolves multiple promises', async (done) => { + const promiseA = new Promise((resolve) => resolve('a')); + const promiseB = new Promise((resolve) => resolve('b')); const [a, b] = await ensureMinimumTime([promiseA, promiseB], 0); expect(a).toBe('a'); expect(b).toBe('b'); done(); }); - it('resolves in the amount of time provided, at minimum', async done => { + it('resolves in the amount of time provided, at minimum', async (done) => { const startTime = new Date().getTime(); - const promise = new Promise(resolve => resolve()); + const promise = new Promise((resolve) => resolve()); await ensureMinimumTime(promise, 100); const endTime = new Date().getTime(); expect(endTime - startTime).toBeGreaterThanOrEqual(100); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.ts index 84852ece485eb..3b3895cbb9ad9 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.ts @@ -54,7 +54,7 @@ export async function ensureMinimumTime( if (asyncActionDuration < bufferedMinimumTimeMs) { const additionalWaitingTime = bufferedMinimumTimeMs - (asyncActionCompletionTime - asyncActionStartTime); - await new Promise(resolve => setTimeout(resolve, additionalWaitingTime)); + await new Promise((resolve) => setTimeout(resolve, additionalWaitingTime)); } return returnValue; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts index 809c9f77b2832..b7056ad17343a 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { IFieldType } from '../../../../../../plugins/data/public'; export function extractTimeFields(fields: IFieldType[]) { - const dateFields = fields.filter(field => field.type === 'date'); + const dateFields = fields.filter((field) => field.type === 'date'); const label = i18n.translate( 'indexPatternManagement.createIndexPattern.stepTime.noTimeFieldsLabel', { @@ -54,7 +54,7 @@ export function extractTimeFields(fields: IFieldType[]) { }; return [ - ...dateFields.map(field => ({ + ...dateFields.map((field) => ({ display: field.name, fieldName: field.name, })), diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts index 1c67fea9fa352..b1faca8a04964 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts @@ -92,7 +92,7 @@ function esClientFactory(search: (params: any) => any): LegacyApiCaller { search, msearch: () => ({ abort: () => {}, - ...new Promise(resolve => resolve({})), + ...new Promise((resolve) => resolve({})), }), }; } @@ -120,7 +120,7 @@ describe('getIndices', () => { it('should trim the input', async () => { let index; const esClient = esClientFactory( - jest.fn().mockImplementation(params => { + jest.fn().mockImplementation((params) => { index = params.index; }) ); @@ -132,7 +132,7 @@ describe('getIndices', () => { it('should use the limit', async () => { let limit; const esClient = esClientFactory( - jest.fn().mockImplementation(params => { + jest.fn().mockImplementation((params) => { limit = params.body.aggs.indices.terms.size; }) ); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts index cc3fd4075aa0e..7e2eeb17ab387 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts @@ -41,7 +41,7 @@ function filterSystemIndices(indices: MatchedIndex[], isIncludingSystemIndices: const acceptableIndices = isIncludingSystemIndices ? indices : // All system indices begin with a period. - indices.filter(index => !isSystemIndex(index.name)); + indices.filter((index) => !isSystemIndex(index.name)); return acceptableIndices.slice(0, MAX_NUMBER_OF_MATCHING_INDICES); } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx index fb5c27774f506..f7b982ef1659e 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -21,9 +21,9 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { HttpStart, DocLinksStart } from 'src/core/public'; -import { ChromeDocTitle, NotificationsStart, IUiSettingsClient } from 'src/core/public'; -import { IndexPattern, DataPublicPluginStart } from '../../../../../../plugins/data/public'; +import { IndexPattern } from '../../../../../../plugins/data/public'; +import { useKibana } from '../../../../../../plugins/kibana_react/public'; +import { IndexPatternManagmentContext } from '../../../types'; import { IndexHeader } from '../index_header'; import { TAB_SCRIPTED_FIELDS, TAB_INDEXED_FIELDS } from '../constants'; @@ -33,17 +33,6 @@ interface CreateEditFieldProps extends RouteComponentProps { indexPattern: IndexPattern; mode?: string; fieldName?: string; - fieldFormatEditors: any; - services: { - uiSettings: IUiSettingsClient; - docTitle: ChromeDocTitle; - http: HttpStart; - docLinksScriptedFields: DocLinksStart['links']['scriptedFields']; - SearchBar: DataPublicPluginStart['ui']['SearchBar']; - toasts: NotificationsStart['toasts']; - fieldFormats: DataPublicPluginStart['fieldFormats']; - indexPatterns: DataPublicPluginStart['indexPatterns']; - }; } const newFieldPlaceholder = i18n.translate( @@ -54,18 +43,14 @@ const newFieldPlaceholder = i18n.translate( ); export const CreateEditField = withRouter( - ({ - indexPattern, - mode, - fieldName, - fieldFormatEditors, - services, - history, - }: CreateEditFieldProps) => { + ({ indexPattern, mode, fieldName, history }: CreateEditFieldProps) => { + const { data, uiSettings, chrome, notifications } = useKibana< + IndexPatternManagmentContext + >().services; const field = mode === 'edit' && fieldName ? indexPattern.fields.getByName(fieldName) - : services.indexPatterns.createField( + : data.indexPatterns.createField( indexPattern, { scripted: true, @@ -85,16 +70,18 @@ export const CreateEditField = withRouter( values: { indexPatternTitle: indexPattern.title, fieldName }, } ); - services.toasts.addWarning(message); + notifications.toasts.addWarning(message); history.push(url); } const docFieldName = field?.name || newFieldPlaceholder; - services.docTitle.change([docFieldName, indexPattern.title]); + chrome.docTitle.change([docFieldName, indexPattern.title]); const redirectAway = () => { - history.push(`${url}?_a=(tab:${field?.scripted ? TAB_SCRIPTED_FIELDS : TAB_INDEXED_FIELDS})`); + history.push( + `${url}#/?_a=(tab:${field?.scripted ? TAB_SCRIPTED_FIELDS : TAB_INDEXED_FIELDS})` + ); }; if (field) { @@ -104,7 +91,7 @@ export const CreateEditField = withRouter( @@ -112,15 +99,7 @@ export const CreateEditField = withRouter( indexPattern={indexPattern} field={field} services={{ - uiSettings: services.uiSettings, - http: services.http, - fieldFormatEditors, redirectAway, - docLinksScriptedFields: services.docLinksScriptedFields, - SearchBar: services.SearchBar, - toasts: services.toasts, - fieldFormats: services.fieldFormats, - indexPatterns: services.indexPatterns, }} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field_container.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field_container.tsx index 70851b712d756..39c0add40e9ad 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field_container.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field_container.tsx @@ -20,53 +20,30 @@ import React, { useEffect, useState } from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { - HttpStart, - DocLinksStart, - ChromeDocTitle, - NotificationsStart, - IUiSettingsClient, -} from 'src/core/public'; - -import { IndexPattern, DataPublicPluginStart } from '../../../../../../plugins/data/public'; -import { ManagementAppMountParams } from '../../../../../management/public'; +import { IndexPattern } from '../../../../../../plugins/data/public'; import { getEditFieldBreadcrumbs, getCreateFieldBreadcrumbs } from '../../breadcrumbs'; +import { useKibana } from '../../../../../../plugins/kibana_react/public'; +import { IndexPatternManagmentContext } from '../../../types'; import { CreateEditField } from './create_edit_field'; -export interface CreateEditFieldContainerProps - extends RouteComponentProps<{ id: string; fieldName: string }> { - getIndexPattern: (id: string) => Promise; - fieldFormatEditors: any; - getConfig: IUiSettingsClient; - services: { - uiSettings: IUiSettingsClient; - notifications: NotificationsStart; - docTitle: ChromeDocTitle; - http: HttpStart; - docLinksScriptedFields: DocLinksStart['links']['scriptedFields']; - SearchBar: DataPublicPluginStart['ui']['SearchBar']; - toasts: NotificationsStart['toasts']; - fieldFormats: DataPublicPluginStart['fieldFormats']; - indexPatterns: DataPublicPluginStart['indexPatterns']; - setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; - }; -} +export type CreateEditFieldContainerProps = RouteComponentProps<{ id: string; fieldName: string }>; const CreateEditFieldCont: React.FC = ({ ...props }) => { + const { setBreadcrumbs, data } = useKibana().services; const [indexPattern, setIndexPattern] = useState(); useEffect(() => { - props.getIndexPattern(props.match.params.id).then((ip: IndexPattern) => { + data.indexPatterns.get(props.match.params.id).then((ip: IndexPattern) => { setIndexPattern(ip); if (ip) { - props.services.setBreadcrumbs( + setBreadcrumbs( props.match.params.fieldName ? getEditFieldBreadcrumbs(ip, props.match.params.fieldName) : getCreateFieldBreadcrumbs(ip) ); } }); - }, [props.match.params.id, props.getIndexPattern, props]); + }, [props.match.params.id, props.match.params.fieldName, setBreadcrumbs, data.indexPatterns]); if (indexPattern) { return ( @@ -74,17 +51,6 @@ const CreateEditFieldCont: React.FC = ({ ...props indexPattern={indexPattern} mode={props.match.params.fieldName ? 'edit' : 'create'} fieldName={props.match.params.fieldName} - fieldFormatEditors={props.fieldFormatEditors} - services={{ - uiSettings: props.services.uiSettings, - http: props.services.http, - docLinksScriptedFields: props.services.docLinksScriptedFields, - SearchBar: props.services.SearchBar, - toasts: props.services.toasts, - fieldFormats: props.services.fieldFormats, - docTitle: props.services.docTitle, - indexPatterns: props.services.indexPatterns, - }} /> ); } else { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index f1c020cc409e0..eab8b2c231c9c 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -33,31 +33,16 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - ChromeDocTitle, - NotificationsStart, - OverlayStart, - IUiSettingsClient, - SavedObjectsClientContract, -} from 'src/core/public'; import { IndexPattern, IndexPatternField } from '../../../../../plugins/data/public'; -import { IndexPatternManagementStart } from '../..'; +import { useKibana } from '../../../../../plugins/kibana_react/public'; +import { IndexPatternManagmentContext } from '../../types'; import { Tabs } from './tabs'; import { IndexHeader } from './index_header'; import { IndexPatternTableItem } from '../types'; import { getIndexPatterns } from '../utils'; -interface EditIndexPatternProps extends RouteComponentProps { +export interface EditIndexPatternProps extends RouteComponentProps { indexPattern: IndexPattern; - config: IUiSettingsClient; - services: { - notifications: NotificationsStart; - docTitle: ChromeDocTitle; - overlays: OverlayStart; - savedObjectsClient: SavedObjectsClientContract; - indexPatternManagement: IndexPatternManagementStart; - painlessDocLink: string; - }; } const mappingAPILink = i18n.translate( @@ -97,68 +82,69 @@ const confirmModalOptionsDelete = { }; export const EditIndexPattern = withRouter( - ({ indexPattern, config, services, history, location }: EditIndexPatternProps) => { + ({ indexPattern, history, location }: EditIndexPatternProps) => { + const { uiSettings, indexPatternManagementStart, overlays, savedObjects, chrome } = useKibana< + IndexPatternManagmentContext + >().services; const [fields, setFields] = useState(indexPattern.getNonScriptedFields()); const [conflictedFields, setConflictedFields] = useState( - indexPattern.fields.filter(field => field.type === 'conflict') + indexPattern.fields.filter((field) => field.type === 'conflict') ); - const [defaultIndex, setDefaultIndex] = useState(config.get('defaultIndex')); + const [defaultIndex, setDefaultIndex] = useState(uiSettings.get('defaultIndex')); const [tags, setTags] = useState([]); useEffect(() => { setFields(indexPattern.getNonScriptedFields()); - setConflictedFields(indexPattern.fields.filter(field => field.type === 'conflict')); + setConflictedFields(indexPattern.fields.filter((field) => field.type === 'conflict')); }, [indexPattern]); useEffect(() => { const indexPatternTags = - services.indexPatternManagement.list.getIndexPatternTags( + indexPatternManagementStart.list.getIndexPatternTags( indexPattern, indexPattern.id === defaultIndex ) || []; setTags(indexPatternTags); - }, [defaultIndex, indexPattern, services.indexPatternManagement.list]); + }, [defaultIndex, indexPattern, indexPatternManagementStart.list]); const setDefaultPattern = useCallback(() => { - config.set('defaultIndex', indexPattern.id); + uiSettings.set('defaultIndex', indexPattern.id); setDefaultIndex(indexPattern.id || ''); - }, [config, indexPattern.id]); + }, [uiSettings, indexPattern.id]); const refreshFields = () => { - services.overlays - .openConfirm(confirmMessage, confirmModalOptionsRefresh) - .then(async isConfirmed => { - if (isConfirmed) { - await indexPattern.init(true); - setFields(indexPattern.getNonScriptedFields()); - } - }); + overlays.openConfirm(confirmMessage, confirmModalOptionsRefresh).then(async (isConfirmed) => { + if (isConfirmed) { + await indexPattern.init(true); + setFields(indexPattern.getNonScriptedFields()); + } + }); }; - const removePatternClick = () => { + const removePattern = () => { async function doRemove() { if (indexPattern.id === defaultIndex) { const indexPatterns: IndexPatternTableItem[] = await getIndexPatterns( - services.savedObjectsClient, - config.get('defaultIndex'), - services.indexPatternManagement + savedObjects.client, + uiSettings.get('defaultIndex'), + indexPatternManagementStart ); - config.remove('defaultIndex'); - const otherPatterns = filter(indexPatterns, pattern => { + uiSettings.remove('defaultIndex'); + const otherPatterns = filter(indexPatterns, (pattern) => { return pattern.id !== indexPattern.id; }); if (otherPatterns.length) { - config.set('defaultIndex', otherPatterns[0].id); + uiSettings.set('defaultIndex', otherPatterns[0].id); } } - Promise.resolve(indexPattern.destroy()).then(function() { + Promise.resolve(indexPattern.destroy()).then(function () { history.push(''); }); } - services.overlays.openConfirm('', confirmModalOptionsDelete).then(isConfirmed => { + overlays.openConfirm('', confirmModalOptionsDelete).then((isConfirmed) => { if (isConfirmed) { doRemove(); } @@ -186,7 +172,7 @@ export const EditIndexPattern = withRouter( defaultMessage: 'Index pattern details', }); - services.docTitle.change(indexPattern.title); + chrome.docTitle.change(indexPattern.title); const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0)); @@ -199,7 +185,7 @@ export const EditIndexPattern = withRouter( indexPattern={indexPattern} setDefault={setDefaultPattern} refreshFields={refreshFields} - deleteIndexPatternClick={removePatternClick} + deleteIndexPatternClick={removePattern} defaultIndex={defaultIndex} /> @@ -244,11 +230,6 @@ export const EditIndexPattern = withRouter( diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_container.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_container.tsx index 2f02765cd0596..9bd42ed8d64ee 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_container.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_container.tsx @@ -19,52 +19,26 @@ import React, { useEffect, useState } from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { - ChromeDocTitle, - NotificationsStart, - OverlayStart, - IUiSettingsClient, - SavedObjectsClientContract, -} from 'src/core/public'; import { IndexPattern } from '../../../../../plugins/data/public'; -import { ManagementAppMountParams } from '../../../../management/public'; -import { IndexPatternManagementStart } from '../..'; +import { useKibana } from '../../../../../plugins/kibana_react/public'; +import { IndexPatternManagmentContext } from '../../types'; import { getEditBreadcrumbs } from '../breadcrumbs'; import { EditIndexPattern } from '../edit_index_pattern'; -interface EditIndexPatternContainerProps extends RouteComponentProps<{ id: string }> { - getIndexPattern: (id: string) => Promise; - config: IUiSettingsClient; - services: { - notifications: NotificationsStart; - docTitle: ChromeDocTitle; - overlays: OverlayStart; - savedObjectsClient: SavedObjectsClientContract; - setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; - indexPatternManagement: IndexPatternManagementStart; - painlessDocLink: string; - }; -} - -const EditIndexPatternCont: React.FC = ({ ...props }) => { +const EditIndexPatternCont: React.FC> = ({ ...props }) => { + const { data, setBreadcrumbs } = useKibana().services; const [indexPattern, setIndexPattern] = useState(); useEffect(() => { - props.getIndexPattern(props.match.params.id).then((ip: IndexPattern) => { + data.indexPatterns.get(props.match.params.id).then((ip: IndexPattern) => { setIndexPattern(ip); - props.services.setBreadcrumbs(getEditBreadcrumbs(ip)); + setBreadcrumbs(getEditBreadcrumbs(ip)); }); - }, [props.match.params.id, props.getIndexPattern, props]); + }, [data.indexPatterns, props.match.params.id, setBreadcrumbs]); if (indexPattern) { - return ( - - ); + return ; } else { return <>; } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_state_container.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_state_container.ts index f99a5b072b423..1febef1b3bb75 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_state_container.ts +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern_state_container.ts @@ -69,7 +69,7 @@ export function createEditIndexPatternPageStateContainer({ stateContainer: { ...stateContainer, // state syncing utility requires state containers to handle "null" - set: state => state && stateContainer.set(state), + set: (state) => state && stateContainer.set(state), }, stateStorage: kbnUrlStateStorage, }); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 2f3360880479c..8c024fa7adcf5 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -71,7 +71,7 @@ describe('IndexedFieldsTable', () => { /> ); - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); component.update(); expect(component).toMatchSnapshot(); @@ -91,7 +91,7 @@ describe('IndexedFieldsTable', () => { /> ); - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); component.setProps({ fieldFilter: 'Elast' }); component.update(); @@ -112,7 +112,7 @@ describe('IndexedFieldsTable', () => { /> ); - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); component.setProps({ indexedFieldTypeFilter: 'date' }); component.update(); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 6b1048d3c9d0c..3344c46c35ac6 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -73,7 +73,7 @@ export class IndexedFieldsTable extends Component< return ( (fields && - fields.map(field => { + fields.map((field) => { return { ...field, displayName: field.displayName, @@ -95,11 +95,11 @@ export class IndexedFieldsTable extends Component< (fields, fieldFilter, indexedFieldTypeFilter) => { if (fieldFilter) { const normalizedFieldFilter = fieldFilter.toLowerCase(); - fields = fields.filter(field => field.name.toLowerCase().includes(normalizedFieldFilter)); + fields = fields.filter((field) => field.name.toLowerCase().includes(normalizedFieldFilter)); } if (indexedFieldTypeFilter) { - fields = fields.filter(field => field.type === indexedFieldTypeFilter); + fields = fields.filter((field) => field.type === indexedFieldTypeFilter); } return fields; @@ -115,7 +115,7 @@ export class IndexedFieldsTable extends Component< this.props.helpers.redirectToRoute(field)} + editField={(field) => this.props.helpers.redirectToRoute(field)} /> ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx index 11fdae39aee3c..a0d6a43d6f776 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx @@ -20,6 +20,8 @@ import React from 'react'; import { render } from 'enzyme'; import { RouteComponentProps } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; +import { scopedHistoryMock } from '../../../../../../../../core/public/mocks'; import { Header } from './header'; @@ -28,7 +30,7 @@ describe('Header', () => { const component = render( diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx index dc48f61d1aa65..e432b9b466367 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx @@ -22,9 +22,13 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ScopedHistory } from 'kibana/public'; + +import { reactRouterNavigate } from '../../../../../../../kibana_react/public'; interface HeaderProps extends RouteComponentProps { indexPatternId: string; + history: ScopedHistory; } export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => ( @@ -52,9 +56,7 @@ export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => ( { - history.push(`${indexPatternId}/create-field/`); - }} + {...reactRouterNavigate(history, `patterns/${indexPatternId}/create-field/`)} > field.lang === this.props.scriptedFieldLanguageFilter + (field) => field.lang === this.props.scriptedFieldLanguageFilter ); } @@ -104,7 +104,7 @@ export class ScriptedFieldsTable extends Component< if (fieldFilter) { const normalizedFieldFilter = fieldFilter.toLowerCase(); - filteredFields = languageFilteredFields.filter(field => + filteredFields = languageFilteredFields.filter((field) => field.name.toLowerCase().includes(normalizedFieldFilter) ); } @@ -151,7 +151,7 @@ export class ScriptedFieldsTable extends Component<
this.props.helpers.redirectToRoute(field)} + editField={(field) => this.props.helpers.redirectToRoute(field)} deleteField={this.startDeleteField} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx index 2a5e29827ccc5..1d840743065a1 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx @@ -49,7 +49,7 @@ export const AddFilter = ({ onAddFilter }: AddFilterProps) => { setFilter(e.target.value.trim())} + onChange={(e) => setFilter(e.target.value.trim())} placeholder={sourcePlaceholder} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx index 421b5b67c2288..ab5a253a98e29 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx @@ -103,10 +103,7 @@ describe('Table', () => { // Wrap in a div because: https://github.com/airbnb/enzyme/issues/1213
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); - editingComponent - .find('EuiButtonIcon') - .at(1) - .simulate('click'); + editingComponent.find('EuiButtonIcon').at(1).simulate('click'); // Ensure the state change propagates component.update(); @@ -123,10 +120,7 @@ describe('Table', () => {
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); - editingComponent - .find('EuiButtonIcon') - .at(1) - .simulate('click'); + editingComponent.find('EuiButtonIcon').at(1).simulate('click'); // Ensure the state change propagates component.update(); @@ -159,10 +153,7 @@ describe('Table', () => {
{localComponent.prop('columns')[2].render({ clientId, value: 'tim*' })}
); - editingComponent - .find('EuiButtonIcon') - .at(1) - .simulate('click'); + editingComponent.find('EuiButtonIcon').at(1).simulate('click'); // Update the value localComponent.setState({ editingFilterValue: 'time*' }); @@ -191,10 +182,7 @@ describe('Table', () => {
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); - editingComponent - .find('EuiButtonIcon') - .at(0) - .simulate('click'); + editingComponent.find('EuiButtonIcon').at(0).simulate('click'); editingComponent.update(); @@ -228,10 +216,7 @@ describe('Table', () => { // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`.
{component.prop('columns')[2].render({ clientId: 1, value: 'tim*' })}
); - deleteCellComponent - .find('EuiButtonIcon') - .at(1) - .simulate('click'); + deleteCellComponent.find('EuiButtonIcon').at(1).simulate('click'); expect(deleteFilter).toBeCalled(); }); @@ -254,10 +239,7 @@ describe('Table', () => { // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`.
{component.prop('columns')[2].render({ clientId: 1, value: 'tim*' })}
); - editingComponent - .find('EuiButtonIcon') - .at(0) - .simulate('click'); + editingComponent.find('EuiButtonIcon').at(0).simulate('click'); component.update(); @@ -295,10 +277,7 @@ describe('Table', () => {
{component.prop('columns')[2].render({ clientId: 1, value: 'tim*' })}
); - editingComponent - .find('EuiButtonIcon') - .at(0) - .simulate('click'); + editingComponent.find('EuiButtonIcon').at(0).simulate('click'); // Ensure the state change propagates component.update(); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx index ccdda3915e979..e5c753886ea9f 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx @@ -80,7 +80,7 @@ export class SourceFiltersTable extends Component< (filters, filterFilter) => { if (filterFilter) { const filterFilterToLowercase = filterFilter.toLowerCase(); - return filters.filter(filter => + return filters.filter((filter) => filter.value.toLowerCase().includes(filterFilterToLowercase) ); } @@ -107,7 +107,7 @@ export class SourceFiltersTable extends Component< const { indexPattern, onAddOrRemoveFilter } = this.props; const { filterToDelete, filters } = this.state; - indexPattern.sourceFilters = filters.filter(filter => { + indexPattern.sourceFilters = filters.filter((filter) => { return filter.clientId !== filterToDelete.clientId; }); @@ -143,7 +143,7 @@ export class SourceFiltersTable extends Component< const { indexPattern } = this.props; const { filters } = this.state; - indexPattern.sourceFilters = filters.map(filter => { + indexPattern.sourceFilters = filters.map((filter) => { if (filter.clientId === clientId) { return { value, diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index 5cf7fd9b2af06..a59dca80a3684 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -31,9 +31,13 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { fieldWildcardMatcher } from '../../../../../kibana_utils/public'; -import { IndexPatternManagementStart } from '../../../../../index_pattern_management/public'; -import { IndexPattern, IndexPatternField } from '../../../../../data/public'; -import { META_FIELDS_SETTING } from '../../../../../data/common'; +import { + IndexPattern, + IndexPatternField, + UI_SETTINGS, +} from '../../../../../../plugins/data/public'; +import { useKibana } from '../../../../../../plugins/kibana_react/public'; +import { IndexPatternManagmentContext } from '../../../types'; import { createEditIndexPatternPageStateContainer } from '../edit_index_pattern_state_container'; import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from '../constants'; import { SourceFiltersTable } from '../source_filters_table'; @@ -43,12 +47,7 @@ import { getTabs, getPath, convertToEuiSelectOption } from './utils'; interface TabsProps extends Pick { indexPattern: IndexPattern; - config: Record; fields: IndexPatternField[]; - services: { - indexPatternManagement: IndexPatternManagementStart; - painlessDocLink: string; - }; } const searchAriaLabel = i18n.translate( @@ -72,7 +71,10 @@ const filterPlaceholder = i18n.translate( } ); -export function Tabs({ config, indexPattern, fields, services, history, location }: TabsProps) { +export function Tabs({ indexPattern, fields, history, location }: TabsProps) { + const { uiSettings, indexPatternManagementStart, docLinks } = useKibana< + IndexPatternManagmentContext + >().services; const [fieldFilter, setFieldFilter] = useState(''); const [indexedFieldTypeFilter, setIndexedFieldTypeFilter] = useState(''); const [scriptedFieldLanguageFilter, setScriptedFieldLanguageFilter] = useState(''); @@ -85,7 +87,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location const refreshFilters = useCallback(() => { const tempIndexedFieldTypes: string[] = []; const tempScriptedFieldLanguages: string[] = []; - indexPattern.fields.forEach(field => { + indexPattern.fields.forEach((field) => { if (field.scripted) { if (field.lang) { tempScriptedFieldLanguages.push(field.lang); @@ -106,8 +108,8 @@ export function Tabs({ config, indexPattern, fields, services, history, location }, [indexPattern, indexPattern.fields, refreshFilters]); const fieldWildcardMatcherDecorated = useCallback( - (filters: string[]) => fieldWildcardMatcher(filters, config.get(META_FIELDS_SETTING)), - [config] + (filters: string[]) => fieldWildcardMatcher(filters, uiSettings.get(UI_SETTINGS.META_FIELDS)), + [uiSettings] ); const getFilterSection = useCallback( @@ -118,7 +120,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location setFieldFilter(e.target.value)} + onChange={(e) => setFieldFilter(e.target.value)} data-test-subj="indexPatternFieldFilter" aria-label={searchAriaLabel} /> @@ -128,7 +130,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location setIndexedFieldTypeFilter(e.target.value)} + onChange={(e) => setIndexedFieldTypeFilter(e.target.value)} data-test-subj="indexedFieldTypeFilterDropdown" aria-label={filterAriaLabel} /> @@ -139,7 +141,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location setScriptedFieldLanguageFilter(e.target.value)} + onChange={(e) => setScriptedFieldLanguageFilter(e.target.value)} data-test-subj="scriptedFieldLanguageFilterDropdown" /> @@ -175,7 +177,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location redirectToRoute: (field: IndexPatternField) => { history.push(getPath(field)); }, - getFieldInfo: services.indexPatternManagement.list.getFieldInfo, + getFieldInfo: indexPatternManagementStart.list.getFieldInfo, }} /> @@ -196,7 +198,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location }, }} onRemoveField={refreshFilters} - painlessDocLink={services.painlessDocLink} + painlessDocLink={docLinks.links.scriptedFields.painless} /> ); @@ -217,23 +219,23 @@ export function Tabs({ config, indexPattern, fields, services, history, location } }, [ + docLinks.links.scriptedFields.painless, fieldFilter, fieldWildcardMatcherDecorated, fields, getFilterSection, history, indexPattern, + indexPatternManagementStart.list.getFieldInfo, indexedFieldTypeFilter, refreshFilters, scriptedFieldLanguageFilter, - services.indexPatternManagement.list.getFieldInfo, - services.painlessDocLink, ] ); const euiTabs: EuiTabbedContentTab[] = useMemo( () => - getTabs(indexPattern, fieldFilter, services.indexPatternManagement.list).map( + getTabs(indexPattern, fieldFilter, indexPatternManagementStart.list).map( (tab: Pick) => { return { ...tab, @@ -241,7 +243,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location }; } ), - [fieldFilter, getContent, indexPattern, services.indexPatternManagement.list] + [fieldFilter, getContent, indexPattern, indexPatternManagementStart.list] ); const [selectedTabId, setSelectedTabId] = useState(euiTabs[0].id); @@ -253,7 +255,7 @@ export function Tabs({ config, indexPattern, fields, services, history, location setCurrentTab, getCurrentTab, } = createEditIndexPatternPageStateContainer({ - useHashedUrl: config.get('state:storeInSessionStorage'), + useHashedUrl: uiSettings.get('state:storeInSessionStorage'), defaultTab: TAB_INDEXED_FIELDS, }); @@ -267,13 +269,13 @@ export function Tabs({ config, indexPattern, fields, services, history, location return () => { stopSyncingState(); }; - }, [config]); + }, [uiSettings]); return ( tab.id === selectedTabId)} - onTabClick={tab => { + selectedTab={euiTabs.find((tab) => tab.id === selectedTabId)} + onTabClick={(tab) => { setSelectedTabId(tab.id); syncingStateFunc.setCurrentTab(tab.id); }} diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts index b9b59142290dc..52cd5b0c3f5bd 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts @@ -25,7 +25,7 @@ import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from '../ function filterByName(items: IndexPatternField[], filter: string) { const lowercaseFilter = (filter || '').toLowerCase(); - return items.filter(item => item.name.toLowerCase().includes(lowercaseFilter)); + return items.filter((item) => item.name.toLowerCase().includes(lowercaseFilter)); } function getCounts( @@ -35,7 +35,7 @@ function getCounts( }, fieldFilter = '' ) { - const fieldCount = countBy(filterByName(fields, fieldFilter), function(field) { + const fieldCount = countBy(filterByName(fields, fieldFilter), function (field) { return field.scripted ? 'scripted' : 'indexed'; }); @@ -43,7 +43,7 @@ function getCounts( indexed: 0, scripted: 0, sourceFilters: sourceFilters.excludes - ? sourceFilters.excludes.filter(value => + ? sourceFilters.excludes.filter((value) => value.toLowerCase().includes(fieldFilter.toLowerCase()) ).length : 0, @@ -117,7 +117,7 @@ export function getTabs( } export function getPath(field: IndexPatternField) { - return `${field.indexPattern?.id}/field/${field.name}`; + return `/patterns/${field.indexPattern?.id}/field/${field.name}`; } const allTypesDropDown = i18n.translate( @@ -145,7 +145,7 @@ export function convertToEuiSelectOption(options: string[], type: string) { ] : []; return euiOptions.concat( - unique(options).map(option => { + unique(options).map((option) => { return { value: option, text: option, diff --git a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap index a7ed4e1c9cafd..6bc99c356592e 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap @@ -19,14 +19,10 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` isVisible={false} /> , "painlessLink": { + onChange={(e) => { this.onColorChange( { regex: e.target.value, @@ -129,7 +129,7 @@ export class ColorFormatEditor extends DefaultFormatEditor { + onChange={(e) => { this.onColorChange( { range: e.target.value, @@ -153,7 +153,7 @@ export class ColorFormatEditor extends DefaultFormatEditor { + onChange={(newColor) => { this.onColorChange( { text: newColor, @@ -177,7 +177,7 @@ export class ColorFormatEditor extends DefaultFormatEditor { + onChange={(newColor) => { this.onColorChange( { background: newColor, diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.tsx index 55860707a3ee9..e75750210d9c5 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.tsx @@ -37,12 +37,8 @@ export class DateFormatEditor extends DefaultFormatEditor { + onChange={(e) => { this.onChange({ pattern: e.target.value }); }} isInvalid={!!error} diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.tsx index 25152370ac928..4c5227ae79118 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.tsx @@ -77,7 +77,7 @@ export class DateNanosFormatEditor extends DefaultFormatEditor { + onChange={(e) => { this.onChange({ pattern: e.target.value }); }} isInvalid={!!error} diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.tsx index 68ece63c2efbe..de3038c3fd4f5 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.tsx @@ -34,7 +34,7 @@ export const convertSampleInput = ( let samples: Sample[] = []; try { - samples = inputs.map(input => { + samples = inputs.map((input) => { return { input, output: converter(input), diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.tsx index cdda56b4f1f51..bf25df069435f 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.tsx @@ -116,7 +116,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< text: fmt.text, }; })} - onChange={e => { + onChange={(e) => { this.onChange({ inputFormat: e.target.value }); }} isInvalid={!!error} @@ -139,7 +139,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< text: fmt.text, }; })} - onChange={e => { + onChange={(e) => { this.onChange({ outputFormat: e.target.value }); }} isInvalid={!!error} @@ -160,7 +160,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< value={formatParams.outputPrecision} min={0} max={20} - onChange={e => { + onChange={(e) => { this.onChange({ outputPrecision: e.target.value ? Number(e.target.value) : null }); }} isInvalid={!!error} diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.tsx index 05771afdd33e2..62c88b195f8b6 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.tsx @@ -70,7 +70,7 @@ export class NumberFormatEditor extends DefaultFormatEditor { + onChange={(e) => { this.onChange({ pattern: e.target.value }); }} isInvalid={!!error} diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.tsx index fbbbb22e3d18f..0f80bfa681bf3 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.tsx @@ -93,7 +93,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor< return ( { + onChange={(e) => { this.onLookupChange( { key: e.target.value, @@ -117,7 +117,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor< return ( { + onChange={(e) => { this.onLookupChange( { value: e.target.value, @@ -182,7 +182,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor< defaultMessage: 'Leave blank to keep value as-is', } )} - onChange={e => { + onChange={(e) => { this.onChange({ unknownKeyValue: e.target.value }); }} /> diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.tsx index d1befb77bae64..7a3bb6f5cd398 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.tsx @@ -74,7 +74,7 @@ export class StringFormatEditor extends DefaultFormatEditor { + onChange={(e) => { this.onChange({ transform: e.target.value }); }} isInvalid={!!error} diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.tsx index 9b4d5a0f033a9..3881940a78627 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.tsx @@ -58,7 +58,7 @@ export class TruncateFormatEditor extends DefaultFormatEditor { + onChange={(e) => { if (e.target.checkValidity()) { this.onChange({ fieldLength: e.target.value ? Number(e.target.value) : null, diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx index 5b706f1627bce..30acf09526f85 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx @@ -151,7 +151,7 @@ export class UrlFormatEditor extends DefaultFormatEditor< { + onChange={(e) => { this.onChange({ width: e.target.value }); }} /> @@ -164,7 +164,7 @@ export class UrlFormatEditor extends DefaultFormatEditor< { + onChange={(e) => { this.onChange({ height: e.target.value }); }} /> @@ -201,7 +201,7 @@ export class UrlFormatEditor extends DefaultFormatEditor< text: type.text, }; })} - onChange={e => { + onChange={(e) => { this.onTypeChange(e.target.value); }} /> @@ -225,7 +225,7 @@ export class UrlFormatEditor extends DefaultFormatEditor< ) } checked={!formatParams.openLinkInCurrentTab} - onChange={e => { + onChange={(e) => { this.onChange({ openLinkInCurrentTab: !e.target.checked }); }} /> @@ -253,7 +253,7 @@ export class UrlFormatEditor extends DefaultFormatEditor< { + onChange={(e) => { this.onChange({ urlTemplate: e.target.value }); }} /> @@ -280,7 +280,7 @@ export class UrlFormatEditor extends DefaultFormatEditor< { + onChange={(e) => { this.onChange({ labelTemplate: e.target.value }); }} /> diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_call_outs/__snapshots__/warning_call_out.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_call_outs/__snapshots__/warning_call_out.test.tsx.snap index c9b5c84939bc6..2652e6b8ddd04 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_call_outs/__snapshots__/warning_call_out.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_call_outs/__snapshots__/warning_call_out.test.tsx.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ScriptingWarningCallOut should render normally 1`] = ` - + } > -

- +

+
{ public async updateBeatsData(beatsKBar?: string) { const beats = sortBy(await this.props.libs.beats.getAll(beatsKBar), 'id') || []; - const tags = await this.props.libs.tags.getTagsWithIds(flatten(beats.map(beat => beat.tags))); + const tags = await this.props.libs.tags.getTagsWithIds(flatten(beats.map((beat) => beat.tags))); this.setState({ tags, @@ -172,7 +172,7 @@ class BeatsPageComponent extends React.PureComponent { path={`/overview/enrolled_beats`} /> - {autocompleteProps => ( + {(autocompleteProps) => (
{ break; } }} - items={this.state.beats.map(beat => ({ + items={this.state.beats.map((beat) => ({ ...beat, - tags: (this.state.tags || []).filter(tag => beat.tags.includes(tag.id)), + tags: (this.state.tags || []).filter((tag) => beat.tags.includes(tag.id)), }))} ref={this.tableRef} type={BeatsTableType} @@ -348,7 +348,7 @@ class BeatsPageComponent extends React.PureComponent { const selectedIds = this.tableRef.current.state.selection.map((beat: any) => beat.id); const beats: CMBeat[] = []; selectedIds.forEach((id: any) => { - const beat = this.props.containers.beats.state.list.find(b => b.id === id); + const beat = this.props.containers.beats.state.list.find((b) => b.id === id); if (beat) { beats.push(beat); } diff --git a/x-pack/plugins/beats_management/public/pages/tag/create.tsx b/x-pack/plugins/beats_management/public/pages/tag/create.tsx index 65fe8bf5d52c1..881bb433b1d9a 100644 --- a/x-pack/plugins/beats_management/public/pages/tag/create.tsx +++ b/x-pack/plugins/beats_management/public/pages/tag/create.tsx @@ -72,7 +72,7 @@ class TagCreatePageComponent extends React.PureComponent< total: this.state.configuration_blocks.length, }} onTagChange={(field: string, value: string | number) => - this.setState(oldState => ({ + this.setState((oldState) => ({ tag: { ...oldState.tag, [field]: value }, })) } @@ -82,13 +82,13 @@ class TagCreatePageComponent extends React.PureComponent< }); }} onConfigAddOrEdit={(block: ConfigurationBlock) => { - this.setState(previousState => ({ + this.setState((previousState) => ({ configuration_blocks: previousState.configuration_blocks.concat([block]), })); }} onConfigRemoved={(block: ConfigurationBlock) => { - this.setState(previousState => { - const selectedIndex = previousState.configuration_blocks.findIndex(c => { + this.setState((previousState) => { + const selectedIndex = previousState.configuration_blocks.findIndex((c) => { return isEqual(block, c); }); const blocks = [...previousState.configuration_blocks]; @@ -141,7 +141,7 @@ class TagCreatePageComponent extends React.PureComponent< ); } const createBlocksResponse = await this.props.libs.configBlocks.upsert( - this.state.configuration_blocks.map(block => ({ ...block, tag: this.state.tag.id })) + this.state.configuration_blocks.map((block) => ({ ...block, tag: this.state.tag.id })) ); const creationError = createBlocksResponse.results.reduce( (err: string, resp) => (!err ? (err = resp.error ? resp.error.message : '') : err), @@ -155,7 +155,7 @@ class TagCreatePageComponent extends React.PureComponent< }; private getNumExclusiveConfigurationBlocks = () => this.state.configuration_blocks - .map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some(uniqueType => uniqueType === type)) + .map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some((uniqueType) => uniqueType === type)) .reduce((acc, cur) => (cur ? acc + 1 : acc), 0); } diff --git a/x-pack/plugins/beats_management/public/pages/tag/edit.tsx b/x-pack/plugins/beats_management/public/pages/tag/edit.tsx index 918ae0bb6fcb2..10d7f7bbd7193 100644 --- a/x-pack/plugins/beats_management/public/pages/tag/edit.tsx +++ b/x-pack/plugins/beats_management/public/pages/tag/edit.tsx @@ -88,15 +88,15 @@ class TagEditPageComponent extends React.PureComponent< await this.loadAttachedBeats(); }} onTagChange={(field: string, value: string | number) => - this.setState(oldState => ({ + this.setState((oldState) => ({ tag: { ...oldState.tag, [field]: value }, })) } attachedBeats={ - (this.state.attachedBeats || []).map(beat => ({ + (this.state.attachedBeats || []).map((beat) => ({ ...beat, tags: flatten( - beat.tags.map(tagId => this.state.beatsTags.filter(tag => tag.id === tagId)) + beat.tags.map((tagId) => this.state.beatsTags.filter((tag) => tag.id === tagId)) ), })) as any } @@ -186,7 +186,7 @@ class TagEditPageComponent extends React.PureComponent< private loadAttachedBeats = async () => { const beats = await this.props.libs.beats.getBeatsWithTag(this.props.match.params.tagid); const beatsTags = await this.props.libs.tags.getTagsWithIds( - flatten(beats.map(beat => beat.tags)) + flatten(beats.map((beat) => beat.tags)) ); this.setState({ attachedBeats: beats, @@ -199,7 +199,7 @@ class TagEditPageComponent extends React.PureComponent< }; private getNumExclusiveConfigurationBlocks = () => this.state.configuration_blocks.list - .map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some(uniqueType => uniqueType === type)) + .map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some((uniqueType) => uniqueType === type)) .reduce((acc, cur) => (cur ? acc + 1 : acc), 0); } diff --git a/x-pack/plugins/beats_management/public/pages/walkthrough/initial/index.tsx b/x-pack/plugins/beats_management/public/pages/walkthrough/initial/index.tsx index a78bf3fa7cf8e..5b307692e8a4c 100644 --- a/x-pack/plugins/beats_management/public/pages/walkthrough/initial/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/walkthrough/initial/index.tsx @@ -17,7 +17,7 @@ type Props = AppPageProps & { intl: InjectedIntl; }; -const InitialWalkthroughPageComponent: React.FC = props => { +const InitialWalkthroughPageComponent: React.FC = (props) => { if (props.location.pathname === '/walkthrough/initial') { return ( { total: this.state.configuration_blocks.length, }} onTagChange={(field: string, value: string | number) => - this.setState(oldState => ({ + this.setState((oldState) => ({ tag: { ...oldState.tag, [field]: value }, })) } @@ -63,13 +63,13 @@ export class InitialTagPage extends Component { }); }} onConfigAddOrEdit={(block: ConfigurationBlock) => { - this.setState(previousState => ({ + this.setState((previousState) => ({ configuration_blocks: previousState.configuration_blocks.concat([block]), })); }} onConfigRemoved={(block: ConfigurationBlock) => { - this.setState(previousState => { - const selectedIndex = previousState.configuration_blocks.findIndex(c => { + this.setState((previousState) => { + const selectedIndex = previousState.configuration_blocks.findIndex((c) => { return isEqual(block, c); }); const blocks = [...previousState.configuration_blocks]; @@ -121,7 +121,7 @@ export class InitialTagPage extends Component { ); } const createBlocksResponse = await this.props.libs.configBlocks.upsert( - this.state.configuration_blocks.map(block => ({ ...block, tag: this.state.tag.id })) + this.state.configuration_blocks.map((block) => ({ ...block, tag: this.state.tag.id })) ); const creationError = createBlocksResponse.results.reduce( (err: string, resp) => (!err ? (err = resp.error ? resp.error.message : '') : err), diff --git a/x-pack/plugins/beats_management/public/router.tsx b/x-pack/plugins/beats_management/public/router.tsx index ec7aceaa6d747..c23d0dd4aca99 100644 --- a/x-pack/plugins/beats_management/public/router.tsx +++ b/x-pack/plugins/beats_management/public/router.tsx @@ -62,7 +62,7 @@ export class AppRouter extends Component { {/* License check (UI displays when license exists but is expired) */} {get(this.props.libs.framework.info, 'license.expired', true) && ( + render={(props) => !props.location.pathname.includes('/error') ? ( ) : null @@ -73,7 +73,7 @@ export class AppRouter extends Component { {/* Ensure security is eanabled for elastic and kibana */} {!get(this.props.libs.framework.info, 'security.enabled', true) && ( + render={(props) => !props.location.pathname.includes('/error') ? ( ) : null @@ -86,7 +86,7 @@ export class AppRouter extends Component { ['beats_admin'].concat(this.props.libs.framework.info.settings.defaultUserRoles) ) && ( + render={(props) => !props.location.pathname.includes('/error') ? ( ) : null @@ -97,7 +97,7 @@ export class AppRouter extends Component { {/* If there are no beats or tags yet, redirect to the walkthrough */} {countOfEverything === 0 && ( + render={(props) => !props.location.pathname.includes('/walkthrough') ? ( ) : null diff --git a/x-pack/plugins/beats_management/public/utils/random_eui_color.ts b/x-pack/plugins/beats_management/public/utils/random_eui_color.ts index 5c358941f4bc3..edc3e2863a89a 100644 --- a/x-pack/plugins/beats_management/public/utils/random_eui_color.ts +++ b/x-pack/plugins/beats_management/public/utils/random_eui_color.ts @@ -8,8 +8,8 @@ import { sample } from 'lodash'; export const randomEUIColor = (euiVars: any) => { const rgb = sample( Object.keys(euiVars) - .filter(key => key.startsWith('euiColorVis')) - .map(key => (euiVars as any)[key]) + .filter((key) => key.startsWith('euiColorVis')) + .map((key) => (euiVars as any)[key]) ); const matchedrgb = rgb.match( diff --git a/x-pack/plugins/canvas/__tests__/fixtures/function_specs.ts b/x-pack/plugins/canvas/__tests__/fixtures/function_specs.ts index edf11f5a25c08..09b5e29cba87e 100644 --- a/x-pack/plugins/canvas/__tests__/fixtures/function_specs.ts +++ b/x-pack/plugins/canvas/__tests__/fixtures/function_specs.ts @@ -7,4 +7,4 @@ import { functions as browserFns } from '../../canvas_plugin_src/functions/browser'; import { ExpressionFunction } from '../../../../../src/plugins/expressions'; -export const functionSpecs = browserFns.map(fn => new ExpressionFunction(fn())); +export const functionSpecs = browserFns.map((fn) => new ExpressionFunction(fn())); diff --git a/x-pack/plugins/canvas/__tests__/fixtures/kibana.js b/x-pack/plugins/canvas/__tests__/fixtures/kibana.js index 4503adcec0c2c..e32041cd99f1a 100644 --- a/x-pack/plugins/canvas/__tests__/fixtures/kibana.js +++ b/x-pack/plugins/canvas/__tests__/fixtures/kibana.js @@ -24,10 +24,10 @@ export class Plugin { elasticsearch: mockElasticsearch, }, config: () => ({ - get: key => get(config, key), - has: key => has(config, key), + get: (key) => get(config, key), + has: (key) => has(config, key), }), - route: def => this.routes.push(def), + route: (def) => this.routes.push(def), }; const { init } = this.props; diff --git a/x-pack/plugins/canvas/__tests__/helpers/function_wrapper.js b/x-pack/plugins/canvas/__tests__/helpers/function_wrapper.js index 4f078169f699f..dc07932e70e0f 100644 --- a/x-pack/plugins/canvas/__tests__/helpers/function_wrapper.js +++ b/x-pack/plugins/canvas/__tests__/helpers/function_wrapper.js @@ -9,7 +9,7 @@ import { mapValues } from 'lodash'; // It takes a function spec and passes in default args into the spec fn export const functionWrapper = (fnSpec, mockReduxStore) => { const spec = fnSpec(); - const defaultArgs = mapValues(spec.args, argSpec => { + const defaultArgs = mapValues(spec.args, (argSpec) => { return argSpec.default; }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts index ec3b8a7798be1..961b0cd034248 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts @@ -66,7 +66,7 @@ const initializeElementFactories = [metricElementInitializer]; export const initializeElements: SetupInitializer = (core, plugins) => { const specs = [ ...elementSpecs, - ...initializeElementFactories.map(factory => factory(core, plugins)), + ...initializeElementFactories.map((factory) => factory(core, plugins)), ]; return applyElementStrings(specs); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts index 7256657903aab..14409ae166a84 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts @@ -7,6 +7,7 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; import { SetupInitializer } from '../../plugin'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; export const metricElementInitializer: SetupInitializer = (core, setup) => { return () => ({ @@ -23,7 +24,7 @@ export const metricElementInitializer: SetupInitializer = (core, | metric "Countries" metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48} labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"} - metricFormat="${core.uiSettings.get('format:number:defaultPattern')}" + metricFormat="${core.uiSettings.get(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN)}" | render`, }); }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts b/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts index 74de223b9fcb7..cb2f418537a33 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts @@ -12,9 +12,23 @@ export const EmbeddableExpressionType = 'embeddable'; export { EmbeddableTypes, EmbeddableInput }; export interface EmbeddableExpression { + /** + * The type of the expression result + */ type: typeof EmbeddableExpressionType; + /** + * The input to be passed to the embeddable + */ input: Input; + /** + * The type of embeddable + */ embeddableType: string; + /** + * Timestamp. Needed to get a different result after each time the expression is evaluated + * to force a reload of the embeddables internal data + */ + generatedAt: number; } export const embeddableType = (): ExpressionTypeDefinition< diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/location.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/location.ts index 1e13ebdee3e4b..4a01df3b0ac50 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/location.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/location.ts @@ -24,7 +24,7 @@ export function location(): ExpressionFunctionDefinition<'location', null, {}, P args: {}, help, fn: () => { - return new Promise(resolve => { + return new Promise((resolve) => { function createLocation(geoposition: Position) { const { latitude, longitude } = geoposition.coords; return resolve({ diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js index 27ea290fb4dcc..71b6af6739408 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js @@ -31,7 +31,7 @@ describe('markdown', () => { it('compiles and concatenates handlebars expressions using context', () => { let expectedContent = 'Columns:'; - testTable.columns.map(col => (expectedContent += ` ${col.name}`)); + testTable.columns.map((col) => (expectedContent += ` ${col.name}`)); const result = fn(testTable, { content: ['Columns:', '{{#each columns}} {{name}}{{/each}}'], diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts index e94b9f201a174..41323a82f4ee0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts @@ -62,7 +62,7 @@ export function markdown(): ExpressionFunctionDefinition< }, }, fn: (input, args) => { - const compileFunctions = args.content.map(str => + const compileFunctions = args.content.map((str) => Handlebars.compile(String(str), { knownHelpersOnly: true }) ); const ctx = { @@ -76,7 +76,7 @@ export function markdown(): ExpressionFunctionDefinition< type: 'render', as: 'markdown', value: { - content: compileFunctions.map(fn => fn(ctx)).join(''), + content: compileFunctions.map((fn) => fn(ctx)).join(''), font: args.font, openLinksInNewTab: args.openLinksInNewTab, }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js index d53fe94d4a838..17f9defa15dc3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js @@ -18,9 +18,7 @@ describe('progress', () => { it('returns a render as progress', () => { const result = fn(0.2); - expect(result) - .to.have.property('type', 'render') - .and.to.have.property('as', 'progress'); + expect(result).to.have.property('type', 'render').and.to.have.property('as', 'progress'); }); it('sets the progress to context', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts index e6739a71b1608..68c1957c808a3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts @@ -51,7 +51,7 @@ export function alterColumn(): ExpressionFunctionDefinition< return input; } - const column = input.columns.find(col => col.name === args.column); + const column = input.columns.find((col) => col.name === args.column); if (!column) { throw errors.columnNotFound(args.column); } @@ -94,7 +94,7 @@ export function alterColumn(): ExpressionFunctionDefinition< })(); } - const rows = input.rows.map(row => ({ + const rows = input.rows.map((row) => ({ ...omit(row, column.name), [name]: handler(row[column.name]), })); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.ts index 71c5376428a79..63d3102a19e9a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.ts @@ -43,27 +43,27 @@ export function columns(): ExpressionFunctionDefinition< let result = { ...input }; if (exclude) { - const fields = exclude.split(',').map(field => field.trim()); - const cols = contextColumns.filter(col => !fields.includes(col.name)); - const rows = cols.length > 0 ? contextRows.map(row => omit(row, fields)) : []; + const fields = exclude.split(',').map((field) => field.trim()); + const cols = contextColumns.filter((col) => !fields.includes(col.name)); + const rows = cols.length > 0 ? contextRows.map((row) => omit(row, fields)) : []; result = { rows, columns: cols, ...rest }; } if (include) { - const fields = include.split(',').map(field => field.trim()); + const fields = include.split(',').map((field) => field.trim()); // const columns = result.columns.filter(col => fields.includes(col.name)); // Include columns in the order the user specified const cols: DatatableColumn[] = []; - fields.forEach(field => { + fields.forEach((field) => { const column = find(result.columns, { name: field }); if (column) { cols.push(column); } }); - const rows = cols.length > 0 ? result.rows.map(row => pick(row, fields)) : []; + const rows = cols.length > 0 ? result.rows.map((row) => pick(row, fields)) : []; result = { rows, columns: cols, ...rest }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.ts index d1302a1e579a1..bc87d6793e2f0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.ts @@ -14,6 +14,6 @@ export function context(): ExpressionFunctionDefinition<'context', unknown, {}, name: 'context', help, args: {}, - fn: obj => obj, + fn: (obj) => obj, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.ts index 5f0c848d76708..0fe45243ce721 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.ts @@ -23,6 +23,6 @@ export function doFn(): ExpressionFunctionDefinition<'do', unknown, Arguments, u help: argHelp.fn, }, }, - fn: context => context, + fn: (context) => context, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts index 29a277283494a..7231f01671e02 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts @@ -53,7 +53,7 @@ export function dropdownControl(): ExpressionFunctionDefinition< let choices = []; if (input.rows[0][valueColumn]) { - choices = uniq(input.rows.map(row => row[valueColumn])).sort(); + choices = uniq(input.rows.map((row) => row[valueColumn])).sort(); } const column = filterColumn || valueColumn; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js index 2f44655f60e44..179be8aff2e19 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js @@ -8,29 +8,29 @@ import { functionWrapper } from '../../../__tests__/helpers/function_wrapper'; import { testTable } from './__tests__/fixtures/test_tables'; import { filterrows } from './filterrows'; -const inStock = datatable => datatable.rows[0].in_stock; +const inStock = (datatable) => datatable.rows[0].in_stock; const returnFalse = () => false; describe('filterrows', () => { const fn = functionWrapper(filterrows); it('returns a datable', () => { - return fn(testTable, { fn: inStock }).then(result => { + return fn(testTable, { fn: inStock }).then((result) => { expect(result).toHaveProperty('type', 'datatable'); }); }); it('keeps rows that evaluate to true and removes rows that evaluate to false', () => { - const inStockRows = testTable.rows.filter(row => row.in_stock); + const inStockRows = testTable.rows.filter((row) => row.in_stock); - return fn(testTable, { fn: inStock }).then(result => { + return fn(testTable, { fn: inStock }).then((result) => { expect(result.columns).toEqual(testTable.columns); expect(result.rows).toEqual(inStockRows); }); }); it('returns datatable with no rows when no rows meet function condition', () => { - return fn(testTable, { fn: returnFalse }).then(result => { + return fn(testTable, { fn: returnFalse }).then((result) => { expect(result.rows).toEqual([]); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts index 17d5211588238..1f8b9f4c238cf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts @@ -35,7 +35,7 @@ export function filterrows(): ExpressionFunctionDefinition< }, }, fn(input, { fn }) { - const checks = input.rows.map(row => + const checks = input.rows.map((row) => fn({ ...input, rows: [row], @@ -43,9 +43,9 @@ export function filterrows(): ExpressionFunctionDefinition< ); return Promise.all(checks) - .then(results => input.rows.filter((row, i) => results[i])) + .then((results) => input.rows.filter((row, i) => results[i])) .then( - rows => + (rows) => ({ ...input, rows, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts index 7f8a7b525180c..fd2b8c6e5d5e4 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts @@ -56,7 +56,7 @@ export function joinRows(): ExpressionFunctionDefinition<'joinRows', Datatable, }, }, fn: (input, { column, separator, quote, distinct }) => { - const columnMatch = input.columns.find(col => col.name === column); + const columnMatch = input.columns.find((col) => col.name === column); if (!columnMatch) { throw errors.columnNotFound(column); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js index b170ebafd61c0..e5ef06d1503ee 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js @@ -8,13 +8,13 @@ import { functionWrapper } from '../../../__tests__/helpers/function_wrapper'; import { testTable, emptyTable } from './__tests__/fixtures/test_tables'; import { mapColumn } from './mapColumn'; -const pricePlusTwo = datatable => Promise.resolve(datatable.rows[0].price + 2); +const pricePlusTwo = (datatable) => Promise.resolve(datatable.rows[0].price + 2); describe('mapColumn', () => { const fn = functionWrapper(mapColumn); it('returns a datatable with a new column with the values from mapping a function over each row in a datatable', () => { - return fn(testTable, { name: 'pricePlusTwo', expression: pricePlusTwo }).then(result => { + return fn(testTable, { name: 'pricePlusTwo', expression: pricePlusTwo }).then((result) => { const arbitraryRowIndex = 2; expect(result.type).toBe('datatable'); @@ -28,7 +28,7 @@ describe('mapColumn', () => { }); it('overwrites existing column with the new column if an existing column name is provided', () => { - return fn(testTable, { name: 'name', expression: pricePlusTwo }).then(result => { + return fn(testTable, { name: 'name', expression: pricePlusTwo }).then((result) => { const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); const arbitraryRowIndex = 4; @@ -41,7 +41,7 @@ describe('mapColumn', () => { }); it('adds a column to empty tables', () => { - return fn(emptyTable, { name: 'name', expression: pricePlusTwo }).then(result => { + return fn(emptyTable, { name: 'name', expression: pricePlusTwo }).then((result) => { expect(result.type).toBe('datatable'); expect(result.columns).toHaveLength(1); expect(result.columns[0]).toHaveProperty('name', 'name'); @@ -51,7 +51,7 @@ describe('mapColumn', () => { describe('expression', () => { it('maps null values to the new column', () => { - return fn(testTable, { name: 'empty' }).then(result => { + return fn(testTable, { name: 'empty' }).then((result) => { const emptyColumnIndex = result.columns.findIndex(({ name }) => name === 'empty'); const arbitraryRowIndex = 8; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts index d8b15a65252e6..7dd309cba5c64 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts @@ -45,18 +45,18 @@ export function mapColumn(): ExpressionFunctionDefinition< const expression = args.expression || (() => Promise.resolve(null)); const columns = [...input.columns]; - const rowPromises = input.rows.map(row => { + const rowPromises = input.rows.map((row) => { return expression({ type: 'datatable', columns, rows: [row], - }).then(val => ({ + }).then((val) => ({ ...row, [args.name]: val, })); }); - return Promise.all(rowPromises).then(rows => { + return Promise.all(rowPromises).then((rows) => { const existingColumnIndex = columns.findIndex(({ name }) => name === args.name); const type = rows.length ? getType(rows[0][args.name]) : 'null'; const newColumn = { name: args.name, type }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts index dfbb37be0797c..7f84dc54d8092 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts @@ -43,7 +43,7 @@ export function math(): ExpressionFunctionDefinition<'math', Input, Arguments, n const mathContext = isDatatable(input) ? pivotObjectArray( input.rows, - input.columns.map(col => col.name) + input.columns.map((col) => col.name) ) : { value: input }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.test.js index f1cccd1d8dda4..4ef6583096213 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.test.js @@ -56,7 +56,7 @@ describe('pie', () => { describe('seriesStyle', () => { it('sets the color for a specific series', () => { const result = fn(testPie, { seriesStyle: [seriesStyle] }).value; - const seriesIndex = result.data.findIndex(series => series.label === seriesStyle.label); + const seriesIndex = result.data.findIndex((series) => series.label === seriesStyle.label); const resultSeries = result.data[seriesIndex]; expect(resultSeries).toHaveProperty('color', seriesStyle.color); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts index 36f1bf85b97e7..6cb64a43ea582 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts @@ -141,7 +141,7 @@ export function pie(): ExpressionFunctionDefinition<'pie', PointSeries, Argument const data: PieData[] = map(groupBy(input.rows, 'color'), (series, label = '') => { const item: PieData = { label, - data: series.map(point => point.size || 1), + data: series.map((point) => point.size || 1), }; const style = seriesStyles[label]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot.test.js index 6ce2978b75d56..d983cb7429863 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot.test.js @@ -66,7 +66,7 @@ describe('plot', () => { describe('seriesStyle', () => { it('sets the seriesStyle for a specific series', () => { const result = fn(testPlot, { seriesStyle: [seriesStyle] }).value; - const seriesIndex = result.data.findIndex(series => series.label === seriesStyle.label); + const seriesIndex = result.data.findIndex((series) => series.label === seriesStyle.label); const resultSeries = result.data[seriesIndex]; expect(resultSeries.lines).toHaveProperty('lineWidth', seriesStyle.lines); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts index 64fa1b259ade6..4839db047c871 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts @@ -20,7 +20,7 @@ export const getTickHash = (columns: PointSeriesColumns, rows: DatatableRow[]) = }; if (get(columns, 'x.type') === 'string') { - sortBy(rows, ['x']).forEach(row => { + sortBy(rows, ['x']).forEach((row) => { if (!ticks.x.hash[row.x]) { ticks.x.hash[row.x] = ticks.x.counter++; } @@ -30,7 +30,7 @@ export const getTickHash = (columns: PointSeriesColumns, rows: DatatableRow[]) = if (get(columns, 'y.type') === 'string') { sortBy(rows, ['y']) .reverse() - .forEach(row => { + .forEach((row) => { if (!ticks.y.hash[row.y]) { ticks.y.hash[row.y] = ticks.y.counter++; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts index 34e5d9f600d8d..e8214ca8eaf9f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts @@ -96,7 +96,7 @@ export function plot(): ExpressionFunctionDefinition<'plot', PointSeries, Argume return { ...flotStyle, label, - data: series.map(point => { + data: series.map((point) => { const attrs: { size?: number; text?: string; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js index 59c76e594a40a..2dfb9eeea76bc 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js @@ -11,7 +11,7 @@ import { ply } from './ply'; const errors = getFunctionErrors().ply; -const averagePrice = datatable => { +const averagePrice = (datatable) => { const average = datatable.rows.reduce((sum, row) => sum + row.price, 0) / datatable.rows.length; return Promise.resolve({ @@ -21,8 +21,8 @@ const averagePrice = datatable => { }); }; -const doublePrice = datatable => { - const newRows = datatable.rows.map(row => ({ double_price: row.price * 2 })); +const doublePrice = (datatable) => { + const newRows = datatable.rows.map((row) => ({ double_price: row.price * 2 })); return Promise.resolve({ type: 'datatable', @@ -31,7 +31,7 @@ const doublePrice = datatable => { }); }; -const rowCount = datatable => { +const rowCount = (datatable) => { return Promise.resolve({ type: 'datatable', columns: [{ name: 'row_count', type: 'number' }], @@ -50,7 +50,7 @@ describe('ply', () => { const arbitaryRowIndex = 0; return fn(testTable, { by: ['name', 'in_stock'], expression: [averagePrice, rowCount] }).then( - result => { + (result) => { expect(result.type).toBe('datatable'); expect(result.columns).toEqual([ { name: 'name', type: 'string' }, @@ -66,12 +66,12 @@ describe('ply', () => { describe('missing args', () => { it('returns the original datatable if both args are missing', () => { - return fn(testTable).then(result => expect(result).toEqual(testTable)); + return fn(testTable).then((result) => expect(result).toEqual(testTable)); }); describe('by', () => { it('passes the entire context into the expression when no columns are provided', () => { - return fn(testTable, { expression: [rowCount] }).then(result => + return fn(testTable, { expression: [rowCount] }).then((result) => expect(result).toEqual({ type: 'datatable', rows: [{ row_count: testTable.rows.length }], @@ -95,7 +95,7 @@ describe('ply', () => { it('returns the original datatable grouped by the specified columns', () => { const arbitaryRowIndex = 6; - return fn(testTable, { by: ['price', 'quantity'] }).then(result => { + return fn(testTable, { by: ['price', 'quantity'] }).then((result) => { expect(result.columns[0]).toHaveProperty('name', 'price'); expect(result.columns[1]).toHaveProperty('name', 'quantity'); expect(result.rows[arbitaryRowIndex]).toHaveProperty('price'); @@ -104,7 +104,7 @@ describe('ply', () => { }); it('throws when row counts do not match across resulting datatables', () => { - return fn(testTable, { by: ['name'], expression: [doublePrice, rowCount] }).catch(e => + return fn(testTable, { by: ['name'], expression: [doublePrice, rowCount] }).catch((e) => expect(e.message).toBe(errors.rowCountMismatch().message) ); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts index 391ff20461fb4..a541fbc89c634 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts @@ -47,8 +47,8 @@ export function ply(): ExpressionFunctionDefinition<'ply', Datatable, Arguments, let originalDatatables: Datatable[]; if (args.by) { - byColumns = args.by.map(by => { - const column = input.columns.find(col => col.name === by); + byColumns = args.by.map((by) => { + const column = input.columns.find((col) => col.name === by); if (!column) { throw errors.columnNotFound(by); @@ -57,9 +57,9 @@ export function ply(): ExpressionFunctionDefinition<'ply', Datatable, Arguments, return column; }); - const keyedDatatables = groupBy(input.rows, row => JSON.stringify(pick(row, args.by))); + const keyedDatatables = groupBy(input.rows, (row) => JSON.stringify(pick(row, args.by))); - originalDatatables = Object.values(keyedDatatables).map(rows => ({ + originalDatatables = Object.values(keyedDatatables).map((rows) => ({ ...input, rows, })); @@ -67,11 +67,11 @@ export function ply(): ExpressionFunctionDefinition<'ply', Datatable, Arguments, originalDatatables = [input]; } - const datatablePromises = originalDatatables.map(originalDatatable => { + const datatablePromises = originalDatatables.map((originalDatatable) => { let expressionResultPromises = []; if (args.expression) { - expressionResultPromises = args.expression.map(expression => + expressionResultPromises = args.expression.map((expression) => expression(originalDatatable) ); } else { @@ -81,13 +81,13 @@ export function ply(): ExpressionFunctionDefinition<'ply', Datatable, Arguments, return Promise.all(expressionResultPromises).then(combineAcross); }); - return Promise.all(datatablePromises).then(newDatatables => { + return Promise.all(datatablePromises).then((newDatatables) => { // Here we're just merging each for the by splits, so it doesn't actually matter if the rows are the same length const columns = combineColumns([byColumns].concat(map(newDatatables, 'columns'))); const rows = flatten( newDatatables.map((dt, i) => { const byColumnValues = pick(originalDatatables[i].rows[0], args.by); - return dt.rows.map(row => ({ + return dt.rows.map((row) => ({ ...byColumnValues, ...row, })); @@ -107,8 +107,8 @@ export function ply(): ExpressionFunctionDefinition<'ply', Datatable, Arguments, function combineColumns(arrayOfColumnsArrays: DatatableColumn[][]) { return arrayOfColumnsArrays.reduce((resultingColumns, columns) => { if (columns) { - columns.forEach(column => { - if (resultingColumns.find(resultingColumn => resultingColumn.name === column.name)) { + columns.forEach((column) => { + if (resultingColumns.find((resultingColumn) => resultingColumn.name === column.name)) { return; } else { resultingColumns.push(column); @@ -128,7 +128,7 @@ function combineAcross(datatableArray: Datatable[]) { const targetRowLength = referenceTable.rows.length; // Sanity check - datatableArray.forEach(datatable => { + datatableArray.forEach((datatable) => { if (datatable.rows.length !== targetRowLength) { throw errors.rowCountMismatch(); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts index d1027f784c9a9..d6425c7db8544 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts @@ -18,6 +18,6 @@ export function rowCount(): ExpressionFunctionDefinition<'rowCount', Datatable, inputTypes: ['datatable'], help, args: {}, - fn: input => input.rows.length, + fn: (input) => input.rows.length, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts index 8fc55ddf9cc59..49b8c5562af65 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts @@ -76,6 +76,7 @@ export function savedLens(): ExpressionFunctionDefinition< disableTriggers: true, }, embeddableType: EmbeddableTypes.lens, + generatedAt: Date.now(), }; }, }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts index dacdc30e0c6f7..faa2937aeaa14 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts @@ -98,6 +98,7 @@ export function savedMap(): ExpressionFunctionDefinition< hiddenLayers: args.hideLayer || [], }, embeddableType: EmbeddableTypes.map, + generatedAt: Date.now(), }; }, }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts index d54ef3097f830..0e00dbbb07c70 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts @@ -49,6 +49,7 @@ export function savedSearch(): ExpressionFunctionDefinition< ...buildEmbeddableFilters(filters), }, embeddableType: EmbeddableTypes.search, + generatedAt: Date.now(), }; }, }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts index 94c7a1c8a9eea..83663dd2a00ad 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts @@ -92,6 +92,7 @@ export function savedVisualization(): ExpressionFunctionDefinition< vis: visOptions, }, embeddableType: EmbeddableTypes.visualization, + generatedAt: Date.now(), }; }, }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js index e38bb76edabe2..ff11669db05f7 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js @@ -16,8 +16,8 @@ describe('staticColumn', () => { expect(result.type).toBe('datatable'); expect(result.columns).toEqual([...testTable.columns, { name: 'foo', type: 'string' }]); - expect(result.rows.every(row => typeof row.foo === 'string')).toBe(true); - expect(result.rows.every(row => row.foo === 'bar')).toBe(true); + expect(result.rows.every((row) => typeof row.foo === 'string')).toBe(true); + expect(result.rows.every((row) => row.foo === 'bar')).toBe(true); }); it('overwrites an existing column if provided an existing column name', () => { @@ -25,8 +25,8 @@ describe('staticColumn', () => { expect(result.type).toBe('datatable'); expect(result.columns).toEqual(testTable.columns); - expect(result.rows.every(row => typeof row.name === 'string')).toBe(true); - expect(result.rows.every(row => row.name === 'John')).toBe(true); + expect(result.rows.every((row) => typeof row.name === 'string')).toBe(true); + expect(result.rows.every((row) => row.name === 'John')).toBe(true); }); it('adds a column with null values', () => { @@ -34,7 +34,7 @@ describe('staticColumn', () => { expect(result.type).toBe('datatable'); expect(result.columns).toEqual([...testTable.columns, { name: 'empty', type: 'null' }]); - expect(result.rows.every(row => row.empty === null)).toBe(true); + expect(result.rows.every((row) => row.empty === null)).toBe(true); }); it('adds a column to empty tables', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts index 228d879c91a9c..9dd38dd57c677 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts @@ -45,7 +45,7 @@ export function staticColumn(): ExpressionFunctionDefinition< }, }, fn: (input, args) => { - const rows = input.rows.map(row => ({ ...row, [args.name]: args.value })); + const rows = input.rows.map((row) => ({ ...row, [args.name]: args.value })); const type = getType(args.value) as DatatableColumnType; const columns = [...input.columns]; const existingColumnIndex = columns.findIndex(({ name }) => name === args.name); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js index 7fd83d6a2b742..7ecccdd5ee544 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js @@ -9,7 +9,7 @@ import { switchFn } from './switch'; describe('switch', () => { const fn = functionWrapper(switchFn); - const getter = value => () => value; + const getter = (value) => () => value; const mockCases = [ { type: 'case', @@ -37,7 +37,7 @@ describe('switch', () => { result: 5, }, ]; - const nonMatchingCases = mockCases.filter(c => !c.matches); + const nonMatchingCases = mockCases.filter((c) => !c.matches); describe('spec', () => { it('is a function', () => { @@ -80,7 +80,7 @@ describe('switch', () => { it('should return the first match', async () => { const context = 'foo'; const args = { case: mockCases.map(getter) }; - const firstMatch = mockCases.find(c => c.matches); + const firstMatch = mockCases.find((c) => c.matches); expect(await fn(context, args)).toBe(firstMatch.result); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js index 834b9d195856c..2edbba278ffde 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js @@ -14,11 +14,11 @@ const errors = getFunctionErrors().timefilter; let clock = null; -beforeEach(function() { +beforeEach(function () { clock = sinon.useFakeTimers(); }); -afterEach(function() { +afterEach(function () { clock.restore(); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts index 1eb0a7c74780c..2b229b8957ec1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts @@ -93,12 +93,12 @@ export function esdocs(): ExpressionFunctionDefinition< } if (fields) { - const allFields = fields.split(',').map(field => field.trim()); - allFields.forEach(field => (query = query.field(field))); + const allFields = fields.split(',').map((field) => field.trim()); + allFields.forEach((field) => (query = query.field(field))); } if (sort) { - const [sortField, sortOrder] = sort.split(',').map(str => str.trim()); + const [sortField, sortOrder] = sort.split(',').map((str) => str.trim()); if (sortField) { query.order(`"${sortField}"`, sortOrder === 'asc'); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts index 17f0af4c9689e..54e48c8abf04b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts @@ -81,7 +81,7 @@ export function pointseries(): ExpressionFunctionDefinition< fn: (input, args) => { const errors = getFunctionErrors().pointseries; // Note: can't replace pivotObjectArray with datatableToMathContext, lose name of non-numeric columns - const columnNames = input.columns.map(col => col.name); + const columnNames = input.columns.map((col) => col.name); const mathScope = pivotObjectArray(input.rows, columnNames); const autoQuoteColumn = (col: string | null) => { if (!col || !columnNames.includes(col)) { @@ -97,7 +97,7 @@ export function pointseries(): ExpressionFunctionDefinition< // Separates args into dimensions and measures arrays // by checking if arg is a column reference (dimension) - keysOf(args).forEach(argName => { + keysOf(args).forEach((argName) => { const mathExp = autoQuoteColumn(args[argName]); if (mathExp != null && mathExp.trim() !== '') { @@ -175,7 +175,7 @@ export function pointseries(): ExpressionFunctionDefinition< // Measures // First group up all of the distinct dimensioned bits. Each of these will be reduced to just 1 value // for each measure - const measureKeys = groupBy(rows, row => + const measureKeys = groupBy(rows, (row) => dimensions .map(({ name }) => { const value = args[name]; @@ -185,13 +185,13 @@ export function pointseries(): ExpressionFunctionDefinition< ); // Then compute that 1 value for each measure - Object.values(measureKeys).forEach(valueRows => { + Object.values(measureKeys).forEach((valueRows) => { const subtable = { type: 'datatable', columns: input.columns, rows: valueRows }; const subScope = pivotObjectArray( subtable.rows, - subtable.columns.map(col => col.name) + subtable.columns.map((col) => col.name) ); - const measureValues = measureNames.map(measure => { + const measureValues = measureNames.map((measure) => { try { const ev = evaluate(args[measure], subScope); if (Array.isArray(ev)) { @@ -205,14 +205,14 @@ export function pointseries(): ExpressionFunctionDefinition< } }); - valueRows.forEach(row => { + valueRows.forEach((row) => { Object.assign(results[row[PRIMARY_KEY]], zipObject(measureNames, measureValues)); }); }); // It only makes sense to uniq the rows in a point series as 2 values can not exist in the exact same place at the same time. const resultingRows = uniqBy( - Object.values(results).map(row => omit(row, PRIMARY_KEY)), + Object.values(results).map((row) => omit(row, PRIMARY_KEY)), JSON.stringify ); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/advanced_filter.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/advanced_filter.tsx index 3e94c0d7476f4..e4d4510d40f53 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/advanced_filter.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/advanced_filter.tsx @@ -22,7 +22,7 @@ export interface Props { export const AdvancedFilter: FunctionComponent = ({ value = '', onChange, commit }) => ( { + onSubmit={(e) => { e.preventDefault(); commit(value); }} @@ -35,7 +35,7 @@ export const AdvancedFilter: FunctionComponent = ({ value = '', onChange, className="canvasAdvancedFilter__input" placeholder={strings.getInputPlaceholder()} value={value} - onChange={e => onChange(e.target.value)} + onChange={(e) => onChange(e.target.value)} /> diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/dropdown_filter.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/dropdown_filter.tsx index 68fccb39f413e..9cade90bd5870 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/dropdown_filter.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/dropdown_filter.tsx @@ -37,7 +37,7 @@ export const DropdownFilter: FunctionComponent = ({ let options = [ { value: '%%CANVAS_MATCH_ALL%%', text: `-- ${strings.getMatchAllOptionLabel()} --` }, ]; - options = options.concat(choices.map(choice => ({ value: choice, text: choice }))); + options = options.concat(choices.map((choice) => ({ value: choice, text: choice }))); const changeHandler = (e: FocusEvent | ChangeEvent) => { if (e && e.target) { @@ -47,7 +47,7 @@ export const DropdownFilter: FunctionComponent = ({ } }; - const dropdownOptions = options.map(option => { + const dropdownOptions = options.map((option) => { const { text } = option; const optionValue = option.value; const selected = optionValue === value; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx index 0ddfa1b41be70..ad368a912cd8c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx @@ -11,12 +11,10 @@ import { StartDeps } from '../../plugin'; import { IEmbeddable, EmbeddableFactory, - EmbeddablePanel, EmbeddableFactoryNotFoundError, } from '../../../../../../src/plugins/embeddable/public'; import { EmbeddableExpression } from '../../expression_types/embeddable'; import { RendererStrings } from '../../../i18n'; -import { getSavedObjectFinder } from '../../../../../../src/plugins/saved_objects/public'; import { embeddableInputToExpression } from './embeddable_input_to_expression'; import { EmbeddableInput } from '../../expression_types'; import { RendererHandlers } from '../../../types'; @@ -38,17 +36,7 @@ const renderEmbeddableFactory = (core: CoreStart, plugins: StartDeps) => { style={{ width: domNode.offsetWidth, height: domNode.offsetHeight, cursor: 'auto' }} > - + ); @@ -71,7 +59,7 @@ export const embeddableRendererFactory = (core: CoreStart, plugins: StartDeps) = if (!embeddablesRegistry[uniqueId]) { const factory = Array.from(plugins.embeddable.getEmbeddableFactories()).find( - embeddableFactory => embeddableFactory.type === embeddableType + (embeddableFactory) => embeddableFactory.type === embeddableType ) as EmbeddableFactory; if (!factory) { @@ -84,7 +72,7 @@ export const embeddableRendererFactory = (core: CoreStart, plugins: StartDeps) = embeddablesRegistry[uniqueId] = embeddableObject; ReactDOM.unmountComponentAtNode(domNode); - const subscription = embeddableObject.getInput$().subscribe(function(updatedInput) { + const subscription = embeddableObject.getInput$().subscribe(function (updatedInput) { const updatedExpression = embeddableInputToExpression(updatedInput, embeddableType); if (updatedExpression) { @@ -112,6 +100,7 @@ export const embeddableRendererFactory = (core: CoreStart, plugins: StartDeps) = }); } else { embeddablesRegistry[uniqueId].updateInput(input); + embeddablesRegistry[uniqueId].reload(); } }, }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts index 306020293abe6..07f828755e46f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts @@ -63,8 +63,8 @@ describe('toExpression', () => { const colors = ast.chain[0].arguments.colors as Ast[]; - const aColor = colors.find(color => color.chain[0].arguments.label[0] === 'a'); - const bColor = colors.find(color => color.chain[0].arguments.label[0] === 'b'); + const aColor = colors.find((color) => color.chain[0].arguments.label[0] === 'a'); + const bColor = colors.find((color) => color.chain[0].arguments.label[0] === 'b'); expect(aColor?.chain[0].arguments.color[0]).toBe(colorMap.a); expect(bColor?.chain[0].arguments.color[0]).toBe(colorMap.b); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.js index 971792ae4bfab..b7e3fc300a189 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.js @@ -21,7 +21,7 @@ export const error = () => ({ render(domNode, config, handlers) { const draw = () => { const buttonSize = Math.min(domNode.clientHeight, domNode.clientWidth); - const button = handleClick => ( + const button = (handleClick) => ( ( + .addDecorator((story) => (
({ }; config.options.series.pie.label.formatter = labelFormatter; - const legendFormatter = label => { + const legendFormatter = (label) => { const labelSpan = document.createElement('span'); Object.assign(labelSpan.style, config.font.spec); labelSpan.textContent = label; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js index 042cbe83fb10f..2c77f860fd14f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js @@ -89,7 +89,7 @@ function init(plot) { // add hook to determine if pie plugin in enabled, and then perform necessary operations - plot.hooks.processOptions.push(function(plot, options) { + plot.hooks.processOptions.push(function (plot, options) { if (options.series.pie.show) { options.grid.show = false; @@ -123,7 +123,7 @@ function init(plot) { } }); - plot.hooks.bindEvents.push(function(plot, eventHolder) { + plot.hooks.bindEvents.push(function (plot, eventHolder) { const options = plot.getOptions(); if (options.series.pie.show) { if (options.grid.hoverable) { @@ -136,21 +136,21 @@ function init(plot) { } }); - plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { + plot.hooks.processDatapoints.push(function (plot, series, data, datapoints) { const options = plot.getOptions(); if (options.series.pie.show) { processDatapoints(plot, series, data, datapoints); } }); - plot.hooks.drawOverlay.push(function(plot, octx) { + plot.hooks.drawOverlay.push(function (plot, octx) { const options = plot.getOptions(); if (options.series.pie.show) { drawOverlay(plot, octx); } }); - plot.hooks.draw.push(function(plot, newCtx) { + plot.hooks.draw.push(function (plot, newCtx) { const options = plot.getOptions(); if (options.series.pie.show) { draw(plot, newCtx); @@ -263,12 +263,7 @@ function init(plot) { const canvasWidth = plot.getPlaceholder().width(); const canvasHeight = plot.getPlaceholder().height(); - const legendWidth = - target - .children() - .filter('.legend') - .children() - .width() || 0; + const legendWidth = target.children().filter('.legend').children().width() || 0; ctx = newCtx; @@ -351,10 +346,7 @@ function init(plot) { function clear() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); - target - .children() - .filter('.pieLabel, .pieLabelBackground') - .remove(); + target.children().filter('.pieLabel, .pieLabelBackground').remove(); } function drawShadow() { @@ -867,7 +859,7 @@ const options = { }, label: { show: 'auto', - formatter: function(label, slice) { + formatter: function (label, slice) { return ( "
drawPoint(point)); + series.data.forEach((point) => drawPoint(point)); } } } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/plugins/text.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/plugins/text.js index 65dc7517453a1..2c603a62c1d76 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/plugins/text.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/plugins/text.js @@ -14,10 +14,10 @@ const options = { numbers: {}, }; -const xAlign = function(x) { +const xAlign = function (x) { return x; }; -const yAlign = function(y) { +const yAlign = function (y) { return y; }; //const horizontalShift = 1; @@ -28,7 +28,7 @@ function processOptions(/*plot, options*/) { function draw(plot, ctx) { $('.valueLabel', plot.getPlaceholder()).remove(); - plot.getData().forEach(function(series) { + plot.getData().forEach(function (series) { const show = get(series.numbers, 'show'); if (!show) { return; @@ -59,12 +59,9 @@ function draw(plot, ctx) { if (typeof text === 'undefined') { return; } - const textNode = $('
') - .text(String(text)) - .addClass('valueLabel') - .css({ - position: 'absolute', - }); + const textNode = $('
').text(String(text)).addClass('valueLabel').css({ + position: 'absolute', + }); plot.getPlaceholder().append(textNode); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.js index 576a7f00cfa45..67d0abb65837d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.js @@ -30,7 +30,7 @@ export const progress = () => ({ const initialViewBox = shapeSvg .getAttribute('viewBox') .split(' ') - .map(v => parseInt(v, 10)); + .map((v) => parseInt(v, 10)); let [minX, minY, width, height] = initialViewBox; if (shape !== 'horizontalBar') { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.js index 31f6d1056095e..2e48c8a2d5ec3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.js @@ -41,7 +41,7 @@ export const repeatImage = () => ({ } const img = new Image(); - img.onload = function() { + img.onload = function () { setSize(img); if (settings.max && settings.count > settings.max) { settings.count = settings.max; @@ -54,7 +54,7 @@ export const repeatImage = () => ({ } const emptyImage = new Image(); - emptyImage.onload = function() { + emptyImage.onload = function () { setSize(emptyImage); times(settings.max - settings.count, () => container.append(emptyImage.cloneNode(true))); finish(); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.js index a15345fed08bd..96c8d80794c0c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.js @@ -23,7 +23,7 @@ export const revealImage = () => ({ domNode.className = 'revealImage'; // set up the overlay image - img.onload = function() { + img.onload = function () { setSize(); finish(); }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.js index 1c9c76a74974a..02c86afd7182b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.js @@ -40,7 +40,7 @@ export const shape = () => ({ const initialViewBox = shapeSvg .getAttribute('viewBox') .split(' ') - .map(v => parseInt(v, 10)); + .map((v) => parseInt(v, 10)); const draw = () => { const width = domNode.offsetWidth; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/__examples__/time_filter.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/__examples__/time_filter.stories.tsx index c854ea8267bf5..abc4be44c4b2e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/__examples__/time_filter.stories.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/__examples__/time_filter.stories.tsx @@ -23,7 +23,7 @@ const timeRanges = [ ]; storiesOf('renderers/TimeFilter', module) - .addDecorator(story => ( + .addDecorator((story) => (
> = (core, plugins) => { const { uiSettings } = core; - const customQuickRanges = (uiSettings.get('timepicker:quickRanges') || []).map( + const customQuickRanges = (uiSettings.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES) || []).map( ({ from, to, display }: { from: string; to: string; display: string }) => ({ start: from, end: to, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx index e60c99b683f34..5fff29167e6a0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx @@ -29,7 +29,7 @@ const defaultValues = { class Interactive extends React.Component<{}, typeof defaultValues> { public state = defaultValues; - _onValueChange: (argValue: ExpressionAstExpression) => void = argValue => { + _onValueChange: (argValue: ExpressionAstExpression) => void = (argValue) => { action('onValueChange')(argValue); this.setState({ argValue }); }; @@ -46,13 +46,13 @@ class Interactive extends React.Component<{}, typeof defaultValues> { } storiesOf('arguments/AxisConfig', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('extended', () => ); storiesOf('arguments/AxisConfig/components', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('extended disabled', () => ( diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx index 1446fe2933f8a..14d61f13ea488 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx @@ -20,7 +20,7 @@ class Interactive extends React.Component<{}, typeof defaultValues> { public render() { return ( { + onValueChange={(argValue) => { action('onValueChange')(argValue); this.setState({ argValue }); }} @@ -31,13 +31,13 @@ class Interactive extends React.Component<{}, typeof defaultValues> { } storiesOf('arguments/AxisConfig', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('simple', () => ); storiesOf('arguments/AxisConfig/components', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('simple template', () => ( diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/__tests__/get_form_object.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/__tests__/get_form_object.js index dbc129094acff..cb8e999489fbf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/__tests__/get_form_object.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/__tests__/get_form_object.js @@ -20,14 +20,14 @@ describe('getFormObject', () => { it('number', () => { expect(getFormObject) .withArgs('2') - .to.throwException(e => { + .to.throwException((e) => { expect(e.message).to.be('Cannot render scalar values or complex math expressions'); }); }); it('complex expression', () => { expect(getFormObject) .withArgs('mean(field * 3)') - .to.throwException(e => { + .to.throwException((e) => { expect(e.message).to.be('Cannot render scalar values or complex math expressions'); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js index 8c1a4aad5d0f3..56849d6fe0c41 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js @@ -17,7 +17,7 @@ import { SimpleMathFunction } from './simple_math_function'; import { getFormObject } from './get_form_object'; const { DataColumn: strings } = ArgumentStrings; -const maybeQuoteValue = val => (val.match(/\s/) ? `'${val}'` : val); +const maybeQuoteValue = (val) => (val.match(/\s/) ? `'${val}'` : val); // TODO: Garbage, we could make a much nicer math form that can handle way more. class DatacolumnArgInput extends Component { @@ -51,7 +51,7 @@ class DatacolumnArgInput extends Component { const allowedTypes = typeInstance.options.allowedTypes || false; const onlyShowMathFunctions = typeInstance.options.onlyMath || false; - const valueNotSet = val => !val || val.length === 0; + const valueNotSet = (val) => !val || val.length === 0; const updateFunctionValue = () => { const fn = this.inputRefs.fn.value; @@ -79,11 +79,12 @@ class DatacolumnArgInput extends Component { onValueChange(`${fn}(${maybeQuoteValue(column)})`); }; - const column = columns.map(col => col.name).find(colName => colName === mathValue.column) || ''; + const column = + columns.map((col) => col.name).find((colName) => colName === mathValue.column) || ''; const options = [{ value: '', text: 'select column', disabled: true }]; - sortBy(columns, 'name').forEach(column => { + sortBy(columns, 'name').forEach((column) => { if (allowedTypes && !allowedTypes.includes(column.type)) { return; } @@ -96,7 +97,7 @@ class DatacolumnArgInput extends Component { (this.inputRefs.fn = ref)} + inputRef={(ref) => (this.inputRefs.fn = ref)} onlymath={onlyShowMathFunctions} onChange={updateFunctionValue} /> @@ -106,7 +107,7 @@ class DatacolumnArgInput extends Component { compressed options={options} value={column} - inputRef={ref => (this.inputRefs.column = ref)} + inputRef={(ref) => (this.inputRefs.column = ref)} onChange={updateFunctionValue} /> @@ -117,7 +118,7 @@ class DatacolumnArgInput extends Component { const EnhancedDatacolumnArgInput = compose( withPropsOnChange(['argValue', 'columns'], ({ argValue, columns }) => ({ - mathValue: (argValue => { + mathValue: ((argValue) => { if (getType(argValue) !== 'string') { return { error: 'argValue is not a string type' }; } @@ -132,7 +133,7 @@ const EnhancedDatacolumnArgInput = compose( })), createStatefulPropHoc('mathValue', 'setMathValue'), withHandlers({ - setMathFunction: ({ mathValue, setMathValue }) => fn => setMathValue({ ...mathValue, fn }), + setMathFunction: ({ mathValue, setMathValue }) => (fn) => setMathValue({ ...mathValue, fn }), }) )(DatacolumnArgInput); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts index 655a362fe6d33..fce9b21fa0387 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts @@ -30,7 +30,7 @@ export const dateFormatInitializer: SetupInitializer ({ + const dateFormats = Object.values(formatMap).map((format) => ({ value: format, text: moment.utc(moment()).format(format), })); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/filter_group.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/filter_group.js index 1659b0757cbd8..4a09fd5540d30 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/filter_group.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/filter_group.js @@ -29,15 +29,15 @@ const FilterGroupInput = ({ onValueChange, argValue, argId, filterGroups }) => { const choices = [{ text: 'No group', value: '' }].concat( argValueChoice, - filterGroups.map(f => ({ text: f })) + filterGroups.map((f) => ({ text: f })) ); - const handleSelectGroup = ev => { + const handleSelectGroup = (ev) => { const selected = ev.target.value; onValueChange(selected); }; - const handleAddGroup = ev => { + const handleAddGroup = (ev) => { // stop the form from submitting ev.preventDefault(); // set the new value @@ -56,7 +56,7 @@ const FilterGroupInput = ({ onValueChange, argValue, argId, filterGroups }) => { compressed type="text" value={inputValue} - onChange={ev => setInputValue(ev.target.value)} + onChange={(ev) => setInputValue(ev.target.value)} /> diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index a3c327da2e4dc..d7c7cd9e1a32f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -58,7 +58,7 @@ class ImageUpload extends React.Component { this._isMounted = false; } - updateAST = assetId => { + updateAST = (assetId) => { this.props.onValueChange({ type: 'expression', chain: [ @@ -73,7 +73,7 @@ class ImageUpload extends React.Component { }); }; - handleUpload = files => { + handleUpload = (files) => { const { onAssetAdd } = this.props; const [file] = files; @@ -82,8 +82,8 @@ class ImageUpload extends React.Component { this.setState({ loading: true }); // start loading indicator encode(file) - .then(dataurl => onAssetAdd('dataurl', dataurl)) - .then(assetId => { + .then((dataurl) => onAssetAdd('dataurl', dataurl)) + .then((assetId) => { this.updateAST(assetId); // this component can go away when onValueChange is called, check for _isMounted @@ -92,7 +92,7 @@ class ImageUpload extends React.Component { } }; - changeUrlType = optionId => { + changeUrlType = (optionId) => { this.setState({ urlType: optionId }); }; @@ -140,7 +140,7 @@ class ImageUpload extends React.Component { link: ( (this.inputRefs.srcUrlText = ref)} + inputRef={(ref) => (this.inputRefs.srcUrlText = ref)} onSubmit={this.setSrcUrl} /> ), diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts index bd84a2eb97d24..2f9a21d8a009f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts @@ -53,5 +53,5 @@ export const args = [ export const initializers = [dateFormatInitializer, numberFormatInitializer]; export const initializeArgs: SetupInitializer = (core, plugins) => { - return [...args, ...initializers.map(initializer => initializer(core, plugins))]; + return [...args, ...initializers.map((initializer) => initializer(core, plugins))]; }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number.js index aae96f1266a36..f9c2f5eb21a2a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number.js @@ -27,7 +27,7 @@ const NumberArgInput = ({ updateValue, value, confirm, commit, argId }) => ( compressed id={argId} value={Number(value)} - onChange={confirm ? updateValue : ev => commit(Number(ev.target.value))} + onChange={confirm ? updateValue : (ev) => commit(Number(ev.target.value))} /> {confirm && ( diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts index 4025d4deaf997..5a3e3904f4f23 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts @@ -11,6 +11,7 @@ import { templateFromReactComponent } from '../../../../public/lib/template_from import { ArgumentFactory } from '../../../../types/arguments'; import { ArgumentStrings } from '../../../../i18n'; import { SetupInitializer } from '../../../plugin'; +import { UI_SETTINGS } from '../../../../../../../src/plugins/data/public'; const { NumberFormat: strings } = ArgumentStrings; @@ -19,11 +20,11 @@ export const numberFormatInitializer: SetupInitializer { const formatMap = { - NUMBER: core.uiSettings.get('format:number:defaultPattern'), - PERCENT: core.uiSettings.get('format:percent:defaultPattern'), - CURRENCY: core.uiSettings.get('format:currency:defaultPattern'), + NUMBER: core.uiSettings.get(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN), + PERCENT: core.uiSettings.get(UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN), + CURRENCY: core.uiSettings.get(UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN), DURATION: '00:00:00', - BYTES: core.uiSettings.get('format:bytes:defaultPattern'), + BYTES: core.uiSettings.get(UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN), }; const numberFormats = [ diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js index d60dc13f0105b..eddaa20a4800e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js @@ -26,7 +26,7 @@ const PaletteArgInput = ({ onValueChange, argValue, renderError }) => { throwNotParsed(); } try { - const colors = chain[0].arguments._.map(astObj => { + const colors = chain[0].arguments._.map((astObj) => { if (getType(astObj) !== 'string') { throwNotParsed(); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/percentage.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/percentage.js index 5fa2ca42ca78a..746d1ff7a9d18 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/percentage.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/percentage.js @@ -13,7 +13,7 @@ import { ArgumentStrings } from '../../../i18n'; const { Percentage: strings } = ArgumentStrings; const PercentageArgInput = ({ onValueChange, argValue }) => { - const handleChange = ev => { + const handleChange = (ev) => { return onValueChange(ev.target.value / 100); }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/range.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/range.js index b3d1670c5d711..d23d6560f0124 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/range.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/range.js @@ -14,7 +14,7 @@ const { Range: strings } = ArgumentStrings; const RangeArgInput = ({ typeInstance, onValueChange, argValue }) => { const { min, max, step } = typeInstance.options; - const handleChange = ev => { + const handleChange = (ev) => { return onValueChange(Number(ev.target.value)); }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/select.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/select.js index 478acd41d797f..d218ca39b7f72 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/select.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/select.js @@ -14,7 +14,7 @@ const { Select: strings } = ArgumentStrings; const SelectArgInput = ({ typeInstance, onValueChange, argValue, argId }) => { const choices = typeInstance.options.choices.map(({ value, name }) => ({ value, text: name })); - const handleChange = ev => { + const handleChange = (ev) => { // Get the value from the choices passed in since it could be a number or // boolean, but ev.target.value is always a string const { value } = choices[ev.target.selectedIndex]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/string.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/string.js index dc31497a7da78..53c4c8bee5282 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/string.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/string.js @@ -22,7 +22,7 @@ const StringArgInput = ({ updateValue, value, confirm, commit, argId }) => ( compressed id={argId} value={value} - onChange={confirm ? updateValue : ev => commit(ev.target.value)} + onChange={confirm ? updateValue : (ev) => commit(ev.target.value)} /> {confirm && ( diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/textarea.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/textarea.js index a0af71585eed0..3bbdd165541ff 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/textarea.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/textarea.js @@ -30,7 +30,7 @@ const TextAreaArgInput = ({ updateValue, value, confirm, commit, renderError, ar rows={10} value={value} resize="none" - onChange={confirm ? updateValue : ev => commit(ev.target.value)} + onChange={confirm ? updateValue : (ev) => commit(ev.target.value)} /> diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js index 282ec17e94c9b..7384986fa5c2b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js @@ -59,12 +59,12 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { if (commas.length === 0) { return []; } - return commas.split(',').map(str => str.trim()); + return commas.split(',').map((str) => str.trim()); }; const getSortBy = () => { const commas = getSimpleArg('sort', args)[0] || ', DESC'; - return commas.split(',').map(str => str.trim()); + return commas.split(',').map((str) => str.trim()); }; const fields = getFields(); @@ -88,7 +88,7 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { helpText={strings.getIndexLabel()} display="rowCompressed" > - setArg('index', index)} /> + setArg('index', index)} /> { > setArg('fields', fields.join(', '))} + onChange={(fields) => setArg('fields', fields.join(', '))} selected={fields} /> @@ -114,14 +114,14 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { setArg('sort', [field, sortOrder].join(', '))} + onChange={(field) => setArg('sort', [field, sortOrder].join(', '))} /> setArg('sort', [sortField, e.target.value].join(', '))} + onChange={(e) => setArg('sort', [sortField, e.target.value].join(', '))} options={sortOptions} compressed /> @@ -140,7 +140,7 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { > setArg(getArgName(), e.target.value)} + onChange={(e) => setArg(getArgName(), e.target.value)} compressed /> diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js index 44e335dd7b41f..958898893d7eb 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js @@ -49,7 +49,7 @@ class EssqlDatasource extends PureComponent { }); }; - onChange = e => { + onChange = (e) => { const { value } = e.target; this.props.setInvalid(!value.trim()); this.setArg(this.getArgName(), value); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js index b36f1a747f120..c5126ab3e969f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js @@ -99,7 +99,7 @@ const TimelionDatasource = ({ args, updateArgs, defaultIndex }) => { setArg(argName, e.target.value)} + onChange={(e) => setArg(argName, e.target.value)} rows={15} /> @@ -115,7 +115,7 @@ const TimelionDatasource = ({ args, updateArgs, defaultIndex }) => { setArg('interval', e.target.value)} + onChange={(e) => setArg('interval', e.target.value)} />
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts index fdcaf21982050..34877f2fd551b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts @@ -54,5 +54,5 @@ export const viewSpecs = [ export const viewInitializers = [metricInitializer]; export const initializeViews: SetupInitializer = (core, plugins) => { - return [...viewSpecs, ...viewInitializers.map(initializer => initializer(core, plugins))]; + return [...viewSpecs, ...viewInitializers.map((initializer) => initializer(core, plugins))]; }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts index 93912b7b0517f..11bee46088576 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts @@ -7,6 +7,7 @@ import { openSans } from '../../../common/lib/fonts'; import { ViewStrings } from '../../../i18n'; import { SetupInitializer } from '../../plugin'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; const { Metric: strings } = ViewStrings; @@ -22,7 +23,7 @@ export const metricInitializer: SetupInitializer = (core, plugin) => { displayName: strings.getMetricFormatDisplayName(), help: strings.getMetricFormatHelp(), argType: 'numberFormat', - default: `"${core.uiSettings.get('format:number:defaultPattern')}"`, + default: `"${core.uiSettings.get(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN)}"`, }, { name: '_', diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie.js index 783140b0c8b9e..f1b6a48d1e7b0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie.js @@ -90,6 +90,6 @@ export const pie = () => ({ if (getState(context) !== 'ready') { return { labels: [] }; } - return { labels: uniq(map(getValue(context).rows, 'color').filter(v => v !== undefined)) }; + return { labels: uniq(map(getValue(context).rows, 'color').filter((v) => v !== undefined)) }; }, }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js index 6e000336a6b34..1449bddf322b5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js @@ -72,6 +72,6 @@ export const plot = () => ({ if (getState(context) !== 'ready') { return { labels: [] }; } - return { labels: uniq(map(getValue(context).rows, 'color').filter(v => v !== undefined)) }; + return { labels: uniq(map(getValue(context).rows, 'color').filter((v) => v !== undefined)) }; }, }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js index ae68c9c5c6031..c36334e18704b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js @@ -22,7 +22,7 @@ export const progress = () => ({ help: strings.getShapeHelp(), argType: 'select', options: { - choices: Object.keys(shapes).map(key => ({ + choices: Object.keys(shapes).map((key) => ({ value: key, //turns camel into title case name: key[0].toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1'), diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/table.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/table.js index 03a13b3f8fe21..73324feddcab0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/table.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/table.js @@ -24,7 +24,7 @@ export const table = () => ({ argType: 'select', default: 10, options: { - choices: ['', 5, 10, 25, 50, 100].map(v => ({ name: String(v), value: v })), + choices: ['', 5, 10, 25, 50, 100].map((v) => ({ name: String(v), value: v })), }, }, { diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.test.ts b/x-pack/plugins/canvas/common/lib/autocomplete.test.ts index 31d213f4853ff..f7a773f6ca36a 100644 --- a/x-pack/plugins/canvas/common/lib/autocomplete.test.ts +++ b/x-pack/plugins/canvas/common/lib/autocomplete.test.ts @@ -18,7 +18,7 @@ describe('autocomplete', () => { it('should return function definition for plot', () => { const expression = 'plot '; const def = getFnArgDefAtPosition(functionSpecs, expression, expression.length); - const plotFn = functionSpecs.find(spec => spec.name === 'plot'); + const plotFn = functionSpecs.find((spec) => spec.name === 'plot'); expect(def.fnDef).toBe(plotFn); }); }); @@ -34,7 +34,7 @@ describe('autocomplete', () => { it('should suggest arguments', () => { const expression = 'plot '; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); - const plotFn = functionSpecs.find(spec => spec.name === 'plot'); + const plotFn = functionSpecs.find((spec) => spec.name === 'plot'); expect(suggestions.length).toBe(Object.keys(plotFn!.args).length); expect(suggestions[0].start).toBe(expression.length); expect(suggestions[0].end).toBe(expression.length); @@ -43,7 +43,7 @@ describe('autocomplete', () => { it('should suggest values', () => { const expression = 'shape shape='; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); - const shapeFn = functionSpecs.find(spec => spec.name === 'shape'); + const shapeFn = functionSpecs.find((spec) => spec.name === 'shape'); expect(suggestions.length).toBe(shapeFn!.args.shape.options.length); expect(suggestions[0].start).toBe(expression.length); expect(suggestions[0].end).toBe(expression.length); @@ -85,14 +85,14 @@ describe('autocomplete', () => { expect(suggestions[0].fnDef.inputTypes).toEqual(['datatable']); const withReturnOnly = suggestions.findIndex( - suggestion => + (suggestion) => suggestion.fnDef.type === 'datatable' && suggestion.fnDef.inputTypes && !(suggestion.fnDef.inputTypes as string[]).includes('datatable') ); const withNeither = suggestions.findIndex( - suggestion => + (suggestion) => suggestion.fnDef.type !== 'datatable' && (!suggestion.fnDef.inputTypes || !(suggestion.fnDef.inputTypes as string[]).includes('datatable')) @@ -111,7 +111,7 @@ describe('autocomplete', () => { expression, expression.length - 1 ); - const ltFn = functionSpecs.find(spec => spec.name === 'lt'); + const ltFn = functionSpecs.find((spec) => spec.name === 'lt'); expect(suggestions.length).toBe(Object.keys(ltFn!.args).length); expect(suggestions[0].start).toBe(expression.length - 1); expect(suggestions[0].end).toBe(expression.length - 1); @@ -124,7 +124,7 @@ describe('autocomplete', () => { expression, expression.length - 1 ); - const shapeFn = functionSpecs.find(spec => spec.name === 'shape'); + const shapeFn = functionSpecs.find((spec) => spec.name === 'shape'); expect(suggestions.length).toBe(shapeFn!.args.shape.options.length); expect(suggestions[0].start).toBe(expression.length - 1); expect(suggestions[0].end).toBe(expression.length - 1); @@ -137,7 +137,7 @@ describe('autocomplete', () => { expression, expression.length - 1 ); - const shapeFn = functionSpecs.find(spec => spec.name === 'shape'); + const shapeFn = functionSpecs.find((spec) => spec.name === 'shape'); expect(suggestions.length).toBe(shapeFn!.args.shape.options.length); expect(suggestions[0].start).toBe(expression.length - '"ar"'.length); expect(suggestions[0].end).toBe(expression.length); @@ -146,32 +146,32 @@ describe('autocomplete', () => { it('should prioritize functions that match the previous function type', () => { const expression = 'plot | '; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); - const renderIndex = suggestions.findIndex(suggestion => suggestion.text.includes('render')); - const anyIndex = suggestions.findIndex(suggestion => suggestion.text.includes('any')); + const renderIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('render')); + const anyIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('any')); expect(renderIndex).toBeLessThan(anyIndex); }); it('should alphabetize functions', () => { const expression = ''; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); - const metricIndex = suggestions.findIndex(suggestion => suggestion.text.includes('metric')); - const anyIndex = suggestions.findIndex(suggestion => suggestion.text.includes('any')); + const metricIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('metric')); + const anyIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('any')); expect(anyIndex).toBeLessThan(metricIndex); }); it('should prioritize unnamed arguments', () => { const expression = 'case '; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); - const whenIndex = suggestions.findIndex(suggestion => suggestion.text.includes('when')); - const thenIndex = suggestions.findIndex(suggestion => suggestion.text.includes('then')); + const whenIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('when')); + const thenIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('then')); expect(whenIndex).toBeLessThan(thenIndex); }); it('should alphabetize arguments', () => { const expression = 'plot '; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); - const yaxisIndex = suggestions.findIndex(suggestion => suggestion.text.includes('yaxis')); - const defaultStyleIndex = suggestions.findIndex(suggestion => + const yaxisIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('yaxis')); + const defaultStyleIndex = suggestions.findIndex((suggestion) => suggestion.text.includes('defaultStyle') ); expect(defaultStyleIndex).toBeLessThan(yaxisIndex); diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.ts b/x-pack/plugins/canvas/common/lib/autocomplete.ts index 5ee4d2104a0f7..982aee1ea19c8 100644 --- a/x-pack/plugins/canvas/common/lib/autocomplete.ts +++ b/x-pack/plugins/canvas/common/lib/autocomplete.ts @@ -200,7 +200,7 @@ export function getAutocompleteSuggestions( item. The context function for `formatnumber` is the return of `math "divide(value, 2)"`. */ function getFnArgAtPosition(ast: ExpressionASTWithMeta, position: number): FnArgAtPosition { - const fnIndex = ast.node.chain.findIndex(fn => fn.start <= position && position <= fn.end); + const fnIndex = ast.node.chain.findIndex((fn) => fn.start <= position && position <= fn.end); const fn = ast.node.chain[fnIndex]; for (const [argName, argValues] of Object.entries(fn.node.arguments)) { for (let argIndex = 0; argIndex < argValues.length; argIndex++) { @@ -285,7 +285,7 @@ function getFnNameSuggestions( return aScore > bScore ? -1 : 1; }); - return fnDefs.map(fnDef => { + return fnDefs.map((fnDef) => { return { type: 'function', text: `${fnDef.name} `, start, end: end - MARKER.length, fnDef }; }); } @@ -325,7 +325,7 @@ function getSubFnNameSuggestions( return aScore > bScore ? -1 : 1; }); - return fnDefs.map(fnDef => { + return fnDefs.map((fnDef) => { return { type: 'function', text: fnDef.name + ' ', start, end: end - MARKER.length, fnDef }; }); } @@ -402,7 +402,7 @@ function getArgNameSuggestions( // Filter the list of args by those which aren't already present (unless they allow multi) const argEntries = Object.entries(fn.arguments).map<[string, ExpressionArgASTWithMeta[]]>( ([name, values]) => { - return [name, values.filter(value => !value.text.includes(MARKER))]; + return [name, values.filter((value) => !value.text.includes(MARKER))]; } ); @@ -422,7 +422,7 @@ function getArgNameSuggestions( .map(([name, arg]) => ({ name, ...arg })) .sort(unnamedArgComparator); - return argDefs.map(argDef => { + return argDefs.map((argDef) => { return { type: 'argument', text: argDef.name + '=', @@ -464,7 +464,7 @@ function getArgValueSuggestions( suggestions.push(argDef.default); } - return uniq(suggestions).map(value => { + return uniq(suggestions).map((value) => { const text = maybeQuote(value) + ' '; return { start, end: end - MARKER.length, type: 'value', text }; }); diff --git a/x-pack/plugins/canvas/common/lib/datatable/query.js b/x-pack/plugins/canvas/common/lib/datatable/query.js index 63945ce7690f9..ec7530ca47a5b 100644 --- a/x-pack/plugins/canvas/common/lib/datatable/query.js +++ b/x-pack/plugins/canvas/common/lib/datatable/query.js @@ -13,17 +13,17 @@ export function queryDatatable(datatable, query) { } if (query.and) { - query.and.forEach(filter => { + query.and.forEach((filter) => { // handle exact matches if (filter.filterType === 'exactly') { - datatable.rows = datatable.rows.filter(row => { + datatable.rows = datatable.rows.filter((row) => { return row[filter.column] === filter.value; }); } // handle time filters if (filter.filterType === 'time') { - const columnNames = datatable.columns.map(col => col.name); + const columnNames = datatable.columns.map((col) => col.name); // remove row if no column match if (!columnNames.includes(filter.column)) { @@ -31,7 +31,7 @@ export function queryDatatable(datatable, query) { return; } - datatable.rows = datatable.rows.filter(row => { + datatable.rows = datatable.rows.filter((row) => { const fromTime = new Date(filter.from).getTime(); const toTime = new Date(filter.to).getTime(); const rowTime = new Date(row[filter.column]).getTime(); diff --git a/x-pack/plugins/canvas/common/lib/dataurl.ts b/x-pack/plugins/canvas/common/lib/dataurl.ts index eddc8da1a51aa..ea5a26b27e423 100644 --- a/x-pack/plugins/canvas/common/lib/dataurl.ts +++ b/x-pack/plugins/canvas/common/lib/dataurl.ts @@ -55,7 +55,7 @@ export function encode(data: any | null, type = 'text/plain') { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); - reader.onerror = err => reject(err); + reader.onerror = (err) => reject(err); reader.readAsDataURL(data); }); } diff --git a/x-pack/plugins/canvas/common/lib/get_field_type.ts b/x-pack/plugins/canvas/common/lib/get_field_type.ts index aaea95334e62e..db817393a1cdb 100644 --- a/x-pack/plugins/canvas/common/lib/get_field_type.ts +++ b/x-pack/plugins/canvas/common/lib/get_field_type.ts @@ -19,6 +19,6 @@ export function getFieldType(columns: DatatableColumn[], field?: string): string return 'null'; } const realField = unquoteString(field); - const column = columns.find(dataTableColumn => dataTableColumn.name === realField); + const column = columns.find((dataTableColumn) => dataTableColumn.name === realField); return column ? column.type : 'null'; } diff --git a/x-pack/plugins/canvas/common/lib/hex_to_rgb.ts b/x-pack/plugins/canvas/common/lib/hex_to_rgb.ts index 586666e633e84..d05536ededae3 100644 --- a/x-pack/plugins/canvas/common/lib/hex_to_rgb.ts +++ b/x-pack/plugins/canvas/common/lib/hex_to_rgb.ts @@ -10,12 +10,12 @@ export const hexToRgb = (hex: string) => { const shorthandMatches = shorthandHexColor.exec(hex); if (shorthandMatches) { - return shorthandMatches.slice(1, 4).map(mappedHex => parseInt(mappedHex + mappedHex, 16)); + return shorthandMatches.slice(1, 4).map((mappedHex) => parseInt(mappedHex + mappedHex, 16)); } const hexMatches = hexColor.exec(hex); if (hexMatches) { - return hexMatches.slice(1, 4).map(slicedHex => parseInt(slicedHex, 16)); + return hexMatches.slice(1, 4).map((slicedHex) => parseInt(slicedHex, 16)); } return null; diff --git a/x-pack/plugins/canvas/common/lib/pivot_object_array.ts b/x-pack/plugins/canvas/common/lib/pivot_object_array.ts index f13a2a2af8844..c098b7772ef11 100644 --- a/x-pack/plugins/canvas/common/lib/pivot_object_array.ts +++ b/x-pack/plugins/canvas/common/lib/pivot_object_array.ts @@ -20,6 +20,6 @@ export function pivotObjectArray< throw new Error('Columns should be an array of strings'); } - const columnValues = map(columnNames, name => map(rows, name)); + const columnValues = map(columnNames, (name) => map(rows, name)); return zipObject(columnNames, columnValues); } diff --git a/x-pack/plugins/canvas/i18n/elements/apply_strings.ts b/x-pack/plugins/canvas/i18n/elements/apply_strings.ts index 4464ed5dbf185..5e5115abb0194 100644 --- a/x-pack/plugins/canvas/i18n/elements/apply_strings.ts +++ b/x-pack/plugins/canvas/i18n/elements/apply_strings.ts @@ -16,7 +16,7 @@ import { getElementStrings } from './index'; export const applyElementStrings = (elements: ElementFactory[]) => { const elementStrings = getElementStrings(); - return elements.map(spec => { + return elements.map((spec) => { const result = spec(); const { name } = result; const strings = elementStrings[name]; diff --git a/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts b/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts index 1eff73c1525b1..e1e35a5b6b0dd 100644 --- a/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts +++ b/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts @@ -11,27 +11,27 @@ const elementSpecs = initializeElements(coreMock.createSetup() as any, {} as any describe('ElementStrings', () => { const elementStrings = getElementStrings(); - const elementNames = elementSpecs.map(spec => spec().name); + const elementNames = elementSpecs.map((spec) => spec().name); const stringKeys = Object.keys(elementStrings); test('All element names should exist in the strings definition', () => { - elementNames.forEach(name => expect(stringKeys).toContain(name)); + elementNames.forEach((name) => expect(stringKeys).toContain(name)); }); test('All string definitions should correspond to an existing element', () => { - stringKeys.forEach(key => expect(elementNames).toContain(key)); + stringKeys.forEach((key) => expect(elementNames).toContain(key)); }); const strings = Object.values(elementStrings); test('All elements should have a displayName string defined', () => { - strings.forEach(value => { + strings.forEach((value) => { expect(value).toHaveProperty('displayName'); }); }); test('All elements should have a help string defined', () => { - strings.forEach(value => { + strings.forEach((value) => { expect(value).toHaveProperty('help'); }); }); diff --git a/x-pack/plugins/canvas/i18n/functions/dict/alter_column.ts b/x-pack/plugins/canvas/i18n/functions/dict/alter_column.ts index cc601b0ea0e31..f201e73d717eb 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/alter_column.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/alter_column.ts @@ -18,7 +18,7 @@ export const help: FunctionHelp> = { values: { list: Object.values(DATATABLE_COLUMN_TYPES) .slice(0, -1) - .map(type => `\`${type}\``) + .map((type) => `\`${type}\``) .join(', '), end: Object.values(DATATABLE_COLUMN_TYPES).slice(-1)[0], mapColumnFn: '`mapColumn`', diff --git a/x-pack/plugins/canvas/i18n/functions/dict/axis_config.ts b/x-pack/plugins/canvas/i18n/functions/dict/axis_config.ts index 15708dd949b22..7cf0ec6c58761 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/axis_config.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/axis_config.ts @@ -38,7 +38,7 @@ export const help: FunctionHelp> = { values: { list: Object.values(Position) .slice(0, -1) - .map(position => `\`"${position}"\``) + .map((position) => `\`"${position}"\``) .join(', '), end: Object.values(Position).slice(-1)[0], }, diff --git a/x-pack/plugins/canvas/i18n/functions/dict/pie.ts b/x-pack/plugins/canvas/i18n/functions/dict/pie.ts index 1a93cdee05749..2e4bfc88a273a 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/pie.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/pie.ts @@ -41,7 +41,7 @@ export const help: FunctionHelp> = { 'The legend position. For example, {positions}, or {BOOLEAN_FALSE}. When {BOOLEAN_FALSE}, the legend is hidden.', values: { positions: Object.values(Position) - .map(position => `\`"${position}"\``) + .map((position) => `\`"${position}"\``) .join(', '), BOOLEAN_FALSE, }, diff --git a/x-pack/plugins/canvas/i18n/functions/dict/plot.ts b/x-pack/plugins/canvas/i18n/functions/dict/plot.ts index 3d4624bd2495e..068156f14c91b 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/plot.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/plot.ts @@ -33,7 +33,7 @@ export const help: FunctionHelp> = { 'The legend position. For example, {positions}, or {BOOLEAN_FALSE}. When {BOOLEAN_FALSE}, the legend is hidden.', values: { positions: Object.values(Position) - .map(position => `\`"${position}"\``) + .map((position) => `\`"${position}"\``) .join(', '), BOOLEAN_FALSE, }, diff --git a/x-pack/plugins/canvas/i18n/functions/dict/progress.ts b/x-pack/plugins/canvas/i18n/functions/dict/progress.ts index 7e441df9bd246..1880c5dc807f0 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/progress.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/progress.ts @@ -48,7 +48,7 @@ export const help: FunctionHelp> = { values: { list: Object.values(Shape) .slice(0, -1) - .map(shape => `\`"${shape}"\``) + .map((shape) => `\`"${shape}"\``) .join(', '), end: `\`"${Object.values(Shape).slice(-1)[0]}"\``, }, diff --git a/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts b/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts index f210ec8c1d882..410ca29d7b4d4 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts @@ -39,7 +39,7 @@ export const help: FunctionHelp> = { values: { list: Object.values(Position) .slice(0, -1) - .map(position => `\`"${position}"\``) + .map((position) => `\`"${position}"\``) .join(', '), end: Object.values(Position).slice(-1)[0], }, diff --git a/x-pack/plugins/canvas/i18n/templates/apply_strings.ts b/x-pack/plugins/canvas/i18n/templates/apply_strings.ts index 4ddd3a61543c2..01775e9daeb51 100644 --- a/x-pack/plugins/canvas/i18n/templates/apply_strings.ts +++ b/x-pack/plugins/canvas/i18n/templates/apply_strings.ts @@ -18,7 +18,7 @@ import { TagStrings } from '../../i18n'; export const applyTemplateStrings = (templates: CanvasTemplate[]) => { const templateStrings = getTemplateStrings(); - return templates.map(template => { + return templates.map((template) => { const { name: templateName } = template; const strings = templateStrings[templateName]; @@ -36,7 +36,7 @@ export const applyTemplateStrings = (templates: CanvasTemplate[]) => { } if (template.tags) { - template.tags = template.tags.map(tag => { + template.tags = template.tags.map((tag) => { if (TagStrings[tag]) { return TagStrings[tag](); } diff --git a/x-pack/plugins/canvas/i18n/templates/template_strings.test.ts b/x-pack/plugins/canvas/i18n/templates/template_strings.test.ts index eeeb06732d397..4f7b61e13fe69 100644 --- a/x-pack/plugins/canvas/i18n/templates/template_strings.test.ts +++ b/x-pack/plugins/canvas/i18n/templates/template_strings.test.ts @@ -11,7 +11,7 @@ import { TagStrings } from '../tags'; describe('TemplateStrings', () => { const templateStrings = getTemplateStrings(); - const templateNames = templateSpecs.map(template => template().name); + const templateNames = templateSpecs.map((template) => template().name); const stringKeys = Object.keys(templateStrings); test('All template names should exist in the strings definition', () => { @@ -19,19 +19,19 @@ describe('TemplateStrings', () => { }); test('All string definitions should correspond to an existing template', () => { - stringKeys.forEach(key => expect(templateNames).toContain(key)); + stringKeys.forEach((key) => expect(templateNames).toContain(key)); }); const strings = Object.values(templateStrings); test('All templates should have a name string defined', () => { - strings.forEach(value => { + strings.forEach((value) => { expect(value).toHaveProperty('name'); }); }); test('All templates should have a help string defined', () => { - strings.forEach(value => { + strings.forEach((value) => { expect(value).toHaveProperty('help'); }); }); @@ -39,7 +39,7 @@ describe('TemplateStrings', () => { test('All templates should have tags that are defined', () => { const tagNames = Object.keys(TagStrings); - templateSpecs.forEach(template => { + templateSpecs.forEach((template) => { template().tags.forEach((tagName: string) => expect(tagNames).toContain(tagName)); }); }); diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx index d97e4944da13a..8751d8102ad37 100644 --- a/x-pack/plugins/canvas/public/application.tsx +++ b/x-pack/plugins/canvas/public/application.tsx @@ -18,25 +18,27 @@ import { CanvasStartDeps, CanvasSetupDeps } from './plugin'; // @ts-ignore Untyped local import { App } from './components/app'; import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; -import { initInterpreter, resetInterpreter } from './lib/run_interpreter'; import { registerLanguage } from './lib/monaco_language_def'; import { SetupRegistries } from './plugin_api'; import { initRegistries, populateRegistries, destroyRegistries } from './registries'; import { getDocumentationLinks } from './lib/documentation_links'; // @ts-ignore untyped component import { HelpMenu } from './components/help_menu/help_menu'; -import { createStore, destroyStore } from './store'; +import { createStore } from './store'; /* eslint-enable */ import { init as initStatsReporter } from './lib/ui_metric'; import { CapabilitiesStrings } from '../i18n'; -import { startServices, stopServices, services } from './services'; +import { startServices, services } from './services'; // @ts-ignore Untyped local import { destroyHistory } from './lib/history_provider'; // @ts-ignore Untyped local import { stopRouter } from './lib/router_provider'; +import { initFunctions } from './functions'; +// @ts-ignore Untyped local +import { appUnload } from './state/actions/app'; import './style/index.scss'; @@ -68,6 +70,7 @@ export const renderApp = ( ); return () => { ReactDOM.unmountComponentAtNode(element); + canvasStore.dispatch(appUnload()); }; }; @@ -79,15 +82,25 @@ export const initializeCanvas = async ( registries: SetupRegistries, appUpdater: BehaviorSubject ) => { - startServices(coreSetup, coreStart, setupPlugins, startPlugins, appUpdater); + await startServices(coreSetup, coreStart, setupPlugins, startPlugins, appUpdater); + + // Adding these functions here instead of in plugin.ts. + // Some of these functions have deep dependencies into Canvas, which was bulking up the size + // of our bundle entry point. Moving them here pushes that load to when canvas is actually loaded. + const canvasFunctions = initFunctions({ + timefilter: setupPlugins.data.query.timefilter.timefilter, + prependBasePath: coreSetup.http.basePath.prepend, + typesRegistry: setupPlugins.expressions.__LEGACY.types, + }); + + for (const fn of canvasFunctions) { + services.expressions.getService().registerFunction(fn); + } // Create Store const canvasStore = await createStore(coreSetup, setupPlugins); - // Init Interpreter - initInterpreter(startPlugins.expressions, setupPlugins.expressions).then(() => { - registerLanguage(Object.values(startPlugins.expressions.getFunctions())); - }); + registerLanguage(Object.values(services.expressions.getService().getFunctions())); // Init Registries initRegistries(); @@ -115,7 +128,7 @@ export const initializeCanvas = async ( href: getDocumentationLinks().canvas, }, ], - content: domNode => { + content: (domNode) => { ReactDOM.render(, domNode); return () => ReactDOM.unmountComponentAtNode(domNode); }, @@ -129,10 +142,14 @@ export const initializeCanvas = async ( }; export const teardownCanvas = (coreStart: CoreStart, startPlugins: CanvasStartDeps) => { - stopServices(); destroyRegistries(); - resetInterpreter(); - destroyStore(); + + // TODO: Not cleaning these up temporarily. + // We have an issue where if requests are inflight, and you navigate away, + // those requests could still be trying to act on the store and possibly require services. + // stopServices(); + // resetInterpreter(); + // destroyStore(); coreStart.chrome.setBadge(undefined); coreStart.chrome.setHelpExtension(undefined); diff --git a/x-pack/plugins/canvas/public/apps/export/export/index.js b/x-pack/plugins/canvas/public/apps/export/export/index.js index dafcb9f4c2510..95c46d9e1c8ae 100644 --- a/x-pack/plugins/canvas/public/apps/export/export/index.js +++ b/x-pack/plugins/canvas/public/apps/export/export/index.js @@ -11,12 +11,12 @@ import { getWorkpad, getSelectedPageIndex } from '../../../state/selectors/workp import { LoadWorkpad } from './load_workpad'; import { ExportApp as Component } from './export_app'; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ workpad: getWorkpad(state), selectedPageIndex: getSelectedPageIndex(state), }); -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ initializeWorkpad() { dispatch(initializeWorkpad()); }, diff --git a/x-pack/plugins/canvas/public/apps/export/routes.js b/x-pack/plugins/canvas/public/apps/export/routes.js index a0eafe3e3ffe9..33e375115aa19 100644 --- a/x-pack/plugins/canvas/public/apps/export/routes.js +++ b/x-pack/plugins/canvas/public/apps/export/routes.js @@ -18,7 +18,7 @@ export const routes = [ { name: 'exportWorkpad', path: '/pdf/:id/page/:page', - action: dispatch => async ({ params, router }) => { + action: (dispatch) => async ({ params, router }) => { // load workpad if given a new id via url param const fetchedWorkpad = await workpadService.get(params.id); const pageNumber = parseInt(params.page, 10); diff --git a/x-pack/plugins/canvas/public/apps/home/home_app/index.js b/x-pack/plugins/canvas/public/apps/home/home_app/index.js index f26b3510c7682..f78ee1f8a18af 100644 --- a/x-pack/plugins/canvas/public/apps/home/home_app/index.js +++ b/x-pack/plugins/canvas/public/apps/home/home_app/index.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { resetWorkpad } from '../../../state/actions/workpad'; import { HomeApp as Component } from './home_app'; -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ onLoad() { dispatch(resetWorkpad()); }, diff --git a/x-pack/plugins/canvas/public/apps/workpad/routes.js b/x-pack/plugins/canvas/public/apps/workpad/routes.js index 4e3920bf34f67..a330020b741ac 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/routes.js +++ b/x-pack/plugins/canvas/public/apps/workpad/routes.js @@ -25,7 +25,7 @@ export const routes = [ { name: 'createWorkpad', path: '/create', - action: dispatch => async ({ router }) => { + action: (dispatch) => async ({ router }) => { const newWorkpad = getDefaultWorkpad(); try { await workpadService.create(newWorkpad); diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js index f0a5325baf3c0..ac50cd3fb99b6 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js +++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js @@ -15,7 +15,7 @@ import { withElementsLoadedTelemetry } from './workpad_telemetry'; export { WORKPAD_CONTAINER_ID } from './workpad_app'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { const appReady = getAppReady(state); return { @@ -25,7 +25,7 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ deselectElement(ev) { ev && ev.stopPropagation(); dispatch(selectToplevelNodes([])); diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx index 5f3f114092e61..47b461f22ad65 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx +++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx @@ -57,10 +57,10 @@ function areAllElementsInResolvedArgs(workpad: Workpad, resolvedArgs: ResolvedAr const resolvedArgsElements = Object.keys(resolvedArgs); const workpadElements = workpad.pages.reduce((reduction, page) => { - return [...reduction, ...page.elements.map(element => element.id)]; + return [...reduction, ...page.elements.map((element) => element.id)]; }, []); - return workpadElements.every(element => resolvedArgsElements.includes(element)); + return workpadElements.every((element) => resolvedArgsElements.includes(element)); } export const withUnconnectedElementsLoadedTelemetry =

( diff --git a/x-pack/plugins/canvas/public/components/app/app.js b/x-pack/plugins/canvas/public/components/app/app.js index 16484afc8620b..5eb62f4bab950 100644 --- a/x-pack/plugins/canvas/public/components/app/app.js +++ b/x-pack/plugins/canvas/public/components/app/app.js @@ -65,7 +65,7 @@ export class App extends React.PureComponent { loadingMessage={strings.getLoadingMessage()} onRouteChange={this.props.onRouteChange} onLoad={() => this.props.setAppReady(true)} - onError={err => this.props.setAppError(err)} + onError={(err) => this.props.setAppError(err)} />

); diff --git a/x-pack/plugins/canvas/public/components/app/index.js b/x-pack/plugins/canvas/public/components/app/index.js index 750132dadb97d..a1e3b9c09554a 100644 --- a/x-pack/plugins/canvas/public/components/app/index.js +++ b/x-pack/plugins/canvas/public/components/app/index.js @@ -12,7 +12,7 @@ import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; import { App as Component } from './app'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { // appReady could be an error object const appState = getAppReady(state); @@ -22,7 +22,7 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ setAppReady: () => async () => { try { // set app state to ready @@ -31,7 +31,7 @@ const mapDispatchToProps = dispatch => ({ dispatch(appError(e)); } }, - setAppError: payload => dispatch(appError(payload)), + setAppError: (payload) => dispatch(appError(payload)), }); const mergeProps = (stateProps, dispatchProps, ownProps) => { @@ -46,7 +46,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { export const App = compose( connect(mapStateToProps, mapDispatchToProps, mergeProps), withKibana, - withProps(props => ({ + withProps((props) => ({ onRouteChange: props.kibana.services.canvas.navLink.updatePath, })) )(Component); diff --git a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx index 9c66043454585..c26fdb8c46d0f 100644 --- a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx +++ b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx @@ -45,7 +45,7 @@ export const ArgAddPopover = ({ options }: Props) => { button={button} > {({ closePopover }: PopoverChildrenProps) => - options.map(opt => ( + options.map((opt) => ( { +export const AdvancedFailureComponent = (props) => { const { onValueChange, defaultValue, @@ -26,7 +26,7 @@ export const AdvancedFailureComponent = props => { argId, } = props; - const valueChange = ev => { + const valueChange = (ev) => { ev.preventDefault(); resetErrorState(); // when setting a new value, attempt to reset the error state @@ -36,7 +36,7 @@ export const AdvancedFailureComponent = props => { } }; - const confirmReset = ev => { + const confirmReset = (ev) => { ev.preventDefault(); resetErrorState(); // when setting a new value, attempt to reset the error state onValueChange(fromExpression(defaultValue, 'argument')); @@ -61,7 +61,7 @@ export const AdvancedFailureComponent = props => {
- valueChange(e)} size="s" type="submit"> + valueChange(e)} size="s" type="submit"> {strings.getApplyButtonLabel()} {defaultValue && defaultValue.length && ( @@ -91,7 +91,7 @@ export const AdvancedFailure = compose( })), createStatefulPropHoc('argExpression', 'updateArgExpression'), withPropsOnChange(['argExpression'], ({ argExpression }) => ({ - valid: (function() { + valid: (function () { try { fromExpression(argExpression, 'argument'); return true; diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js index cff38431deb15..dfd99b18646a6 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js +++ b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js @@ -80,7 +80,7 @@ class ArgFormComponent extends PureComponent { }); }, error: hasError, - setLabel: label => this._isMounted && setLabel(label), + setLabel: (label) => this._isMounted && setLabel(label), resetErrorState: () => { resetErrorState(); this._isMounted && setRenderError(false); diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_label.js b/x-pack/plugins/canvas/public/components/arg_form/arg_label.js index 4324eed0892a5..fa0391952d151 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/arg_label.js +++ b/x-pack/plugins/canvas/public/components/arg_form/arg_label.js @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import { EuiFormRow, EuiAccordion, EuiText, EuiToolTip, EuiIcon } from '@elastic/eui'; // This is what is being generated by render() from the Arg class. It is called in FunctionForm -export const ArgLabel = props => { +export const ArgLabel = (props) => { const { argId, className, label, help, expandable, children, simpleArg, initialIsOpen } = props; return ( diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.js b/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.js index 947c3741c735b..7cd30a8987739 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.js +++ b/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.js @@ -47,7 +47,7 @@ class ArgTemplateFormComponent extends React.Component { _domNode = null; - _renderTemplate = domNode => { + _renderTemplate = (domNode) => { const { template, argumentProps, handlers } = this.props; if (template) { return template(domNode, argumentProps, handlers); @@ -72,7 +72,7 @@ class ArgTemplateFormComponent extends React.Component { return ( { + render={(domNode) => { this._domNode = domNode; this._renderTemplate(domNode); }} diff --git a/x-pack/plugins/canvas/public/components/arg_form/index.js b/x-pack/plugins/canvas/public/components/arg_form/index.js index 017ecae3103da..b6e30f9e52c60 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/index.js +++ b/x-pack/plugins/canvas/public/components/arg_form/index.js @@ -25,7 +25,7 @@ export const ArgForm = compose( } }, }), - connect(state => ({ workpad: getWorkpadInfo(state), assets: getAssets(state) })) + connect((state) => ({ workpad: getWorkpadInfo(state), assets: getAssets(state) })) )(Component); ArgForm.propTypes = { diff --git a/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js b/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js index 516ac729935e7..f9a855554db7d 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js +++ b/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js @@ -33,7 +33,7 @@ export class PendingArgValue extends React.PureComponent { setResolvedArgValue(null); } else { argResolver(argValue) - .then(val => setResolvedArgValue(val != null ? val : null)) + .then((val) => setResolvedArgValue(val != null ? val : null)) .catch(() => setResolvedArgValue(null)); // swallow error, it's not important } } diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.examples.tsx b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.examples.tsx index 728f228d989fb..9a6147050bdbb 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.examples.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.examples.tsx @@ -27,7 +27,7 @@ const MARKER: AssetType = { }; storiesOf('components/Assets/Asset', module) - .addDecorator(story =>
{story()}
) + .addDecorator((story) =>
{story()}
) .add('airplane', () => ( void; } -export const Asset: FunctionComponent = props => { +export const Asset: FunctionComponent = (props) => { const { asset, onCreate, onCopy, onDelete } = props; const createImage = ( diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.tsx index c27f0c002c3d1..cb177591fd650 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.tsx @@ -104,7 +104,7 @@ export class AssetManager extends PureComponent { private handleFileUpload = (files: FileList | null) => { if (files == null) return; this.setState({ isLoading: true }); - Promise.all(Array.from(files).map(file => this.props.onAssetAdd(file))).finally(() => { + Promise.all(Array.from(files).map((file) => this.props.onAssetAdd(file))).finally(() => { this.setState({ isLoading: false }); }); }; diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx index 8637db8e9f962..c02fc440abb0b 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx @@ -52,7 +52,7 @@ interface Props { onAssetDelete: (asset: AssetType) => void; } -export const AssetModal: FunctionComponent = props => { +export const AssetModal: FunctionComponent = (props) => { const { assetValues, isLoading, @@ -114,7 +114,7 @@ export const AssetModal: FunctionComponent = props => { {assetValues.length ? ( - {assetValues.map(asset => ( + {assetValues.map((asset) => ( { const [type, subtype] = get(file, 'type', '').split('/'); if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { - return encode(file).then(dataurl => { + return encode(file).then((dataurl) => { const dataurlType = 'dataurl'; const existingId = findExistingAsset(dataurlType, dataurl, assetValues); if (existingId) { diff --git a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx index 0d7e90c500695..4489e877abf88 100644 --- a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx +++ b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx @@ -46,7 +46,7 @@ export class AssetPicker extends PureComponent { return ( - {assets.map(asset => ( + {assets.map((asset) => ( { + onKeyDown = (e) => { const { ESCAPE, TAB, ENTER, UP, DOWN, LEFT, RIGHT } = keyCodes; const { keyCode } = e; const { items } = this.props; @@ -222,14 +222,14 @@ export class Autocomplete extends React.Component {
(this.containerRef = ref)} + ref={(ref) => (this.containerRef = ref)} role="listbox" > {header} {items.map((item, i) => (
(this.itemRefs[i] = ref)} + ref={(ref) => (this.itemRefs[i] = ref)} className={ 'autocompleteItem' + (this.state.selectedIndex === i ? ' autocompleteItem--isActive' : '') diff --git a/x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.stories.tsx b/x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.stories.tsx index 005a2541cbc2e..672580416c286 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.stories.tsx +++ b/x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.stories.tsx @@ -22,7 +22,7 @@ class Interactive extends React.Component<{}, { hasButtons: boolean; value: stri hasButtons={this.state.hasButtons} onAddColor={action('onAddColor')} onRemoveColor={action('onRemoveColor')} - onChange={value => this.setState({ value })} + onChange={(value) => this.setState({ value })} value={this.state.value} />

diff --git a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx index c9db9a9ba3468..8855bffc5e771 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx +++ b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx @@ -75,7 +75,7 @@ export const ColorManager: FunctionComponent = ({ value={value} isInvalid={!validColor && value.length > 0} placeholder={strings.getCodePlaceholder()} - onChange={e => onChange(e.target.value)} + onChange={(e) => onChange(e.target.value)} /> {buttons} diff --git a/x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.stories.tsx b/x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.stories.tsx index 14cd5e6d13b4f..0aef14104030e 100644 --- a/x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.stories.tsx +++ b/x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.stories.tsx @@ -21,7 +21,7 @@ class Interactive extends React.Component<{}, { value: string }> { return ( this.setState({ value })} + onChange={(value) => this.setState({ value })} value={this.state.value} /> ); diff --git a/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx index 90f3f8860b4ae..09bc08f9ae541 100644 --- a/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx +++ b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx @@ -42,14 +42,14 @@ export const ColorPalette: FunctionComponent = ({ return null; } - colors = colors.filter(color => { + colors = colors.filter((color) => { return tinycolor(color).isValid(); }); return (

- {color => { + {(color) => { const match = tinycolor.equals(color, value); const icon = match ? ( diff --git a/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx b/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx index a2825b210ee33..0a7ed75ee728e 100644 --- a/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx +++ b/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx @@ -28,11 +28,11 @@ class Interactive extends React.Component<
this.setState({ colors: this.state.colors.concat(value) })} - onRemoveColor={value => - this.setState({ colors: this.state.colors.filter(color => color !== value) }) + onAddColor={(value) => this.setState({ colors: this.state.colors.concat(value) })} + onRemoveColor={(value) => + this.setState({ colors: this.state.colors.filter((color) => color !== value) }) } - onChange={value => this.setState({ value })} + onChange={(value) => this.setState({ value })} hasButtons={this.state.hasButtons} value={this.state.value} /> diff --git a/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx index 2cd93b6f0e5c7..2bf17301b7b38 100644 --- a/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx +++ b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx @@ -29,7 +29,7 @@ export const ColorPicker: FunctionComponent = ({ const tc = tinycolor(value); const isValidColor = tc.isValid(); - colors = colors.filter(color => { + colors = colors.filter((color) => { return tinycolor(color).isValid(); }); @@ -37,7 +37,7 @@ export const ColorPicker: FunctionComponent = ({ let canAdd = false; if (isValidColor) { - const match = colors.filter(color => tinycolor.equals(value, color)); + const match = colors.filter((color) => tinycolor.equals(value, color)); canRemove = match.length > 0; canAdd = match.length === 0; } diff --git a/x-pack/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.stories.tsx b/x-pack/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.stories.tsx index 83923d1f0da1f..63854b8045e93 100644 --- a/x-pack/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.stories.tsx +++ b/x-pack/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.stories.tsx @@ -27,7 +27,7 @@ class Interactive extends React.Component<
this.setState({ value })} + onChange={(value) => this.setState({ value })} onAddColor={action('onAddColor')} onRemoveColor={action('onRemoveColor')} value={this.state.value} diff --git a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx index f4b0264911734..1be587c31528f 100644 --- a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx +++ b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx @@ -20,7 +20,7 @@ interface Props { className?: string; } -export const ConfirmModal: FunctionComponent = props => { +export const ConfirmModal: FunctionComponent = (props) => { const { isOpen, title, diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx index 56bd0bf5e9f2a..8f73939de69a6 100644 --- a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx +++ b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx @@ -137,7 +137,7 @@ export class CustomElementModal extends PureComponent { + onChange={(e) => e.target.value.length <= MAX_NAME_LENGTH && this._handleChange('name', e.target.value) } @@ -154,7 +154,7 @@ export class CustomElementModal extends PureComponent { + onChange={(e) => e.target.value.length <= MAX_DESCRIPTION_LENGTH && this._handleChange('description', e.target.value) } diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js index 285b69f057cd8..de9d192e4608c 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js @@ -51,7 +51,7 @@ export class DatasourceComponent extends PureComponent { state = { defaultIndex: '' }; componentDidMount() { - getDefaultIndex().then(defaultIndex => this.setState({ defaultIndex })); + getDefaultIndex().then((defaultIndex) => this.setState({ defaultIndex })); } componentDidUpdate(prevProps) { @@ -71,7 +71,7 @@ export class DatasourceComponent extends PureComponent { type: 'function', }); - setSelectedDatasource = value => { + setSelectedDatasource = (value) => { const { datasource, resetArgs, @@ -88,7 +88,7 @@ export class DatasourceComponent extends PureComponent { // otherwise, clear the arguments, the form will update them updateArgs && updateArgs({}); } - selectDatasource && selectDatasource(datasources.find(d => d.name === value)); + selectDatasource && selectDatasource(datasources.find((d) => d.name === value)); setSelecting(false); }; diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_selector.js b/x-pack/plugins/canvas/public/components/datasource/datasource_selector.js index 153a8a7ef75e6..528c37e46caeb 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_selector.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_selector.js @@ -10,7 +10,7 @@ import { EuiCard, EuiIcon } from '@elastic/eui'; export const DatasourceSelector = ({ onSelect, datasources, current }) => (
- {datasources.map(d => ( + {datasources.map((d) => ( ({ +const mapStateToProps = (state) => ({ element: getSelectedElement(state), pageId: getSelectedPage(state), functionDefinitions: getServerFunctions(state), }); -const mapDispatchToProps = dispatch => ({ - dispatchArgumentAtIndex: props => arg => dispatch(setArgumentAtIndex({ ...props, arg })), - dispatchAstAtIndex: ({ index, element, pageId }) => ast => { +const mapDispatchToProps = (dispatch) => ({ + dispatchArgumentAtIndex: (props) => (arg) => dispatch(setArgumentAtIndex({ ...props, arg })), + dispatchAstAtIndex: ({ index, element, pageId }) => (ast) => { dispatch(flushContext(element.id)); dispatch(setAstAtIndex(index, ast, element, pageId)); }, @@ -32,8 +32,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { const { element, pageId, functionDefinitions } = stateProps; const { dispatchArgumentAtIndex, dispatchAstAtIndex } = dispatchProps; - const getDataTableFunctionsByName = name => - functionDefinitions.find(fn => fn.name === name && fn.type === 'datatable'); + const getDataTableFunctionsByName = (name) => + functionDefinitions.find((fn) => fn.name === name && fn.type === 'datatable'); // find the matching datasource from the expression AST const datasourceAst = get(element, 'ast.chain', []) diff --git a/x-pack/plugins/canvas/public/components/datatable/datatable.js b/x-pack/plugins/canvas/public/components/datatable/datatable.js index e0cd3378ff65c..2b7c7464f7b84 100644 --- a/x-pack/plugins/canvas/public/components/datatable/datatable.js +++ b/x-pack/plugins/canvas/public/components/datatable/datatable.js @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import moment from 'moment'; import { Paginate } from '../paginate'; -const getIcon = type => { +const getIcon = (type) => { if (type === null) { return; } @@ -36,9 +36,9 @@ const getIcon = type => { return ; }; -const getColumnName = col => (typeof col === 'string' ? col : col.name); +const getColumnName = (col) => (typeof col === 'string' ? col : col.name); -const getColumnType = col => col.type || null; +const getColumnType = (col) => col.type || null; const getFormattedValue = (val, type) => { if (type === 'date') { @@ -56,7 +56,7 @@ export const Datatable = ({ datatable, perPage, paginate, showHeader }) => ( {!showHeader ? null : (
- {datatable.columns.map(col => ( + {datatable.columns.map((col) => ( {rows.map((row, i) => ( - {datatable.columns.map(col => ( + {datatable.columns.map((col) => ( diff --git a/x-pack/plugins/canvas/public/components/dom_preview/dom_preview.js b/x-pack/plugins/canvas/public/components/dom_preview/dom_preview.js index 172792b429fcd..808008e5664d6 100644 --- a/x-pack/plugins/canvas/public/components/dom_preview/dom_preview.js +++ b/x-pack/plugins/canvas/public/components/dom_preview/dom_preview.js @@ -86,13 +86,13 @@ export class DomPreview extends React.Component { render() { return (
{ + ref={(container) => { this._container = container; }} className="dom-preview" >
{ + ref={(content) => { this._content = content; }} /> diff --git a/x-pack/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx b/x-pack/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx index efb28d34d719e..1ed3e0788c313 100644 --- a/x-pack/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx +++ b/x-pack/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx @@ -11,7 +11,7 @@ import { ElementCard } from '../element_card'; import { elasticLogo } from '../../../lib/elastic_logo'; storiesOf('components/Elements/ElementCard', module) - .addDecorator(story => ( + .addDecorator((story) => (
({ +const mapStateToProps = (state) => ({ elementStats: getElementStats(state), }); diff --git a/x-pack/plugins/canvas/public/components/element_content/index.js b/x-pack/plugins/canvas/public/components/element_content/index.js index 3d7cca95e10b7..a138c3acb8ec7 100644 --- a/x-pack/plugins/canvas/public/components/element_content/index.js +++ b/x-pack/plugins/canvas/public/components/element_content/index.js @@ -12,7 +12,7 @@ import { getSelectedPage, getPageById } from '../../state/selectors/workpad'; import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; import { ElementContent as Component } from './element_content'; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ backgroundColor: getPageById(state, getSelectedPage(state)).style.background, }); diff --git a/x-pack/plugins/canvas/public/components/element_share_container/element_share_container.js b/x-pack/plugins/canvas/public/components/element_share_container/element_share_container.js index ab023aef9520f..3429c40073443 100644 --- a/x-pack/plugins/canvas/public/components/element_share_container/element_share_container.js +++ b/x-pack/plugins/canvas/public/components/element_share_container/element_share_container.js @@ -91,7 +91,7 @@ export class ElementShareContainer extends React.PureComponent { data-shared-item={shouldTrackComplete ? this.state.renderComplete : undefined} data-render-complete={shouldTrackComplete ? this.state.renderComplete : undefined} className={this.props.className} - ref={ref => (this.sharedItemRef = ref)} + ref={(ref) => (this.sharedItemRef = ref)} > {this.props.children}
diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js b/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js index 56a8a148bf915..845fc5927d839 100644 --- a/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js +++ b/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import { Positionable } from '../positionable'; import { ElementContent } from '../element_content'; -export const ElementWrapper = props => { +export const ElementWrapper = (props) => { const { renderable, transformMatrix, width, height, state, handlers } = props; return ( diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/index.js b/x-pack/plugins/canvas/public/components/element_wrapper/index.js index 85882377b7684..390c349ab2ee6 100644 --- a/x-pack/plugins/canvas/public/components/element_wrapper/index.js +++ b/x-pack/plugins/canvas/public/components/element_wrapper/index.js @@ -59,14 +59,14 @@ export const ElementWrapper = compose( connectAdvanced(selectorFactory), withPropsOnChange( (props, nextProps) => !isEqual(props.element, nextProps.element), - props => { + (props) => { const { element, createHandlers } = props; const handlers = createHandlers(element); // this removes element and createHandlers from passed props return { handlers }; } ), - mapProps(props => { + mapProps((props) => { // remove element and createHandlers from props passed to component // eslint-disable-next-line no-unused-vars const { element, createHandlers, selectedPage, ...restProps } = props; diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js b/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js index 8ea90974e2c53..33e8eacd902dd 100644 --- a/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js +++ b/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js @@ -11,12 +11,12 @@ import { fetchEmbeddableRenderable, } from '../../../state/actions/embeddable'; -export const createHandlers = dispatch => { +export const createHandlers = (dispatch) => { let isComplete = false; let oldElement; let completeFn = () => {}; - return element => { + return (element) => { // reset isComplete when element changes if (!isEqual(oldElement, element)) { isComplete = false; diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx index 35eedde59a014..df9dad3e7f678 100644 --- a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx +++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx @@ -30,7 +30,7 @@ export class AddEmbeddableFlyout extends React.Component { const embeddableFactories = this.props.getEmbeddableFactories(); // Find the embeddable type from the saved object type - const found = Array.from(embeddableFactories).find(embeddableFactory => { + const found = Array.from(embeddableFactories).find((embeddableFactory) => { return Boolean( embeddableFactory.savedObjectMetaData && embeddableFactory.savedObjectMetaData.type === savedObjectType @@ -46,11 +46,11 @@ export class AddEmbeddableFlyout extends React.Component { const embeddableFactories = this.props.getEmbeddableFactories(); const availableSavedObjects = Array.from(embeddableFactories) - .filter(factory => { + .filter((factory) => { return this.props.availableEmbeddables.includes(factory.type); }) - .map(factory => factory.savedObjectMetaData) - .filter>(function( + .map((factory) => factory.savedObjectMetaData) + .filter>(function ( maybeSavedObjectMetaData ): maybeSavedObjectMetaData is SavedObjectMetaData<{}> { return maybeSavedObjectMetaData !== undefined; diff --git a/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx b/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx index e24ae57cbc606..134efe61c9dcb 100644 --- a/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx +++ b/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx @@ -27,7 +27,7 @@ interface ComponentProps extends Props { children: (props: Props) => ReactChildren; } -const ErrorBoundaryComponent: FunctionComponent = props => ( +const ErrorBoundaryComponent: FunctionComponent = (props) => ( {props.children({ error: props.error, @@ -68,7 +68,7 @@ export const errorBoundaryHoc = compose( this.props.setErrorInfo(errorInfo); }, }), - mapProps>(props => + mapProps>((props) => omit(props, ['setError', 'setErrorInfo']) ) ); diff --git a/x-pack/plugins/canvas/public/components/enhance/stateful_prop.js b/x-pack/plugins/canvas/public/components/enhance/stateful_prop.js index 9e035db80b4d9..1f865f9b9c6b7 100644 --- a/x-pack/plugins/canvas/public/components/enhance/stateful_prop.js +++ b/x-pack/plugins/canvas/public/components/enhance/stateful_prop.js @@ -7,10 +7,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -const getDisplayName = Comp => Comp.displayName || Comp.name || 'UnnamedComponent'; +const getDisplayName = (Comp) => Comp.displayName || Comp.name || 'UnnamedComponent'; export function createStatefulPropHoc(fieldname, updater = 'updateValue') { - return Comp => { + return (Comp) => { class WrappedControlledInput extends React.PureComponent { constructor(props) { super(props); @@ -24,7 +24,7 @@ export function createStatefulPropHoc(fieldname, updater = 'updateValue') { this.setState({ value: nextProps[fieldname] }); } - handleChange = ev => { + handleChange = (ev) => { if (ev.target) { this.setState({ value: ev.target.value }); } else { diff --git a/x-pack/plugins/canvas/public/components/es_field_select/es_field_select.js b/x-pack/plugins/canvas/public/components/es_field_select/es_field_select.js index 11c8ab88a4cba..16c94a44299a4 100644 --- a/x-pack/plugins/canvas/public/components/es_field_select/es_field_select.js +++ b/x-pack/plugins/canvas/public/components/es_field_select/es_field_select.js @@ -11,14 +11,14 @@ import { get } from 'lodash'; export const ESFieldSelect = ({ value, fields = [], onChange, onFocus, onBlur }) => { const selectedOption = value ? [{ label: value }] : []; - const options = fields.map(field => ({ label: field })); + const options = fields.map((field) => ({ label: field })); return ( onChange(get(field, 'label', null))} - onSearchChange={searchValue => { + onSearchChange={(searchValue) => { // resets input when user starts typing if (searchValue) { onChange(null); diff --git a/x-pack/plugins/canvas/public/components/es_field_select/index.js b/x-pack/plugins/canvas/public/components/es_field_select/index.js index 2dc2484083241..e2e86a2a4aea3 100644 --- a/x-pack/plugins/canvas/public/components/es_field_select/index.js +++ b/x-pack/plugins/canvas/public/components/es_field_select/index.js @@ -19,7 +19,7 @@ export const ESFieldSelect = compose( componentDidUpdate({ index }) { const { value, onChange, setFields } = this.props; if (this.props.index !== index) { - getFields(this.props.index).then(fields => { + getFields(this.props.index).then((fields) => { setFields(fields); }); } diff --git a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.js b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.js index ca2cac5a64793..0b067d94c8a9b 100644 --- a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.js +++ b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.js @@ -9,11 +9,11 @@ import PropTypes from 'prop-types'; import { EuiComboBox } from '@elastic/eui'; export const ESFieldsSelect = ({ selected, fields, onChange, onFocus, onBlur }) => { - const options = fields.map(value => ({ + const options = fields.map((value) => ({ label: value, })); - const selectedOptions = selected.map(value => ({ + const selectedOptions = selected.map((value) => ({ label: value, })); @@ -21,7 +21,7 @@ export const ESFieldsSelect = ({ selected, fields, onChange, onFocus, onBlur }) onChange(values.map(({ label }) => label))} + onChange={(values) => onChange(values.map(({ label }) => label))} className="canvasFieldsSelect" onFocus={onFocus} onBlur={onBlur} diff --git a/x-pack/plugins/canvas/public/components/es_fields_select/index.js b/x-pack/plugins/canvas/public/components/es_fields_select/index.js index aa607a9ebea4a..13b6f4558e6be 100644 --- a/x-pack/plugins/canvas/public/components/es_fields_select/index.js +++ b/x-pack/plugins/canvas/public/components/es_fields_select/index.js @@ -21,7 +21,7 @@ export const ESFieldsSelect = compose( if (this.props.index !== index) { getFields(this.props.index).then((fields = []) => { setFields(fields); - onChange(selected.filter(option => fields.includes(option))); + onChange(selected.filter((option) => fields.includes(option))); }); } }, diff --git a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.js b/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.js index 8f1a4932a5e6c..9bd4966b91891 100644 --- a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.js +++ b/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.js @@ -13,13 +13,13 @@ const defaultIndex = '_all'; export const ESIndexSelect = ({ value, loading, indices, onChange, onFocus, onBlur }) => { const selectedOption = value !== defaultIndex ? [{ label: value }] : []; - const options = indices.map(index => ({ label: index })); + const options = indices.map((index) => ({ label: index })); return ( onChange(get(index, 'label', defaultIndex))} - onSearchChange={searchValue => { + onSearchChange={(searchValue) => { // resets input when user starts typing if (searchValue) { onChange(defaultIndex); @@ -31,7 +31,7 @@ export const ESIndexSelect = ({ value, loading, indices, onChange, onFocus, onBl options={options} singleSelection={{ asPlainText: true }} isClearable={false} - onCreateOption={input => onChange(input || defaultIndex)} + onCreateOption={(input) => onChange(input || defaultIndex)} compressed /> ); diff --git a/x-pack/plugins/canvas/public/components/expression/expression.js b/x-pack/plugins/canvas/public/components/expression/expression.js index f2b7259f8f1f8..37cf1b821d9fd 100644 --- a/x-pack/plugins/canvas/public/components/expression/expression.js +++ b/x-pack/plugins/canvas/public/components/expression/expression.js @@ -28,7 +28,7 @@ const { useRef } = React; const shortcut = (ref, cmd, callback) => ( { + handler={(command) => { const isInputActive = ref.current && ref.current.editor && ref.current.editor.hasTextFocus(); if (isInputActive && command === cmd) { callback(); diff --git a/x-pack/plugins/canvas/public/components/expression/index.js b/x-pack/plugins/canvas/public/components/expression/index.js index c2b559fe03230..4480169dd037d 100644 --- a/x-pack/plugins/canvas/public/components/expression/index.js +++ b/x-pack/plugins/canvas/public/components/expression/index.js @@ -21,13 +21,13 @@ import { setExpression, flushContext } from '../../state/actions/elements'; import { ElementNotSelected } from './element_not_selected'; import { Expression as Component } from './expression'; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ pageId: getSelectedPage(state), element: getSelectedElement(state), }); -const mapDispatchToProps = dispatch => ({ - setExpression: (elementId, pageId) => expression => { +const mapDispatchToProps = (dispatch) => ({ + setExpression: (elementId, pageId) => (expression) => { // destroy the context cache dispatch(flushContext(elementId)); @@ -82,14 +82,14 @@ export const Expression = compose( toggleCompactView: ({ isCompact, setCompact }) => () => { setCompact(!isCompact); }, - updateValue: ({ setFormState }) => expression => { + updateValue: ({ setFormState }) => (expression) => { setFormState({ expression, dirty: true, }); }, - setExpression: ({ setExpression, setFormState }) => exp => { - setFormState(prev => ({ + setExpression: ({ setExpression, setFormState }) => (exp) => { + setFormState((prev) => ({ ...prev, dirty: false, })); @@ -98,7 +98,7 @@ export const Expression = compose( }), expressionLifecycle, withPropsOnChange(['formState'], ({ formState }) => ({ - error: (function() { + error: (function () { try { // TODO: We should merge the advanced UI input and this into a single validated expression input. fromExpression(formState.expression); @@ -108,5 +108,5 @@ export const Expression = compose( } })(), })), - branch(props => !props.element, renderComponent(ElementNotSelected)) + branch((props) => !props.element, renderComponent(ElementNotSelected)) )(Component); diff --git a/x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.examples.tsx b/x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.examples.tsx index 51caf1db196bc..d010f4a554b87 100644 --- a/x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.examples.tsx +++ b/x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.examples.tsx @@ -7,7 +7,7 @@ import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import React from 'react'; -import { monaco } from '@kbn/ui-shared-deps/monaco'; +import { monaco } from '@kbn/monaco'; import { ExpressionInput } from '../expression_input'; import { language, LANGUAGE_ID } from '../../../lib/monaco_language_def'; diff --git a/x-pack/plugins/canvas/public/components/expression_input/expression_input.tsx b/x-pack/plugins/canvas/public/components/expression_input/expression_input.tsx index 99e12b14104be..5ada495208fba 100644 --- a/x-pack/plugins/canvas/public/components/expression_input/expression_input.tsx +++ b/x-pack/plugins/canvas/public/components/expression_input/expression_input.tsx @@ -8,7 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiFormRow } from '@elastic/eui'; import { debounce } from 'lodash'; -import { monaco } from '@kbn/ui-shared-deps/monaco'; +import { monaco } from '@kbn/monaco'; import { ExpressionFunction } from '../../../types'; import { CodeEditor } from '../../../../../../src/plugins/kibana_react/public'; import { diff --git a/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx b/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx index 2650b5ebb8fb3..993ee8bde2653 100644 --- a/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx +++ b/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx @@ -18,7 +18,7 @@ interface Props { onUpload: () => void; } -export const FileUpload: FunctionComponent = props => ( +export const FileUpload: FunctionComponent = (props) => ( ); diff --git a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx index 01685cf78d563..4340430829342 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx +++ b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx @@ -20,14 +20,14 @@ interface Props { value?: FontValue; } -export const FontPicker: FunctionComponent = props => { +export const FontPicker: FunctionComponent = (props) => { const { value, onSelect } = props; // While fonts are strongly-typed, we also support custom fonts someone might type in. // So let's cast the fonts and allow for additions. const displayedFonts: DisplayedFont[] = fonts; - if (value && !fonts.find(font => font.value === value)) { + if (value && !fonts.find((font) => font.value === value)) { const label = (value.indexOf(',') >= 0 ? value.split(',')[0] : value).replace(/['"]/g, ''); displayedFonts.push({ value, label }); displayedFonts.sort((a, b) => a.label.localeCompare(b.label)); @@ -36,7 +36,7 @@ export const FontPicker: FunctionComponent = props => { return ( ({ + options={displayedFonts.map((font) => ({ value: font.value, inputDisplay:
{font.label}
, }))} diff --git a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss index c198884ee7131..7110a22408fe2 100644 --- a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss +++ b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss @@ -1,5 +1,5 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements - // following two rules are for overriding the header bar padding + // following two rules are for overriding the header bar padding &.euiBody--headerIsFixed { padding-top: 0; } @@ -8,6 +8,12 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements min-height: 100vh; } + // following rule is for docked navigation + &.euiBody--collapsibleNavIsDocked { + padding-left: 0 !important; // sass-lint:disable-line no-important + } + + // hide global loading indicator .kbnLoadingIndicator { display: none; diff --git a/x-pack/plugins/canvas/public/components/fullscreen/index.js b/x-pack/plugins/canvas/public/components/fullscreen/index.js index 8087644b51655..f6404d4097699 100644 --- a/x-pack/plugins/canvas/public/components/fullscreen/index.js +++ b/x-pack/plugins/canvas/public/components/fullscreen/index.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { getFullscreen } from '../../state/selectors/app'; import { Fullscreen as Component } from './fullscreen'; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ isFullscreen: getFullscreen(state), }); diff --git a/x-pack/plugins/canvas/public/components/function_form/function_form.js b/x-pack/plugins/canvas/public/components/function_form/function_form.js index 5c8b745d17bd7..8c9f8847d8eed 100644 --- a/x-pack/plugins/canvas/public/components/function_form/function_form.js +++ b/x-pack/plugins/canvas/public/components/function_form/function_form.js @@ -22,7 +22,7 @@ function checkState(state) { // alternate render paths based on expression state const branches = [ // if no expressionType was provided, render the ArgTypeUnknown component - branch(props => !props.expressionType, renderComponent(FunctionUnknown)), + branch((props) => !props.expressionType, renderComponent(FunctionUnknown)), // if the expressionType is in a pending state, render ArgTypeContextPending branch(checkState('pending'), renderComponent(FunctionFormContextPending)), // if the expressionType is in an error state, render ArgTypeContextError diff --git a/x-pack/plugins/canvas/public/components/function_form/function_form_component.js b/x-pack/plugins/canvas/public/components/function_form/function_form_component.js index a076cc8dd714c..8503acf21bc1c 100644 --- a/x-pack/plugins/canvas/public/components/function_form/function_form_component.js +++ b/x-pack/plugins/canvas/public/components/function_form/function_form_component.js @@ -7,7 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -export const FunctionFormComponent = props => { +export const FunctionFormComponent = (props) => { const passedProps = { argResolver: props.argResolver, args: props.args, diff --git a/x-pack/plugins/canvas/public/components/function_form/index.js b/x-pack/plugins/canvas/public/components/function_form/index.js index 774214cf68cec..2b563c30bf50e 100644 --- a/x-pack/plugins/canvas/public/components/function_form/index.js +++ b/x-pack/plugins/canvas/public/components/function_form/index.js @@ -39,8 +39,8 @@ const mapDispatchToProps = (dispatch, { expressionIndex }) => ({ addArgumentValueAtIndex({ index: expressionIndex, element, pageId, argName, value: argValue }) ); }, - updateContext: element => () => dispatch(fetchContext(expressionIndex, element)), - setArgument: (element, pageId) => (argName, valueIndex) => value => { + updateContext: (element) => () => dispatch(fetchContext(expressionIndex, element)), + setArgument: (element, pageId) => (argName, valueIndex) => (value) => { dispatch( setArgumentAtIndex({ index: expressionIndex, diff --git a/x-pack/plugins/canvas/public/components/function_form_list/function_form_list.js b/x-pack/plugins/canvas/public/components/function_form_list/function_form_list.js index ae2e6404f0bb8..529b40b3dcbe5 100644 --- a/x-pack/plugins/canvas/public/components/function_form_list/function_form_list.js +++ b/x-pack/plugins/canvas/public/components/function_form_list/function_form_list.js @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import { FunctionForm } from '../function_form'; export const FunctionFormList = ({ functionFormItems }) => { - const argTypeComponents = functionFormItems.map(functionFormProps => { + const argTypeComponents = functionFormItems.map((functionFormProps) => { return ( { +const functionFormItems = withProps((props) => { const selectedElement = props.element; const FunctionFormChain = get(selectedElement, 'ast.chain', []); @@ -47,7 +47,7 @@ const functionFormItems = withProps(props => { args: argType.arguments, argType: argType.function, argTypeDef: argTypeDef, - argResolver: argAst => interpretAst(argAst, prevContext), + argResolver: (argAst) => interpretAst(argAst, prevContext), contextExpression: getExpression(prevContext), expressionIndex: i, // preserve the index in the AST nextArgType: nextArg && nextArg.function, diff --git a/x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx b/x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx index 94340888eafc8..42ff96c8cea15 100644 --- a/x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx +++ b/x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx @@ -12,17 +12,17 @@ import { ItemGrid } from '../item_grid'; storiesOf('components/ItemGrid', module) .add('simple grid', () => ( -
{item}
} /> +
{item}
} /> )) .add('icon grid', () => ( } + children={(item) => } /> )) .add('color dot grid', () => ( - {item => } + {(item) => } )) .add('complex grid', () => ( @@ -35,7 +35,7 @@ storiesOf('components/ItemGrid', module) ] as Array<{ color: string; icon: IconType }> } > - {item => ( + {(item) => ( diff --git a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx index 1c94969292b59..02aa78b5dfe00 100644 --- a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx +++ b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx @@ -55,7 +55,9 @@ const getDescriptionListItems = (shortcuts: ShortcutMap[]): DescriptionListItem[ {getPrettyShortcut(shortcut) .split(/( )/g) - .map(key => (key === ' ' ? key : {key}))} + .map((key) => + key === ' ' ? key : {key} + )} ); return acc; diff --git a/x-pack/plugins/canvas/public/components/link/link.js b/x-pack/plugins/canvas/public/components/link/link.js index ce709243ceebf..d973164190592 100644 --- a/x-pack/plugins/canvas/public/components/link/link.js +++ b/x-pack/plugins/canvas/public/components/link/link.js @@ -12,7 +12,7 @@ import { ComponentStrings } from '../../../i18n'; const { Link: strings } = ComponentStrings; -const isModifiedEvent = ev => !!(ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey); +const isModifiedEvent = (ev) => !!(ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey); export class Link extends React.PureComponent { static propTypes = { @@ -27,7 +27,7 @@ export class Link extends React.PureComponent { router: PropTypes.object, }; - navigateTo = (name, params) => ev => { + navigateTo = (name, params) => (ev) => { if (this.props.onClick) { this.props.onClick(ev); } diff --git a/x-pack/plugins/canvas/public/components/page_config/index.js b/x-pack/plugins/canvas/public/components/page_config/index.js index a51e6b4b5d987..7770154c8e477 100644 --- a/x-pack/plugins/canvas/public/components/page_config/index.js +++ b/x-pack/plugins/canvas/public/components/page_config/index.js @@ -14,7 +14,7 @@ import { PageConfig as Component } from './page_config'; const { PageConfig: strings } = ComponentStrings; -const mapStateToProps = state => { +const mapStateToProps = (state) => { const pageIndex = getSelectedPageIndex(state); const page = getPages(state)[pageIndex]; return { page, pageIndex }; @@ -25,7 +25,7 @@ const mapDispatchToProps = { stylePage, setPageTransition }; const mergeProps = (stateProps, dispatchProps) => { return { pageIndex: stateProps.pageIndex, - setBackground: background => { + setBackground: (background) => { const itsTheNewStyle = { ...stateProps.page.style, background }; dispatchProps.stylePage(stateProps.page.id, itsTheNewStyle); }, @@ -37,7 +37,7 @@ const mergeProps = (stateProps, dispatchProps) => { text: displayName, })) ), - setTransition: name => { + setTransition: (name) => { dispatchProps.setPageTransition(stateProps.page.id, { name }); }, }; diff --git a/x-pack/plugins/canvas/public/components/page_config/page_config.js b/x-pack/plugins/canvas/public/components/page_config/page_config.js index 583bf1427aab1..51a4762fca501 100644 --- a/x-pack/plugins/canvas/public/components/page_config/page_config.js +++ b/x-pack/plugins/canvas/public/components/page_config/page_config.js @@ -57,7 +57,7 @@ export const PageConfig = ({ value={transition ? transition.name : ''} options={transitions} compressed - onChange={e => setTransition(e.target.value)} + onChange={(e) => setTransition(e.target.value)} /> {transition ? ( diff --git a/x-pack/plugins/canvas/public/components/page_manager/index.js b/x-pack/plugins/canvas/public/components/page_manager/index.js index ef5de3feb575c..a198b7b8c3d8c 100644 --- a/x-pack/plugins/canvas/public/components/page_manager/index.js +++ b/x-pack/plugins/canvas/public/components/page_manager/index.js @@ -12,7 +12,7 @@ import { getSelectedPage, getWorkpad, getPages, isWriteable } from '../../state/ import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants'; import { PageManager as Component } from './page_manager'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { const { id, css } = getWorkpad(state); return { @@ -24,11 +24,11 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ addPage: () => dispatch(pageActions.addPage()), movePage: (id, position) => dispatch(pageActions.movePage(id, position)), - duplicatePage: id => dispatch(pageActions.duplicatePage(id)), - removePage: id => dispatch(pageActions.removePage(id)), + duplicatePage: (id) => dispatch(pageActions.duplicatePage(id)), + removePage: (id) => dispatch(pageActions.removePage(id)), }); export const PageManager = compose( diff --git a/x-pack/plugins/canvas/public/components/page_manager/page_manager.js b/x-pack/plugins/canvas/public/components/page_manager/page_manager.js index 1abb17c2c2b33..3e2ff9dfe2b22 100644 --- a/x-pack/plugins/canvas/public/components/page_manager/page_manager.js +++ b/x-pack/plugins/canvas/public/components/page_manager/page_manager.js @@ -94,7 +94,7 @@ export class PageManager extends React.PureComponent { } }; - confirmDelete = pageId => { + confirmDelete = (pageId) => { this._isMounted && this.props.setDeleteId(pageId); }; @@ -133,13 +133,13 @@ export class PageManager extends React.PureComponent { return ( - {provided => ( + {(provided) => (
{ + ref={(el) => { if (page.id === selectedPage) { this.activePageRef = el; } @@ -194,12 +194,12 @@ export class PageManager extends React.PureComponent { - {provided => ( + {(provided) => (
{ + ref={(el) => { this.pageListRef = el; provided.innerRef(el); }} diff --git a/x-pack/plugins/canvas/public/components/page_preview/page_controls.js b/x-pack/plugins/canvas/public/components/page_preview/page_controls.js index 727794e72ee15..1251f441f1082 100644 --- a/x-pack/plugins/canvas/public/components/page_preview/page_controls.js +++ b/x-pack/plugins/canvas/public/components/page_preview/page_controls.js @@ -13,12 +13,12 @@ import { ComponentStrings } from '../../../i18n'; const { PagePreviewPageControls: strings } = ComponentStrings; export const PageControls = ({ pageId, onDelete, onDuplicate }) => { - const handleDuplicate = ev => { + const handleDuplicate = (ev) => { ev.preventDefault(); onDuplicate(pageId); }; - const handleDelete = ev => { + const handleDelete = (ev) => { ev.preventDefault(); onDelete(pageId); }; diff --git a/x-pack/plugins/canvas/public/components/paginate/paginate.js b/x-pack/plugins/canvas/public/components/paginate/paginate.js index 52e744ec769e8..8dfa616ed8d14 100644 --- a/x-pack/plugins/canvas/public/components/paginate/paginate.js +++ b/x-pack/plugins/canvas/public/components/paginate/paginate.js @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; -export const Paginate = props => { +export const Paginate = (props) => { return props.children({ rows: props.partialRows, perPage: props.perPage, @@ -14,7 +14,7 @@ export const Paginate = props => { totalPages: props.totalPages, nextPageEnabled: props.nextPageEnabled, prevPageEnabled: props.prevPageEnabled, - setPage: num => props.setPage(num), + setPage: (num) => props.setPage(num), nextPage: props.nextPage, prevPage: props.prevPage, }); diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js index e5808dd623530..ca2a499feb84c 100644 --- a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js +++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js @@ -13,7 +13,7 @@ import { PaletteSwatch } from '../palette_swatch'; import { palettes } from '../../../common/lib/palettes'; export const PalettePicker = ({ onChange, value, anchorPosition, ariaLabel }) => { - const button = handleClick => ( + const button = (handleClick) => ( diff --git a/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js b/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js index fcc34c8d85448..71d16260e00c7 100644 --- a/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js +++ b/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js @@ -11,7 +11,7 @@ export const PaletteSwatch = ({ colors, gradient }) => { let colorBoxes; if (!gradient) { - colorBoxes = colors.map(color => ( + colorBoxes = colors.map((color) => (
{ // Throw if there is more than one child React.Children.only(children); // This could probably be made nicer by having just one child - const wrappedChildren = React.Children.map(children, child => { + const wrappedChildren = React.Children.map(children, (child) => { const newStyle = { width, height, diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js index cb9912e717173..db393a8dde4f9 100644 --- a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js +++ b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js @@ -28,7 +28,7 @@ export class RenderToDom extends React.Component { render() { const { domNode, setDomNode, style } = this.props; - const linkRef = refNode => { + const linkRef = (refNode) => { if (!domNode && refNode) { // Initialize the domNode property. This should only happen once, even if config changes. setDomNode(refNode); diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/index.js b/x-pack/plugins/canvas/public/components/render_with_fn/index.js index 473aaaf9d1ac6..37c49624a3940 100644 --- a/x-pack/plugins/canvas/public/components/render_with_fn/index.js +++ b/x-pack/plugins/canvas/public/components/render_with_fn/index.js @@ -20,7 +20,7 @@ export const RenderWithFn = compose( }) ), withKibana, - withProps(props => ({ + withProps((props) => ({ onError: props.kibana.services.canvas.notify.error, })) )(Component); diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.js b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.js index ce27e99256fc6..763cbd5e53eb1 100644 --- a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.js +++ b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.js @@ -94,7 +94,7 @@ export class RenderWithFn extends React.Component { } }; - _resetRenderTarget = domNode => { + _resetRenderTarget = (domNode) => { const { handlers } = this.props; if (!domNode) { @@ -122,7 +122,7 @@ export class RenderWithFn extends React.Component { return div; }; - _shouldFullRerender = prevProps => { + _shouldFullRerender = (prevProps) => { // required to stop re-renders on element move, anything that should // cause a re-render needs to be checked here // TODO: fix props passed in to remove this check @@ -146,7 +146,7 @@ export class RenderWithFn extends React.Component { > { + render={(domNode) => { this._domNode = domNode; this._callRenderFn(); }} diff --git a/x-pack/plugins/canvas/public/components/router/router.js b/x-pack/plugins/canvas/public/components/router/router.js index 40c9c469e5505..dd275b3949f34 100644 --- a/x-pack/plugins/canvas/public/components/router/router.js +++ b/x-pack/plugins/canvas/public/components/router/router.js @@ -43,7 +43,7 @@ export class Router extends React.PureComponent { let firstLoad = true; // when the component in the route changes, render it - router.onPathChange(route => { + router.onPathChange((route) => { const { pathname } = route.location; const { component } = route.meta; @@ -64,7 +64,7 @@ export class Router extends React.PureComponent { router .execute() .then(() => onLoad()) - .catch(err => onError(err)); + .catch((err) => onError(err)); } const appState = getAppState(); diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/element_controls.stories.tsx b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/element_controls.stories.tsx index 5210210ebaa74..ec0304dd63726 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/element_controls.stories.tsx +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/element_controls.stories.tsx @@ -10,7 +10,7 @@ import { action } from '@storybook/addon-actions'; import { ElementControls } from '../element_controls'; storiesOf('components/SavedElementsModal/ElementControls', module) - .addDecorator(story => ( + .addDecorator((story) => (
( + .addDecorator((story) => (
{} }: Props) => { {Object.keys(shapes) .sort() - .map(shapeKey => ( + .map((shapeKey) => ( onChange(shapeKey)}> diff --git a/x-pack/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx b/x-pack/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx index 389ee7791baba..4e3d8ae219d29 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx +++ b/x-pack/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx @@ -20,7 +20,7 @@ class Interactive extends React.Component<{}, { value: string }> { return ( this.setState({ value })} + onChange={(value) => this.setState({ value })} value={this.state.value} /> ); diff --git a/x-pack/plugins/canvas/public/components/sidebar/element_settings/index.tsx b/x-pack/plugins/canvas/public/components/sidebar/element_settings/index.tsx index b5e44b4996537..b8d5882234899 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/element_settings/index.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/element_settings/index.tsx @@ -21,7 +21,7 @@ interface StateProps { element: PositionedElement | undefined; } -const renderIfElement: React.FunctionComponent = props => { +const renderIfElement: React.FunctionComponent = (props) => { if (props.element) { return ; } diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js index f333705a1a3c6..a9b6b80b9ff01 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js @@ -18,7 +18,7 @@ import { ElementSettings } from './element_settings'; const { SidebarContent: strings } = ComponentStrings; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ selectedToplevelNodes: getSelectedToplevelNodes(state), selectedElementId: getSelectedElementId(state), }); diff --git a/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx b/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx index 11c66906a6ef6..5cd9133febc39 100644 --- a/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx @@ -17,7 +17,7 @@ const handlers = { }; storiesOf('components/Sidebar/SidebarHeader', module) - .addDecorator(story =>
{story()}
) + .addDecorator((story) =>
{story()}
) .add('default', () => ) .add('with layer controls', () => ( diff --git a/x-pack/plugins/canvas/public/components/sidebar_header/index.js b/x-pack/plugins/canvas/public/components/sidebar_header/index.js index ac282962afb54..aa67de1f55506 100644 --- a/x-pack/plugins/canvas/public/components/sidebar_header/index.js +++ b/x-pack/plugins/canvas/public/components/sidebar_header/index.js @@ -23,33 +23,33 @@ import { SidebarHeader as Component } from './sidebar_header'; /* * TODO: this is all copied from interactive_workpad_page and workpad_shortcuts */ -const mapStateToProps = state => { +const mapStateToProps = (state) => { const pageId = getSelectedPage(state); const nodes = getNodes(state, pageId); const selectedToplevelNodes = getSelectedToplevelNodes(state); const selectedPrimaryShapeObjects = selectedToplevelNodes - .map(id => nodes.find(s => s.id === id)) - .filter(shape => shape); + .map((id) => nodes.find((s) => s.id === id)) + .filter((shape) => shape); const selectedPersistentPrimaryNodes = flatten( - selectedPrimaryShapeObjects.map(shape => - nodes.find(n => n.id === shape.id) // is it a leaf or a persisted group? + selectedPrimaryShapeObjects.map((shape) => + nodes.find((n) => n.id === shape.id) // is it a leaf or a persisted group? ? [shape.id] - : nodes.filter(s => s.parent === shape.id).map(s => s.id) + : nodes.filter((s) => s.parent === shape.id).map((s) => s.id) ) ); const selectedNodeIds = flatten(selectedPersistentPrimaryNodes.map(crawlTree(nodes))); return { pageId, - selectedNodes: selectedNodeIds.map(id => nodes.find(s => s.id === id)), + selectedNodes: selectedNodeIds.map((id) => nodes.find((s) => s.id === id)), }; }; -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ insertNodes: (selectedNodes, pageId) => dispatch(insertNodes(selectedNodes, pageId)), removeNodes: (nodeIds, pageId) => dispatch(removeElements(nodeIds, pageId)), - selectToplevelNodes: nodes => - dispatch(selectToplevelNodes(nodes.filter(e => !e.position.parent).map(e => e.id))), + selectToplevelNodes: (nodes) => + dispatch(selectToplevelNodes(nodes.filter((e) => !e.position.parent).map((e) => e.id))), elementLayer: (pageId, elementId, movement) => { dispatch(elementLayer({ pageId, elementId, movement })); }, diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js index 179455e15b36e..48d52abb03125 100644 --- a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js +++ b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js @@ -85,9 +85,9 @@ export const TextStylePicker = ({ }); }; - const onAlignmentChange = optionId => doChange('align', optionId); + const onAlignmentChange = (optionId) => doChange('align', optionId); - const onStyleChange = optionId => { + const onStyleChange = (optionId) => { let prop; let value; @@ -106,14 +106,14 @@ export const TextStylePicker = ({
- doChange('family', value)} /> + doChange('family', value)} /> doChange('size', Number(e.target.value))} - options={fontSizes.map(size => ({ text: String(size), value: size }))} + onChange={(e) => doChange('size', Number(e.target.value))} + options={fontSizes.map((size) => ({ text: String(size), value: size }))} prepend="Size" /> @@ -125,7 +125,7 @@ export const TextStylePicker = ({ doChange('color', value)} + onChange={(value) => doChange('color', value)} colors={colors} ariaLabel={strings.getFontColorLabel()} /> diff --git a/x-pack/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.stories.tsx b/x-pack/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.stories.tsx index 01aaeb2388d6c..e9669b3014889 100644 --- a/x-pack/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.stories.tsx +++ b/x-pack/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.stories.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ToolTipShortcut } from '../tool_tip_shortcut'; storiesOf('components/ToolTipShortcut', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('with shortcut', () => ) diff --git a/x-pack/plugins/canvas/public/components/toolbar/index.js b/x-pack/plugins/canvas/public/components/toolbar/index.js index 294a44ba0415a..16860063f8a45 100644 --- a/x-pack/plugins/canvas/public/components/toolbar/index.js +++ b/x-pack/plugins/canvas/public/components/toolbar/index.js @@ -19,7 +19,7 @@ import { import { Toolbar as Component } from './toolbar'; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ workpadName: getWorkpadName(state), workpadId: getWorkpad(state).id, totalPages: getWorkpad(state).pages.length, @@ -35,15 +35,15 @@ export const Toolbar = compose( router: PropTypes.object, }), withHandlers({ - nextPage: props => () => { + nextPage: (props) => () => { const pageNumber = Math.min(props.selectedPageNumber + 1, props.totalPages); props.router.navigateTo('loadWorkpad', { id: props.workpadId, page: pageNumber }); }, - previousPage: props => () => { + previousPage: (props) => () => { const pageNumber = Math.max(1, props.selectedPageNumber - 1); props.router.navigateTo('loadWorkpad', { id: props.workpadId, page: pageNumber }); }, }), - withState('tray', 'setTray', props => props.tray), + withState('tray', 'setTray', (props) => props.tray), withState('showWorkpadManager', 'setShowWorkpadManager', false) )(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad/index.js b/x-pack/plugins/canvas/public/components/workpad/index.js index fca663f8532d8..72c4588e43c03 100644 --- a/x-pack/plugins/canvas/public/components/workpad/index.js +++ b/x-pack/plugins/canvas/public/components/workpad/index.js @@ -23,7 +23,7 @@ import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric'; import { LAUNCHED_FULLSCREEN, LAUNCHED_FULLSCREEN_AUTOPLAY } from '../../../common/lib/constants'; import { Workpad as Component } from './workpad'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { const { width, height, id: workpadId, css: workpadCss } = getWorkpad(state); return { pages: getPages(state), @@ -51,7 +51,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { ...ownProps, ...stateProps, ...dispatchProps, - setFullscreen: value => { + setFullscreen: (value) => { dispatchProps.setFullscreen(value); if (value === true) { @@ -93,7 +93,7 @@ export const Workpad = compose( return { getAnimation }; }), withHandlers({ - onPageChange: props => pageNumber => { + onPageChange: (props) => (pageNumber) => { if (pageNumber === props.selectedPageNumber) { return; } @@ -108,11 +108,11 @@ export const Workpad = compose( }), withHandlers({ onTransitionEnd: ({ setTransition }) => () => setTransition(null), - nextPage: props => () => { + nextPage: (props) => () => { const pageNumber = Math.min(props.selectedPageNumber + 1, props.pages.length); props.onPageChange(pageNumber); }, - previousPage: props => () => { + previousPage: (props) => () => { const pageNumber = Math.max(1, props.selectedPageNumber - 1); props.onPageChange(pageNumber); }, diff --git a/x-pack/plugins/canvas/public/components/workpad_config/index.js b/x-pack/plugins/canvas/public/components/workpad_config/index.js index aa3bbdce97863..913cf7093e726 100644 --- a/x-pack/plugins/canvas/public/components/workpad_config/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_config/index.js @@ -12,7 +12,7 @@ import { getWorkpad } from '../../state/selectors/workpad'; import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants'; import { WorkpadConfig as Component } from './workpad_config'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { const workpad = getWorkpad(state); return { @@ -26,9 +26,9 @@ const mapStateToProps = state => { }; const mapDispatchToProps = { - setSize: size => sizeWorkpad(size), - setName: name => setName(name), - setWorkpadCSS: css => setWorkpadCSS(css), + setSize: (size) => sizeWorkpad(size), + setName: (name) => setName(name), + setWorkpadCSS: (css) => setWorkpadCSS(css), }; export const WorkpadConfig = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.js b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.js index 7dfc378432b57..45758c9965653 100644 --- a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.js +++ b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.js @@ -76,7 +76,7 @@ export class WorkpadConfig extends PureComponent { - setName(e.target.value)} /> + setName(e.target.value)} /> @@ -86,7 +86,7 @@ export class WorkpadConfig extends PureComponent { setSize({ width: Number(e.target.value), height: size.height })} + onChange={(e) => setSize({ width: Number(e.target.value), height: size.height })} value={size.width} /> @@ -107,7 +107,7 @@ export class WorkpadConfig extends PureComponent { setSize({ height: Number(e.target.value), width: size.width })} + onChange={(e) => setSize({ height: Number(e.target.value), width: size.width })} value={size.height} /> @@ -152,7 +152,7 @@ export class WorkpadConfig extends PureComponent { aria-label={strings.getGlobalCSSTooltip()} value={css} compressed - onChange={e => this.setState({ css: e.target.value })} + onChange={(e) => this.setState({ css: e.target.value })} rows={10} /> diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts index a8bb7177dbd24..5debb675af659 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts @@ -67,7 +67,7 @@ const mapStateToProps = (state: State) => { selectedPrimaryShapeObjects.map((shape: PositionedElement) => nodes.find((n: PositionedElement) => n.id === shape.id) // is it a leaf or a persisted group? ? [shape.id] - : nodes.filter((s: PositionedElement) => s.position.parent === shape.id).map(s => s.id) + : nodes.filter((s: PositionedElement) => s.position.parent === shape.id).map((s) => s.id) ) ); const selectedNodeIds = flatten(selectedPersistentPrimaryNodes.map(crawlTree(nodes))); @@ -75,7 +75,7 @@ const mapStateToProps = (state: State) => { return { pageId, selectedToplevelNodes, - selectedNodes: selectedNodeIds.map((id: string) => nodes.find(s => s.id === id)), + selectedNodes: selectedNodeIds.map((id: string) => nodes.find((s) => s.id === id)), state, }; }; @@ -86,7 +86,9 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ removeNodes: (nodeIds: string[], pageId: string) => dispatch(removeElements(nodeIds, pageId)), selectToplevelNodes: (nodes: PositionedElement[]) => dispatch( - selectToplevelNodes(nodes.filter((e: PositionedElement) => !e.position.parent).map(e => e.id)) + selectToplevelNodes( + nodes.filter((e: PositionedElement) => !e.position.parent).map((e) => e.id) + ) ), elementLayer: (pageId: string, elementId: string, movement: number) => { dispatch(elementLayer({ pageId, elementId, movement })); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js index fc0bd4c74ba24..42396b87d2c73 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js @@ -27,7 +27,7 @@ import { FullscreenControl as Component } from './fullscreen_control'; // TODO: a lot of this is borrowed code from `/components/workpad/index.js`. // We should consider extracting the next/prev page logic into to a shared lib file. -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ workpadId: getWorkpad(state).id, pages: getPages(state), selectedPageNumber: getSelectedPageIndex(state) + 1, @@ -35,12 +35,12 @@ const mapStateToProps = state => ({ autoplayEnabled: getAutoplay(state).enabled, }); -const mapDispatchToProps = dispatch => ({ - setFullscreen: value => { +const mapDispatchToProps = (dispatch) => ({ + setFullscreen: (value) => { dispatch(setFullscreen(value)); value && dispatch(selectToplevelNodes([])); }, - enableAutoplay: enabled => dispatch(enableAutoplay(enabled)), + enableAutoplay: (enabled) => dispatch(enableAutoplay(enabled)), fetchAllRenderables: () => dispatch(fetchAllRenderables()), }); @@ -49,7 +49,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { ...ownProps, ...stateProps, ...dispatchProps, - setFullscreen: value => { + setFullscreen: (value) => { dispatchProps.setFullscreen(value); if (value === true) { @@ -89,7 +89,7 @@ export const FullscreenControl = compose( return { getAnimation }; }), withHandlers({ - onPageChange: props => pageNumber => { + onPageChange: (props) => (pageNumber) => { if (pageNumber === props.selectedPageNumber) { return; } @@ -104,11 +104,11 @@ export const FullscreenControl = compose( }), withHandlers({ onTransitionEnd: ({ setTransition }) => () => setTransition(null), - nextPage: props => () => { + nextPage: (props) => () => { const pageNumber = Math.min(props.selectedPageNumber + 1, props.pages.length); props.onPageChange(pageNumber); }, - previousPage: props => () => { + previousPage: (props) => () => { const pageNumber = Math.max(1, props.selectedPageNumber - 1); props.onPageChange(pageNumber); }, diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts index 1ed39b62cccad..64712f0df8d6c 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts @@ -33,7 +33,7 @@ const { WorkpadHeaderShareMenu: strings } = ComponentStrings; const getUnsupportedRenderers = (state: State) => { const renderers: string[] = []; const expressions = getRenderedWorkpadExpressions(state); - expressions.forEach(expression => { + expressions.forEach((expression) => { if (!renderFunctionNames.includes(expression)) { renderers.push(expression); } @@ -71,7 +71,7 @@ export const ShareWebsiteFlyout = compose onCopy: () => { kibana.services.canvas.notify.info(strings.getCopyShareConfigMessage()); }, - onDownload: type => { + onDownload: (type) => { switch (type) { case 'share': downloadRenderedWorkpad(renderedWorkpad); @@ -83,7 +83,7 @@ export const ShareWebsiteFlyout = compose const basePath = kibana.services.http.basePath.get(); arrayBufferFetch .post(`${basePath}${API_ROUTE_SHAREABLE_ZIP}`, JSON.stringify(renderedWorkpad)) - .then(blob => downloadZippedRuntime(blob.data)) + .then((blob) => downloadZippedRuntime(blob.data)) .catch((err: Error) => { kibana.services.canvas.notify.error(err, { title: strings.getShareableZipErrorTitle(workpad.name), diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/index.ts index 9a8936ada6d1e..17fcc50334a8f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/index.ts @@ -46,7 +46,7 @@ export const ShareMenu = compose( withKibana, withProps( ({ workpad, pageCount, kibana }: Props & WithKibanaProps): ComponentProps => ({ - getExportUrl: type => { + getExportUrl: (type) => { if (type === 'pdf') { const pdfUrl = getPdfUrl(workpad, { pageCount }, kibana.services.http.basePath); return getAbsoluteUrl(pdfUrl); @@ -54,7 +54,7 @@ export const ShareMenu = compose( throw new Error(strings.getUnknownExportErrorMessage(type)); }, - onCopy: type => { + onCopy: (type) => { switch (type) { case 'pdf': kibana.services.canvas.notify.info(strings.getCopyPDFMessage()); @@ -66,7 +66,7 @@ export const ShareMenu = compose( throw new Error(strings.getUnknownExportErrorMessage(type)); } }, - onExport: type => { + onExport: (type) => { switch (type) { case 'pdf': return createPdf(workpad, { pageCount }, kibana.services.http.basePath) diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts index 6c7d7ddd0a793..63e7c26cebb04 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts @@ -12,7 +12,7 @@ import { fetch } from '../../../../common/lib/fetch'; import { IBasePath } from 'kibana/public'; const basePath = ({ - prepend: jest.fn().mockImplementation(s => `basepath/s/spacey/${s}`), + prepend: jest.fn().mockImplementation((s) => `basepath/s/spacey/${s}`), get: () => 'basepath/s/spacey', serverBasePath: `basepath`, } as unknown) as IBasePath; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx index cfd599b1d9f3f..d2e76f0afb3e7 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx @@ -151,7 +151,7 @@ export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterv
- setRefresh(value)} /> + setRefresh(value)} />
); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx index ab34f332dc126..cda5733cfcf47 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx @@ -30,7 +30,7 @@ export const CustomInterval = ({ gutterSize, buttonSize, onSubmit, defaultValue return ( { + onSubmit={(ev) => { ev.preventDefault(); if (!isInvalid && refreshInterval) { onSubmit(refreshInterval); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx index e63eed9f9df53..391a17c39523c 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx @@ -118,7 +118,7 @@ export const KioskControls = ({ autoplayInterval, onSetInterval }: Props) => { - onSetInterval(value)} /> + onSetInterval(value)} /> ); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx index b6f108cda37f6..af0a9ed460b8f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx @@ -180,7 +180,7 @@ export const ViewMenu: FunctionComponent = ({ content: ( setRefresh(val)} + setRefresh={(val) => setRefresh(val)} disableInterval={() => disableInterval()} /> ), diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/index.js b/x-pack/plugins/canvas/public/components/workpad_loader/index.js index 8b190338db49e..ab07d5d722405 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/index.js @@ -20,7 +20,7 @@ import { WorkpadLoader as Component } from './workpad_loader'; const { WorkpadLoader: strings } = ComponentStrings; const { WorkpadLoader: errors } = ErrorStrings; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ workpadId: getWorkpad(state).id, canUserWrite: canUserWrite(state), }); @@ -37,7 +37,7 @@ export const WorkpadLoader = compose( })), withHandlers(({ kibana }) => ({ // Workpad creation via navigation - createWorkpad: props => async workpad => { + createWorkpad: (props) => async (workpad) => { // workpad data uploaded, create and load it if (workpad != null) { try { @@ -55,7 +55,7 @@ export const WorkpadLoader = compose( }, // Workpad search - findWorkpads: ({ setWorkpads }) => async text => { + findWorkpads: ({ setWorkpads }) => async (text) => { try { const workpads = await workpadService.find(text); setWorkpads(workpads); @@ -65,10 +65,10 @@ export const WorkpadLoader = compose( }, // Workpad import/export methods - downloadWorkpad: () => workpadId => downloadWorkpad(workpadId), + downloadWorkpad: () => (workpadId) => downloadWorkpad(workpadId), // Clone workpad given an id - cloneWorkpad: props => async workpadId => { + cloneWorkpad: (props) => async (workpadId) => { try { const workpad = await workpadService.get(workpadId); workpad.name = strings.getClonedWorkpadName(workpad.name); @@ -81,20 +81,20 @@ export const WorkpadLoader = compose( }, // Remove workpad given an array of id - removeWorkpads: props => async workpadIds => { + removeWorkpads: (props) => async (workpadIds) => { const { setWorkpads, workpads, workpadId: loadedWorkpad } = props; - const removeWorkpads = workpadIds.map(id => + const removeWorkpads = workpadIds.map((id) => workpadService .remove(id) .then(() => ({ id, err: null })) - .catch(err => ({ + .catch((err) => ({ id, err, })) ); - return Promise.all(removeWorkpads).then(results => { + return Promise.all(removeWorkpads).then((results) => { let redirectHome = false; const [passes, errored] = results.reduce( @@ -135,8 +135,8 @@ export const WorkpadLoader = compose( }); }, })), - withProps(props => ({ - formatDate: date => { + withProps((props) => ({ + formatDate: (date) => { const dateFormat = props.kibana.services.uiSettings.get('dateFormat'); return date && moment(date).format(dateFormat); }, diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 41719e6d6b820..28cfac11e76bd 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -87,14 +87,14 @@ export class WorkpadLoader extends React.PureComponent { }; // create new workpad from uploaded JSON - onUpload = async workpad => { + onUpload = async (workpad) => { this.setState({ createPending: true }); await this.props.createWorkpad(workpad); this._isMounted && this.setState({ createPending: false }); }; // clone existing workpad - cloneWorkpad = async workpad => { + cloneWorkpad = async (workpad) => { this.setState({ createPending: true }); await this.props.cloneWorkpad(workpad.id); this._isMounted && this.setState({ createPending: false }); @@ -108,7 +108,7 @@ export class WorkpadLoader extends React.PureComponent { removeWorkpads = () => { const { selectedWorkpads } = this.state; - this.props.removeWorkpads(selectedWorkpads.map(({ id }) => id)).then(remainingIds => { + this.props.removeWorkpads(selectedWorkpads.map(({ id }) => id)).then((remainingIds) => { const remainingWorkpads = remainingIds.length > 0 ? selectedWorkpads.filter(({ id }) => remainingIds.includes(id)) @@ -127,7 +127,7 @@ export class WorkpadLoader extends React.PureComponent { this.state.selectedWorkpads.forEach(({ id }) => this.props.downloadWorkpad(id)); }; - onSelectionChange = selectedWorkpads => { + onSelectionChange = (selectedWorkpads) => { this.setState({ selectedWorkpads }); }; @@ -145,7 +145,7 @@ export class WorkpadLoader extends React.PureComponent { const actions = [ { - render: workpad => ( + render: (workpad) => ( @@ -202,7 +202,7 @@ export class WorkpadLoader extends React.PureComponent { sortable: true, dataType: 'date', width: '20%', - render: date => this.props.formatDate(date), + render: (date) => this.props.formatDate(date), }, { field: '@timestamp', @@ -210,7 +210,7 @@ export class WorkpadLoader extends React.PureComponent { sortable: true, dataType: 'date', width: '20%', - render: date => this.props.formatDate(date), + render: (date) => this.props.formatDate(date), }, { name: '', actions, width: '5%' }, ]; @@ -374,7 +374,7 @@ export class WorkpadLoader extends React.PureComponent { return ( - {pagination => ( + {(pagination) => ( @@ -387,7 +387,7 @@ export class WorkpadLoader extends React.PureComponent { )} { + onChange={(text) => { pagination.setPage(0); this.props.findWorkpads(text); }} diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js index c2d16949870ea..c72807681306f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js @@ -23,7 +23,7 @@ export class WorkpadSearch extends React.PureComponent { triggerChange = debounce(this.props.onChange, 150); - setSearchText = ev => { + setSearchText = (ev) => { const text = ev.target.value; this.setState({ searchText: text }); this.triggerChange(text); diff --git a/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js b/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js index 731656dd4e095..648ad161ca92a 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js @@ -14,7 +14,7 @@ import { matrixToAngle } from '../../lib/aeroelastic/matrix'; import { isGroupId, elementToShape } from './utils'; export * from './utils'; -const shapeToElement = shape => ({ +const shapeToElement = (shape) => ({ left: shape.transformMatrix[12] - shape.a, top: shape.transformMatrix[13] - shape.b, width: shape.a * 2, @@ -26,7 +26,7 @@ const shapeToElement = shape => ({ const globalPositionUpdates = (setMultiplePositions, { shapes, gestureEnd }, unsortedElements) => { const ascending = (a, b) => (a.id < b.id ? -1 : 1); - const relevant = s => s.type !== 'annotation' && s.subtype !== 'adHocGroup'; + const relevant = (s) => s.type !== 'annotation' && s.subtype !== 'adHocGroup'; const elements = unsortedElements.filter(relevant).sort(ascending); const repositionings = shapes .filter(relevant) @@ -62,26 +62,26 @@ const globalPositionUpdates = (setMultiplePositions, { shapes, gestureEnd }, uns return repositionings; }; -const dedupe = (d, i, a) => a.findIndex(s => s.id === d.id) === i; +const dedupe = (d, i, a) => a.findIndex((s) => s.id === d.id) === i; -const missingParentCheck = groups => { - const idMap = arrayToMap(groups.map(g => g.id)); - groups.forEach(g => { +const missingParentCheck = (groups) => { + const idMap = arrayToMap(groups.map((g) => g.id)); + groups.forEach((g) => { if (g.parent && !idMap[g.parent]) { g.parent = null; } }); }; -export const shapesForNodes = nodes => { +export const shapesForNodes = (nodes) => { const rawShapes = nodes .map(elementToShape) // filtering to eliminate residual element of a possible group that had been deleted in Redux - .filter((d, i, a) => !isGroupId(d.id) || a.find(s => s.parent === d.id)) + .filter((d, i, a) => !isGroupId(d.id) || a.find((s) => s.parent === d.id)) .filter(dedupe); missingParentCheck(rawShapes); const getLocalMatrix = getLocalTransformMatrix(rawShapes); - return rawShapes.map(s => ({ ...s, localTransformMatrix: getLocalMatrix(s) })); + return rawShapes.map((s) => ({ ...s, localTransformMatrix: getLocalMatrix(s) })); }; const updateGlobalPositionsInRedux = (setMultiplePositions, scene, unsortedElements) => { @@ -91,17 +91,17 @@ const updateGlobalPositionsInRedux = (setMultiplePositions, scene, unsortedEleme } }; -export const globalStateUpdater = (dispatch, globalState) => state => { +export const globalStateUpdater = (dispatch, globalState) => (state) => { const nextScene = state.currentScene; const page = getSelectedPage(globalState); const elements = getNodes(globalState, page); const shapes = nextScene.shapes; - const persistableGroups = shapes.filter(s => s.subtype === 'persistentGroup').filter(dedupe); - const persistedGroups = elements.filter(e => isGroupId(e.id)).filter(dedupe); + const persistableGroups = shapes.filter((s) => s.subtype === 'persistentGroup').filter(dedupe); + const persistedGroups = elements.filter((e) => isGroupId(e.id)).filter(dedupe); - persistableGroups.forEach(g => { + persistableGroups.forEach((g) => { if ( - !persistedGroups.find(p => { + !persistedGroups.find((p) => { if (!p.id) { throw new Error('Element has no id'); } @@ -123,12 +123,13 @@ export const globalStateUpdater = (dispatch, globalState) => state => { const elementsToRemove = persistedGroups.filter( // list elements for removal if they're not in the persistable set, or if there's no longer an associated element // the latter of which shouldn't happen, so it's belts and braces - p => - !persistableGroups.find(g => p.id === g.id) || !elements.find(e => e.position.parent === p.id) + (p) => + !persistableGroups.find((g) => p.id === g.id) || + !elements.find((e) => e.position.parent === p.id) ); updateGlobalPositionsInRedux( - positions => dispatch(setMultiplePositions(positions.map(p => ({ ...p, pageId: page })))), + (positions) => dispatch(setMultiplePositions(positions.map((p) => ({ ...p, pageId: page })))), nextScene, elements ); @@ -137,7 +138,7 @@ export const globalStateUpdater = (dispatch, globalState) => state => { // remove elements for groups that were ungrouped dispatch( removeElements( - elementsToRemove.map(e => e.id), + elementsToRemove.map((e) => e.id), page ) ); @@ -149,9 +150,9 @@ export const globalStateUpdater = (dispatch, globalState) => state => { dispatch( selectToplevelNodes( flatten( - selectedPrimaryShapes.map(n => - n.startsWith('group') && (shapes.find(s => s.id === n) || {}).subtype === 'adHocGroup' - ? shapes.filter(s => s.type !== 'annotation' && s.parent === n).map(s => s.id) + selectedPrimaryShapes.map((n) => + n.startsWith('group') && (shapes.find((s) => s.id === n) || {}).subtype === 'adHocGroup' + ? shapes.filter((s) => s.type !== 'annotation' && s.parent === n).map((s) => s.id) : [n] ) ) @@ -160,10 +161,10 @@ export const globalStateUpdater = (dispatch, globalState) => state => { } }; -export const crawlTree = shapes => shapeId => { - const rec = shapeId => [ +export const crawlTree = (shapes) => (shapeId) => { + const rec = (shapeId) => [ shapeId, - ...flatten(shapes.filter(s => s.position.parent === shapeId).map(s => rec(s.id))), + ...flatten(shapes.filter((s) => s.position.parent === shapeId).map((s) => rec(s.id))), ]; return rec(shapeId); }; diff --git a/x-pack/plugins/canvas/public/components/workpad_page/utils.js b/x-pack/plugins/canvas/public/components/workpad_page/utils.js index 04b1e02d422de..0289d96f91f0b 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/utils.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/utils.js @@ -6,9 +6,9 @@ import { multiply, rotateZ, translate } from '../../lib/aeroelastic/matrix'; -export const isGroupId = id => id.startsWith('group'); +export const isGroupId = (id) => id.startsWith('group'); -const headerData = id => +const headerData = (id) => isGroupId(id) ? { id, type: 'group', subtype: 'persistentGroup' } : { id, type: 'rectangleElement', subtype: '' }; diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js index 2d0f8ee5aa0aa..21f4a5b68b6c9 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js @@ -33,7 +33,7 @@ const setupHandler = (commit, canvasOrigin, zoomScale) => { commit('cursorPosition', {}); } }; - window.onmouseup = e => { + window.onmouseup = (e) => { e.stopPropagation(); const { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey } = e; const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale); @@ -81,7 +81,7 @@ const handleMouseDown = (commit, e, canvasOrigin, zoomScale, allowDrag = true) = }; export const eventHandlers = { - onMouseDown: props => e => + onMouseDown: (props) => (e) => handleMouseDown( props.commit, e, @@ -89,8 +89,9 @@ export const eventHandlers = { props.zoomScale, props.canDragElement(e.target) ), - onMouseMove: props => e => handleMouseMove(props.commit, e, props.canvasOrigin, props.zoomScale), - onMouseLeave: props => e => handleMouseLeave(props.commit, e), - onWheel: props => e => handleMouseMove(props.commit, e, props.canvasOrigin), + onMouseMove: (props) => (e) => + handleMouseMove(props.commit, e, props.canvasOrigin, props.zoomScale), + onMouseLeave: (props) => (e) => handleMouseLeave(props.commit, e), + onWheel: (props) => (e) => handleMouseMove(props.commit, e, props.canvasOrigin), resetHandler: () => () => resetHandler(), }; diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js index 2500a412c0fac..41f78165a7394 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js @@ -76,7 +76,7 @@ function closest(s) { // If you interact with an embeddable panel, only the header should be draggable // This function will determine if an element is an embeddable body or not -const isEmbeddableBody = element => { +const isEmbeddableBody = (element) => { const hasClosest = typeof element.closest === 'function'; if (hasClosest) { @@ -94,7 +94,7 @@ const isEmbeddableBody = element => { // Some elements in an embeddable may be portaled out of the embeddable container. // We do not want clicks on those to trigger drags, etc, in the workpad. This function // will check to make sure the clicked item is actually in the container -const isInWorkpad = element => { +const isInWorkpad = (element) => { const hasClosest = typeof element.closest === 'function'; const workpadContainerSelector = '.canvasWorkpadContainer'; @@ -114,7 +114,7 @@ const componentLayoutState = ({ width, }) => { const shapes = shapesForNodes(elements); - const selectedShapes = selectedToplevelNodes.filter(e => shapes.find(s => s.id === e)); + const selectedShapes = selectedToplevelNodes.filter((e) => shapes.find((s) => s.id === e)); const newState = { primaryUpdate: null, currentScene: { @@ -145,13 +145,13 @@ const mapStateToProps = (state, ownProps) => { const selectedToplevelNodes = state.transient.selectedToplevelNodes; const nodes = getNodes(state, ownProps.pageId); const selectedPrimaryShapeObjects = selectedToplevelNodes - .map(id => nodes.find(s => s.id === id)) - .filter(shape => shape); + .map((id) => nodes.find((s) => s.id === id)) + .filter((shape) => shape); const selectedPersistentPrimaryNodes = flatten( - selectedPrimaryShapeObjects.map(shape => - nodes.find(n => n.id === shape.id) // is it a leaf or a persisted group? + selectedPrimaryShapeObjects.map((shape) => + nodes.find((n) => n.id === shape.id) // is it a leaf or a persisted group? ? [shape.id] - : nodes.filter(s => s.parent === shape.id).map(s => s.id) + : nodes.filter((s) => s.parent === shape.id).map((s) => s.id) ) ); const selectedNodeIds = flatten(selectedPersistentPrimaryNodes.map(crawlTree(nodes))); @@ -160,23 +160,25 @@ const mapStateToProps = (state, ownProps) => { isEditable: !getFullscreen(state) && isWriteable(state) && canUserWrite(state), elements: nodes, selectedToplevelNodes, - selectedNodes: selectedNodeIds.map(id => nodes.find(s => s.id === id)), + selectedNodes: selectedNodeIds.map((id) => nodes.find((s) => s.id === id)), pageStyle: getPageById(state, ownProps.pageId).style, zoomScale: getZoomScale(state), }; }; -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ dispatch, insertNodes: (selectedNodes, pageId) => dispatch(insertNodes(selectedNodes, pageId)), removeNodes: (nodeIds, pageId) => dispatch(removeElements(nodeIds, pageId)), - selectToplevelNodes: nodes => - dispatch(selectToplevelNodes(nodes.filter(e => !e.position.parent).map(e => e.id))), + selectToplevelNodes: (nodes) => + dispatch(selectToplevelNodes(nodes.filter((e) => !e.position.parent).map((e) => e.id))), elementLayer: (pageId, elementId, movement) => dispatch(elementLayer({ pageId, elementId, movement })), - setMultiplePositions: pageId => repositionedNodes => + setMultiplePositions: (pageId) => (repositionedNodes) => dispatch( - setMultiplePositions(repositionedNodes.map(node => ({ ...node, pageId, elementId: node.id }))) + setMultiplePositions( + repositionedNodes.map((node) => ({ ...node, pageId, elementId: node.id })) + ) ), }); @@ -226,8 +228,8 @@ export const InteractivePage = compose( }; }), withProps(({ aeroStore, elements }) => { - const elementLookup = new Map(elements.map(element => [element.id, element])); - const elementsToRender = aeroStore.getCurrentState().currentScene.shapes.map(shape => { + const elementLookup = new Map(elements.map((element) => [element.id, element])); + const elementsToRender = aeroStore.getCurrentState().currentScene.shapes.map((shape) => { const element = elementLookup.get(shape.id); return element ? { ...shape, width: shape.a * 2, height: shape.b * 2, filter: element.filter } @@ -240,7 +242,7 @@ export const InteractivePage = compose( })), withProps((...props) => ({ ...props, - canDragElement: element => { + canDragElement: (element) => { return !isEmbeddableBody(element) && isInWorkpad(element); const hasClosest = typeof element.closest === 'function'; diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js index 68f47f35c6fa1..152da323e89ea 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js @@ -71,7 +71,7 @@ export class InteractiveWorkpadPage extends PureComponent {
{ + ref={(node) => { if (!canvasOrigin && node && node.getBoundingClientRect) { saveCanvasOrigin(() => () => node.getBoundingClientRect()); } @@ -92,7 +92,7 @@ export class InteractiveWorkpadPage extends PureComponent { {shortcuts} {elements - .map(node => { + .map((node) => { if (node.type === 'annotation') { const props = { key: node.id, @@ -127,7 +127,7 @@ export class InteractiveWorkpadPage extends PureComponent { return ; } }) - .filter(element => !!element)} + .filter((element) => !!element)}
); } diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js index 9e8962755e00b..aa620b812f76d 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js @@ -25,8 +25,8 @@ export class StaticWorkpadPage extends PureComponent { style={{ ...pageStyle, ...animationStyle, height, width }} > {elements - .filter(node => !isGroupId(node.id)) - .map(element => ( + .filter((node) => !isGroupId(node.id)) + .map((element) => ( ))}
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/index.js b/x-pack/plugins/canvas/public/components/workpad_templates/index.js index a17b77b74e499..73bcf017475b6 100644 --- a/x-pack/plugins/canvas/public/components/workpad_templates/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_templates/index.js @@ -22,7 +22,7 @@ export const WorkpadTemplates = compose( withKibana, withHandlers(({ kibana }) => ({ // Clone workpad given an id - cloneWorkpad: props => workpad => { + cloneWorkpad: (props) => (workpad) => { workpad.id = getId('workpad'); workpad.name = `My Canvas Workpad - ${workpad.name}`; // Remove unneeded fields @@ -32,7 +32,7 @@ export const WorkpadTemplates = compose( return workpadService .create(workpad) .then(() => props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 })) - .catch(err => + .catch((err) => kibana.services.canvas.notify.error(err, { title: `Couldn't clone workpad template` }) ); }, diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.js b/x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.js index a9a157f5675f8..80ee5a0396704 100644 --- a/x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.js +++ b/x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.js @@ -51,7 +51,7 @@ export class WorkpadTemplates extends React.PureComponent { onSearch = ({ queryText }) => this.setState(extractSearch(queryText)); - cloneTemplate = template => this.props.cloneWorkpad(template).then(() => this.props.onClose()); + cloneTemplate = (template) => this.props.cloneWorkpad(template).then(() => this.props.onClose()); renderWorkpadTable = ({ rows, pageNumber, totalPages, setPage }) => { const { sortField, sortDirection } = this.state; @@ -90,7 +90,7 @@ export class WorkpadTemplates extends React.PureComponent { sortable: false, dataType: 'string', width: '30%', - render: tags => , + render: (tags) => , }, ]; @@ -148,7 +148,7 @@ export class WorkpadTemplates extends React.PureComponent { const filteredTemplates = sortedTemplates.filter(({ name = '', help = '', tags = [] }) => { const tagMatch = filterTags.length - ? filterTags.every(filterTag => tags.indexOf(filterTag) > -1) + ? filterTags.every((filterTag) => tags.indexOf(filterTag) > -1) : true; const lowercaseSearch = searchTerm.toLowerCase(); @@ -162,7 +162,7 @@ export class WorkpadTemplates extends React.PureComponent { return ( - {pagination => ( + {(pagination) => ( {this.renderSearch()} diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx index 51a1608df67ae..5fdc88ed62406 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx @@ -27,7 +27,7 @@ const defaultValues: Arguments = { class Interactive extends React.Component<{}, Arguments> { public state = defaultValues; - _getArgValue: (arg: T) => Arguments[T] = arg => { + _getArgValue: (arg: T) => Arguments[T] = (arg) => { return this.state[arg]; }; @@ -50,18 +50,18 @@ class Interactive extends React.Component<{}, Arguments> { } } -const getArgValue: (arg: T) => Arguments[T] = arg => { +const getArgValue: (arg: T) => Arguments[T] = (arg) => { return defaultValues[arg]; }; storiesOf('arguments/ContainerStyle', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('extended', () => ); storiesOf('arguments/ContainerStyle/components', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('appearance form', () => ( diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx index 71d95603cfebd..4ef17fbe87616 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx @@ -19,7 +19,7 @@ const defaultValues: Arguments = { class Interactive extends React.Component<{}, Arguments> { public state = defaultValues; - _getArgValue: (arg: T) => Arguments[T] = arg => { + _getArgValue: (arg: T) => Arguments[T] = (arg) => { return this.state[arg]; }; @@ -39,18 +39,18 @@ class Interactive extends React.Component<{}, Arguments> { } } -const getArgValue: (arg: T) => Arguments[T] = arg => { +const getArgValue: (arg: T) => Arguments[T] = (arg) => { return defaultValues[arg]; }; storiesOf('arguments/ContainerStyle', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('simple', () => ); storiesOf('arguments/ContainerStyle/components', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('simple template', () => ( diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/border_form.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/border_form.tsx index 929eaeb02f3c7..c833ed3e99aa1 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/border_form.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/border_form.tsx @@ -79,7 +79,7 @@ export const BorderForm: FunctionComponent = ({ namedChange('borderWidth')(Number(e.target.value))} + onChange={(e) => namedChange('borderWidth')(Number(e.target.value))} />
@@ -89,7 +89,7 @@ export const BorderForm: FunctionComponent = ({ ({ + options={Object.values(BorderStyle).map((style) => ({ value: style, inputDisplay:
, }))} @@ -103,7 +103,7 @@ export const BorderForm: FunctionComponent = ({ namedChange('borderRadius')(e.target.value)} + onChange={(e) => namedChange('borderRadius')(e.target.value)} /> diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/simple_template.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/simple_template.tsx index cb7a5d606c7d9..3e002ead22d4b 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/simple_template.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/simple_template.tsx @@ -27,7 +27,7 @@ export const SimpleTemplate: FunctionComponent = ({ getArgValue, setArgVa
setArgValue('backgroundColor', color)} + onChange={(color) => setArgValue('backgroundColor', color)} colors={workpad.colors} anchorPosition="leftCenter" ariaLabel={strings.getDisplayName()} diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/font.js b/x-pack/plugins/canvas/public/expression_types/arg_types/font.js index 46a97f7c15d74..3e88d60b40d5f 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/font.js +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/font.js @@ -14,7 +14,7 @@ import { ArgTypesStrings } from '../../../i18n'; const { Font: strings } = ArgTypesStrings; -export const FontArgInput = props => { +export const FontArgInput = (props) => { const { onValueChange, argValue, workpad } = props; const chain = get(argValue, 'chain.0', {}); const chainArgs = get(chain, 'arguments', {}); @@ -27,7 +27,7 @@ export const FontArgInput = props => { const newValue = set( argValue, ['chain', 0, 'arguments'], - mapValues(newSpec, v => [v]) + mapValues(newSpec, (v) => [v]) ); return onValueChange(newValue); } diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx index 7e00bd4f33a8a..4a300b3de8923 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx @@ -44,7 +44,7 @@ class Interactive extends React.Component<{}, { argValue: ExpressionAstExpressio return ( { + onValueChange={(argValue) => { action('onValueChange')(argValue); this.setState({ argValue }); }} @@ -61,14 +61,14 @@ class Interactive extends React.Component<{}, { argValue: ExpressionAstExpressio } storiesOf('arguments/SeriesStyle', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .addDecorator(withKnobs) .add('extended', () => ); storiesOf('arguments/SeriesStyle/components', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('extended: defaults', () => ( diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx index 037b15d5c51e9..f9b175e84ec8e 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx @@ -35,7 +35,7 @@ class Interactive extends React.Component<{}, { argValue: ExpressionAstExpressio return ( { + onValueChange={(argValue) => { action('onValueChange')(argValue); this.setState({ argValue }); }} @@ -49,13 +49,13 @@ class Interactive extends React.Component<{}, { argValue: ExpressionAstExpressio } storiesOf('arguments/SeriesStyle', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('simple', () => ); storiesOf('arguments/SeriesStyle/components', module) - .addDecorator(story => ( + .addDecorator((story) => (
{story()}
)) .add('simple: no labels', () => ( diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx index 615179a3f6f68..e0fe6e60c1dab 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx @@ -35,7 +35,7 @@ export interface Props { }; } -export const ExtendedTemplate: FunctionComponent = props => { +export const ExtendedTemplate: FunctionComponent = (props) => { const { typeInstance, onValueChange, labels, argValue } = props; const chain = get(argValue, 'chain.0', {}); const chainArgs = get(chain, 'arguments', {}); @@ -47,7 +47,7 @@ export const ExtendedTemplate: FunctionComponent = props => { } const fields = get(typeInstance, 'options.include', []); - const hasPropFields = fields.some(field => ['lines', 'bars', 'points'].indexOf(field) !== -1); + const hasPropFields = fields.some((field) => ['lines', 'bars', 'points'].indexOf(field) !== -1); const handleChange: (key: T, val: ChangeEvent) => void = ( argName, @@ -70,7 +70,7 @@ export const ExtendedTemplate: FunctionComponent = props => { ]; const labelOptions = [{ value: '', text: strings.getSelectSeriesOption() }]; - labels.sort().forEach(val => labelOptions.push({ value: val, text: val })); + labels.sort().forEach((val) => labelOptions.push({ value: val, text: val })); return (
@@ -81,7 +81,7 @@ export const ExtendedTemplate: FunctionComponent = props => { compressed value={selectedSeries} options={labelOptions} - onChange={ev => handleChange('label', ev)} + onChange={(ev) => handleChange('label', ev)} /> @@ -98,7 +98,7 @@ export const ExtendedTemplate: FunctionComponent = props => { value={get(chainArgs, 'lines.0', 0)} options={values} compressed - onChange={ev => handleChange('lines', ev)} + onChange={(ev) => handleChange('lines', ev)} /> @@ -110,7 +110,7 @@ export const ExtendedTemplate: FunctionComponent = props => { value={get(chainArgs, 'bars.0', 0)} options={values} compressed - onChange={ev => handleChange('bars', ev)} + onChange={(ev) => handleChange('bars', ev)} /> @@ -122,7 +122,7 @@ export const ExtendedTemplate: FunctionComponent = props => { value={get(chainArgs, 'points.0', 0)} options={values} compressed - onChange={ev => handleChange('points', ev)} + onChange={(ev) => handleChange('points', ev)} /> diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx index 226122cf0b25f..eb51492155a3b 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx @@ -32,7 +32,7 @@ interface Props { workpad: CanvasWorkpad; } -export const SimpleTemplate: FunctionComponent = props => { +export const SimpleTemplate: FunctionComponent = (props) => { const { typeInstance, argValue, onValueChange, labels, workpad } = props; const { name } = typeInstance; const chain = get(argValue, 'chain.0', {}); @@ -74,7 +74,7 @@ export const SimpleTemplate: FunctionComponent = props => { handleChange('color', val)} + onChange={(val) => handleChange('color', val)} value={color} ariaLabel={strings.getColorLabel()} /> diff --git a/x-pack/plugins/canvas/public/expression_types/datasource.js b/x-pack/plugins/canvas/public/expression_types/datasource.js index 0539486146136..ffcea83551914 100644 --- a/x-pack/plugins/canvas/public/expression_types/datasource.js +++ b/x-pack/plugins/canvas/public/expression_types/datasource.js @@ -41,7 +41,7 @@ class DatasourceWrapper extends React.PureComponent { render() { return ( { + render={(domNode) => { this.domNode = domNode; this.callRenderFn(); }} diff --git a/x-pack/plugins/canvas/public/expression_types/function_form.js b/x-pack/plugins/canvas/public/expression_types/function_form.js index 299b57e4854c1..7056f87bcfa05 100644 --- a/x-pack/plugins/canvas/public/expression_types/function_form.js +++ b/x-pack/plugins/canvas/public/expression_types/function_form.js @@ -80,16 +80,16 @@ export class FunctionForm extends BaseForm { const { args, argTypeDef } = data; // Don't instaniate these until render time, to give the registries a chance to populate. - const argInstances = this.args.map(argSpec => new Arg(argSpec)); + const argInstances = this.args.map((argSpec) => new Arg(argSpec)); if (!isPlainObject(args)) { throw new Error(`Form "${this.name}" expects "args" object`); } // get a mapping of arg values from the expression and from the renderable's schema - const argNames = uniq(argInstances.map(arg => arg.name).concat(Object.keys(args))); - const dataArgs = argNames.map(argName => { - const arg = argInstances.find(arg => arg.name === argName); + const argNames = uniq(argInstances.map((arg) => arg.name).concat(Object.keys(args))); + const dataArgs = argNames.map((argName) => { + const arg = argInstances.find((arg) => arg.name === argName); // if arg is not multi, only preserve the last value found // otherwise, leave the value alone (including if the arg is not defined) @@ -104,10 +104,10 @@ export class FunctionForm extends BaseForm { try { // allow a hook to override the data args - const resolvedDataArgs = dataArgs.map(d => ({ ...d, ...this.resolveArg(d, props) })); + const resolvedDataArgs = dataArgs.map((d) => ({ ...d, ...this.resolveArg(d, props) })); - const argumentForms = compact(resolvedDataArgs.map(d => this.renderArg(props, d))); - const addableArgs = compact(resolvedDataArgs.map(d => this.getAddableArg(props, d))); + const argumentForms = compact(resolvedDataArgs.map((d) => this.renderArg(props, d))); + const addableArgs = compact(resolvedDataArgs.map((d) => this.getAddableArg(props, d))); if (!addableArgs.length && !argumentForms.length) { return null; diff --git a/x-pack/plugins/canvas/public/expression_types/model.js b/x-pack/plugins/canvas/public/expression_types/model.js index 9ae71dfc0ac18..95c8b27a71048 100644 --- a/x-pack/plugins/canvas/public/expression_types/model.js +++ b/x-pack/plugins/canvas/public/expression_types/model.js @@ -48,7 +48,7 @@ export class Model extends FunctionForm { // if argument is missing from modelArgs, mark it as skipped const argName = get(dataArg, 'arg.name'); - const modelArg = modelArgs.find(modelArg => { + const modelArg = modelArgs.find((modelArg) => { if (Array.isArray(modelArg)) { return modelArg[0] === argName; } diff --git a/x-pack/plugins/canvas/public/functions/__tests__/asset.js b/x-pack/plugins/canvas/public/functions/__tests__/asset.js index 208af754e9105..c21faf9a2e227 100644 --- a/x-pack/plugins/canvas/public/functions/__tests__/asset.js +++ b/x-pack/plugins/canvas/public/functions/__tests__/asset.js @@ -17,7 +17,7 @@ describe.skip('asset', () => { const throwsErr = () => { return fn(null, { id: 'boo' }); }; - expect(throwsErr).to.throwException(err => { + expect(throwsErr).to.throwException((err) => { expect(err.message).to.be('Could not get the asset by ID: boo'); }); }); diff --git a/x-pack/plugins/canvas/public/functions/filters.ts b/x-pack/plugins/canvas/public/functions/filters.ts index 16d0bb0fff708..78cd742b44b26 100644 --- a/x-pack/plugins/canvas/public/functions/filters.ts +++ b/x-pack/plugins/canvas/public/functions/filters.ts @@ -37,7 +37,7 @@ function getFiltersByGroup(allFilters: string[], groups?: string[], ungrouped = return allFilters.filter((filter: string) => { const ast = fromExpression(filter); const expGroups = get(ast, 'chain[0].arguments.filterGroup', []); - return expGroups.length > 0 && expGroups.every(expGroup => groups.includes(expGroup)); + return expGroups.length > 0 && expGroups.every((expGroup) => groups.includes(expGroup)); }); } diff --git a/x-pack/plugins/canvas/public/functions/timelion.ts b/x-pack/plugins/canvas/public/functions/timelion.ts index 7e38e6e710b81..abb294d9cc110 100644 --- a/x-pack/plugins/canvas/public/functions/timelion.ts +++ b/x-pack/plugins/canvas/public/functions/timelion.ts @@ -94,7 +94,7 @@ export function timelionFunctionFactory(initialize: InitializeArguments): () => fn: (input, args): Promise => { // Timelion requires a time range. Use the time range from the timefilter element in the // workpad, if it exists. Otherwise fall back on the function args. - const timeFilter = input.and.find(and => and.filterType === 'time'); + const timeFilter = input.and.find((and) => and.filterType === 'time'); const range = timeFilter ? { min: timeFilter.from, max: timeFilter.to } : parseDateMath({ from: args.from, to: args.to }, args.timezone, initialize.timefilter); @@ -121,11 +121,15 @@ export function timelionFunctionFactory(initialize: InitializeArguments): () => method: 'POST', responseType: 'json', data: body, - }).then(resp => { + }).then((resp) => { const seriesList = resp.data.sheet[0].list; const rows = flatten( seriesList.map((series: { data: any[]; label: string }) => - series.data.map(row => ({ '@timestamp': row[0], value: row[1], label: series.label })) + series.data.map((row) => ({ + '@timestamp': row[0], + value: row[1], + label: series.label, + })) ) ) as DatatableRow[]; diff --git a/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js b/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js index 82cb468be3c54..99d8305768240 100644 --- a/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js +++ b/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js @@ -130,16 +130,16 @@ describe.skip('historyProvider', () => { teardownFn(); }); - it('should call handler on state change', done => { - createOnceHandler(history, done, loc => { + it('should call handler on state change', (done) => { + createOnceHandler(history, done, (loc) => { expect(loc).to.be.a('object'); }); history.push({}); }); - it('should pass location object to handler', done => { - createOnceHandler(history, done, location => { + it('should pass location object to handler', (done) => { + createOnceHandler(history, done, (location) => { expect(location.pathname).to.be.a('string'); expect(location.hash).to.be.a('string'); expect(location.state).to.be.an('object'); @@ -149,7 +149,7 @@ describe.skip('historyProvider', () => { history.push(state); }); - it('should pass decompressed state to handler', done => { + it('should pass decompressed state to handler', (done) => { createOnceHandler(history, done, ({ state: curState }) => { expect(curState).to.eql(state); }); @@ -157,7 +157,7 @@ describe.skip('historyProvider', () => { history.push(state); }); - it('should pass in the previous location object to handler', done => { + it('should pass in the previous location object to handler', (done) => { createOnceHandler(history, done, (location, prevLocation) => { expect(prevLocation.pathname).to.be.a('string'); expect(prevLocation.hash).to.be.a('string'); diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts b/x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts index 38d5233b662cd..cb46a3d6be402 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts @@ -185,7 +185,7 @@ import { // typings:expect-error plain = undefined; // it's undefined // typings:expect-error - plain = a => a; // it's a function + plain = (a) => a; // it's a function // typings:expect-error plain = [new Date()]; // it's a time // typings:expect-error diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/common.js b/x-pack/plugins/canvas/public/lib/aeroelastic/common.js index 97f0e04ea6eb8..357c80488a08b 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/common.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/common.js @@ -8,10 +8,10 @@ import { select } from './select'; // serves as reminder that we start with the state // todo remove it as we add TS annotations (State) -const state = d => d; +const state = (d) => d; -const getScene = state => state.currentScene; +const getScene = (state) => state.currentScene; export const scene = select(getScene)(state); -const getPrimaryUpdate = state => state.primaryUpdate; +const getPrimaryUpdate = (state) => state.primaryUpdate; export const primaryUpdate = select(getPrimaryUpdate)(state); diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/functional.js b/x-pack/plugins/canvas/public/lib/aeroelastic/functional.js index 33cd95875302a..9b4ab8470da87 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/functional.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/functional.js @@ -12,7 +12,7 @@ * @param {*[][]} arrays * @returns *[] */ -export const flatten = arrays => [].concat(...arrays); +export const flatten = (arrays) => [].concat(...arrays); /** * identity @@ -20,7 +20,7 @@ export const flatten = arrays => [].concat(...arrays); * @param d * @returns d */ -export const identity = d => d; +export const identity = (d) => d; /** * map @@ -32,7 +32,7 @@ export const identity = d => d; * @param {Function} fun * @returns {function(*): *} */ -export const map = fun => array => array.map(value => fun(value)); +export const map = (fun) => (array) => array.map((value) => fun(value)); /** * disjunctiveUnion @@ -44,8 +44,8 @@ export const map = fun => array => array.map(value => fun(value)); */ export const disjunctiveUnion = (keyFun, set1, set2) => set1 - .filter(s1 => !set2.find(s2 => keyFun(s2) === keyFun(s1))) - .concat(set2.filter(s2 => !set1.find(s1 => keyFun(s1) === keyFun(s2)))); + .filter((s1) => !set2.find((s2) => keyFun(s2) === keyFun(s1))) + .concat(set2.filter((s2) => !set1.find((s1) => keyFun(s1) === keyFun(s2)))); /** * @@ -70,16 +70,16 @@ export const shallowEqual = (a, b) => { return true; }; -export const not = fun => (...args) => !fun(...args); +export const not = (fun) => (...args) => !fun(...args); export const removeDuplicates = (idFun, a) => - a.filter((d, i) => a.findIndex(s => idFun(s) === idFun(d)) === i); + a.filter((d, i) => a.findIndex((s) => idFun(s) === idFun(d)) === i); -export const arrayToMap = a => Object.assign({}, ...a.map(d => ({ [d]: true }))); +export const arrayToMap = (a) => Object.assign({}, ...a.map((d) => ({ [d]: true }))); export const subMultitree = (pk, fk, elements, inputRoots) => { - const getSubgraphs = roots => { - const children = flatten(roots.map(r => elements.filter(e => fk(e) === pk(r)))); + const getSubgraphs = (roots) => { + const children = flatten(roots.map((r) => elements.filter((e) => fk(e) === pk(r)))); if (children.length) { return [...roots, ...getSubgraphs(children, elements)]; } else { diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/geometry.js b/x-pack/plugins/canvas/public/lib/aeroelastic/geometry.js index de4510e385974..6c0ebc00c7899 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/geometry.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/geometry.js @@ -26,7 +26,7 @@ import { dotProduct } from './matrix2d'; const cornerScreenPositions = (transformMatrix, a, b) => // for unknown perf gain, this could be cached per shape - [TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT].map(corner => + [TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT].map((corner) => mvMultiply(transformMatrix, componentProduct(corner, [a, b, 0, 1])) ); @@ -127,9 +127,9 @@ const shapesAtPoint = (shapes, x, y) => // viewer. But that's not the case. So we maximize the Z value to tell what's on top. export const shapesAt = (shapes, { x, y }) => shapesAtPoint(shapes, x, y) - .filter(shape => shape.inside) + .filter((shape) => shape.inside) .sort((shape1, shape2) => shape2.z - shape1.z || shape2.index - shape1.index) // stable sort: DOM insertion order!!! - .map(shape => shape.shape); // decreasing order, ie. from front (closest to viewer) to back + .map((shape) => shape.shape); // decreasing order, ie. from front (closest to viewer) to back const getExtremum = (transformMatrix, a, b) => normalize(mvMultiply(transformMatrix, [a, b, 0, 1])); diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js b/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js index ba78db9dc670a..ede7affc850b6 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js @@ -25,20 +25,20 @@ const appleKeyboard = Boolean( * (we could turn gesture.js into a factory, with this state root - primaryUpdate - being passed...) */ -const primaryUpdate = state => state.primaryUpdate; +const primaryUpdate = (state) => state.primaryUpdate; -const gestureStatePrev = select(scene => scene.gestureState)(scene); +const gestureStatePrev = select((scene) => scene.gestureState)(scene); /** * Gestures - derived selectors for transient state */ // dispatch the various types of actions -const rawCursorPosition = select(action => +const rawCursorPosition = select((action) => action.type === 'cursorPosition' ? action.payload : null )(primaryUpdate); -const mouseButtonEvent = select(action => (action.type === 'mouseEvent' ? action.payload : null))( +const mouseButtonEvent = select((action) => (action.type === 'mouseEvent' ? action.payload : null))( primaryUpdate ); @@ -46,16 +46,16 @@ const keyFromMouse = select(({ type, payload: { altKey, metaKey, shiftKey, ctrlK type === 'cursorPosition' || type === 'mouseEvent' ? { altKey, metaKey, shiftKey, ctrlKey } : {} )(primaryUpdate); -export const metaHeld = select(appleKeyboard ? e => e.metaKey : e => e.altKey)(keyFromMouse); -export const optionHeld = select(appleKeyboard ? e => e.altKey : e => e.ctrlKey)(keyFromMouse); -export const shiftHeld = select(e => e.shiftKey)(keyFromMouse); +export const metaHeld = select(appleKeyboard ? (e) => e.metaKey : (e) => e.altKey)(keyFromMouse); +export const optionHeld = select(appleKeyboard ? (e) => e.altKey : (e) => e.ctrlKey)(keyFromMouse); +export const shiftHeld = select((e) => e.shiftKey)(keyFromMouse); export const cursorPosition = select(({ cursor }, position) => position || cursor)( gestureStatePrev, rawCursorPosition ); -export const mouseButton = select(next => { +export const mouseButton = select((next) => { if (!next) { return { down: false, up: false, uid: null }; } @@ -74,7 +74,7 @@ export const mouseIsDown = select(({ mouseIsDown }, next) => )(gestureStatePrev, mouseButtonEvent); export const gestureEnd = select( - action => + (action) => action && (action.type === 'actionEvent' || (action.type === 'mouseEvent' && action.payload.event === 'mouseUp')) @@ -128,9 +128,9 @@ const mouseButtonState = select( } )(gestureStatePrev, mouseIsDown, cursorPosition); -export const mouseDowned = select(state => state.buttonState === 'downed')(mouseButtonState); +export const mouseDowned = select((state) => state.buttonState === 'downed')(mouseButtonState); -export const dragging = select(state => state.buttonState === 'dragging')(mouseButtonState); +export const dragging = select((state) => state.buttonState === 'dragging')(mouseButtonState); export const dragVector = select(({ buttonState, downX, downY }, { x, y }) => ({ down: buttonState !== 'up', @@ -140,7 +140,7 @@ export const dragVector = select(({ buttonState, downX, downY }, { x, y }) => ({ y1: y, }))(mouseButtonState, cursorPosition); -export const actionEvent = select(action => +export const actionEvent = select((action) => action.type === 'actionEvent' ? action.payload : null )(primaryUpdate); diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js b/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js index d3da1b5553958..c2c6f77fb167c 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js @@ -138,7 +138,7 @@ const shapeAABB = (shape, prevOuter) => return extend(prevInner, cornerPoint, cornerPoint); }, prevOuter); -const shapesAABB = shapes => +const shapesAABB = (shapes) => shapes.reduce( (prevOuter, shape) => extend(prevOuter, ...shapeAABB(shape, prevOuter)), identityAABB() @@ -167,7 +167,7 @@ export const draggingShape = ({ draggedShape, shapes }, hoveredShape, down, mous // the currently dragged shape is considered in-focus; if no dragging is going on, then the hovered shape export const getFocusedShape = (draggedShape, hoveredShape) => draggedShape || hoveredShape; // focusedShapes has updated position etc. information while focusedShape may have stale position -export const getAlterSnapGesture = metaHeld => (metaHeld ? ['relax'] : []); +export const getAlterSnapGesture = (metaHeld) => (metaHeld ? ['relax'] : []); const initialTransformTuple = { deltaX: 0, @@ -196,38 +196,38 @@ export const getMouseTransformState = (prev, dragging, { x0, y0, x1, y1 }) => { } }; -export const getMouseTransformGesture = tuple => +export const getMouseTransformGesture = (tuple) => [tuple] - .filter(tpl => tpl.transform) + .filter((tpl) => tpl.transform) .map(({ transform, cumulativeTransform }) => ({ transform, cumulativeTransform })); -export const getLocalTransformMatrix = shapes => shape => { +export const getLocalTransformMatrix = (shapes) => (shape) => { if (!shape.parent) { return shape.transformMatrix; } return multiply( - invert(shapes.find(s => s.id === shape.parent).transformMatrix), + invert(shapes.find((s) => s.id === shape.parent).transformMatrix), shape.transformMatrix ); }; export const getSelectedShapeObjects = (scene, shapes) => - (scene.selectedShapes || []).map(s => shapes.find(ss => ss.id === s)); + (scene.selectedShapes || []).map((s) => shapes.find((ss) => ss.id === s)); -const contentShape = allShapes => shape => +const contentShape = (allShapes) => (shape) => shape.type === 'annotation' - ? contentShape(allShapes)(allShapes.find(s => s.id === shape.parent)) + ? contentShape(allShapes)(allShapes.find((s) => s.id === shape.parent)) : shape; const getContentShapes = (allShapes, shapes) => { // fixme no need to export, why doesn't linter or highlighter complain? - const idMap = arrayToMap(allShapes.map(shape => shape.id)); - return shapes.filter(shape => idMap[shape.id]).map(contentShape(allShapes)); + const idMap = arrayToMap(allShapes.map((shape) => shape.id)); + return shapes.filter((shape) => idMap[shape.id]).map(contentShape(allShapes)); }; -const primaryShape = shape => (shape.type === 'annotation' ? shape.parent : shape.id); +const primaryShape = (shape) => (shape.type === 'annotation' ? shape.parent : shape.id); -const rotationManipulation = config => ({ +const rotationManipulation = (config) => ({ shape, directShape, cursorPosition: { x, y }, @@ -265,7 +265,7 @@ const minimumSize = (min, { a, b, baseAB }, vector) => { ]; }; -const centeredResizeManipulation = config => ({ gesture, shape, directShape }) => { +const centeredResizeManipulation = (config) => ({ gesture, shape, directShape }) => { const transform = gesture.cumulativeTransform; // scaling such that the center remains in place (ie. the other side of the shape can grow/shrink) if (!shape || !directShape) { @@ -293,7 +293,7 @@ const centeredResizeManipulation = config => ({ gesture, shape, directShape }) = }; }; -const asymmetricResizeManipulation = config => ({ gesture, shape, directShape }) => { +const asymmetricResizeManipulation = (config) => ({ gesture, shape, directShape }) => { const transform = gesture.cumulativeTransform; // scaling such that the center remains in place (ie. the other side of the shape can grow/shrink) if (!shape || !directShape) { @@ -333,7 +333,7 @@ const asymmetricResizeManipulation = config => ({ gesture, shape, directShape }) const directShapeTranslateManipulation = (cumulativeTransforms, directShapes) => { const shapes = directShapes - .map(shape => shape.type !== 'annotation' && shape.id) + .map((shape) => shape.type !== 'annotation' && shape.id) .filter(identity); return [{ cumulativeTransforms, shapes }]; }; @@ -347,13 +347,13 @@ const rotationAnnotationManipulation = ( alterSnapGesture ) => { const shapeIds = directShapes.map( - shape => + (shape) => shape.type === 'annotation' && shape.subtype === config.rotationHandleName && shape.parent ); - const shapes = shapeIds.map(id => id && allShapes.find(shape => shape.id === id)); + const shapes = shapeIds.map((id) => id && allShapes.find((shape) => shape.id === id)); const tuples = flatten( shapes.map((shape, i) => - directTransforms.map(transform => ({ + directTransforms.map((transform) => ({ transform, shape, directShape: directShapes[i], @@ -373,19 +373,19 @@ const resizeAnnotationManipulation = ( manipulator ) => { const shapeIds = directShapes.map( - shape => + (shape) => shape.type === 'annotation' && shape.subtype === config.resizeHandleName && shape.parent ); - const shapes = shapeIds.map(id => id && allShapes.find(shape => shape.id === id)); + const shapes = shapeIds.map((id) => id && allShapes.find((shape) => shape.id === id)); const tuples = flatten( shapes.map((shape, i) => - transformGestures.map(gesture => ({ gesture, shape, directShape: directShapes[i] })) + transformGestures.map((gesture) => ({ gesture, shape, directShape: directShapes[i] })) ) ); return tuples.map(manipulator); }; -const fromScreen = currentTransform => transform => { +const fromScreen = (currentTransform) => (transform) => { const isTranslate = transform[12] !== 0 || transform[13] !== 0; if (isTranslate) { const composite = compositeComponent(currentTransform); @@ -397,7 +397,7 @@ const fromScreen = currentTransform => transform => { } }; -const horizontalToIndex = horizontal => (horizontal ? 0 : 1); +const horizontalToIndex = (horizontal) => (horizontal ? 0 : 1); const anchorAABB = (aabb, anchorDirection, horizontal) => { const dimension = horizontalToIndex(horizontal); @@ -421,13 +421,13 @@ export const getAlignDistributeTransformIntents = ( } const group = selectedShapes[0]; - const children = shapes.filter(s => s.parent === group.id && s.type !== 'annotation'); + const children = shapes.filter((s) => s.parent === group.id && s.type !== 'annotation'); if (alignAction && children.length > 1) { const { controlledAnchor, horizontal } = alignAction; const groupBoundingBox = shapeAABB(group, identityAABB()); const groupAnchor = anchorAABB(groupBoundingBox, controlledAnchor, horizontal); - const results = children.map(c => { + const results = children.map((c) => { const childBoundingBox = shapeAABB(c, identityAABB()); const childAnchor = anchorAABB(childBoundingBox, controlledAnchor, horizontal); const delta = groupAnchor - childAnchor; @@ -443,16 +443,16 @@ export const getAlignDistributeTransformIntents = ( const groupBoundingBox = shapeAABB(group, identityAABB()); const groupAnchor = anchorAABB(groupBoundingBox, -1, horizontal); const dimension = horizontalToIndex(horizontal); - const childrenBoxes2D = children.map(c => shapeAABB(c, identityAABB())); - const childrenAnchors = childrenBoxes2D.map(childBoundingBox => + const childrenBoxes2D = children.map((c) => shapeAABB(c, identityAABB())); + const childrenAnchors = childrenBoxes2D.map((childBoundingBox) => anchorAABB(childBoundingBox, -1, horizontal) ); - const childrenBoxes1D = childrenBoxes2D.map(box2D => [ + const childrenBoxes1D = childrenBoxes2D.map((box2D) => [ box2D[0][dimension], box2D[1][dimension], ]); - const childrenCenters = childrenBoxes1D.map(box1D => (box1D[1] + box1D[0]) / 2); - const childrenSizes = childrenBoxes1D.map(box1D => box1D[1] - box1D[0]); + const childrenCenters = childrenBoxes1D.map((box1D) => (box1D[1] + box1D[0]) / 2); + const childrenSizes = childrenBoxes1D.map((box1D) => box1D[1] - box1D[0]); const totalChildrenSize = childrenSizes.reduce((a, b) => a + b, 0); const groupSize = horizontal ? 2 * A : 2 * B; const totalFreeSpace = groupSize - totalChildrenSize; @@ -488,14 +488,14 @@ export const getAlignDistributeTransformIntents = ( return []; }; -const shapeApplyLocalTransforms = intents => shape => { +const shapeApplyLocalTransforms = (intents) => (shape) => { const transformIntents = flatten( intents .map( - intent => + (intent) => intent.transforms && intent.transforms.length && - intent.shapes.find(id => id === shape.id) && + intent.shapes.find((id) => id === shape.id) && intent.transforms.map(fromScreen(shape.localTransformMatrix)) ) .filter(identity) @@ -503,10 +503,10 @@ const shapeApplyLocalTransforms = intents => shape => { const sizeIntents = flatten( intents .map( - intent => + (intent) => intent.sizes && intent.sizes.length && - intent.shapes.find(id => id === shape.id) && + intent.shapes.find((id) => id === shape.id) && intent.sizes ) .filter(identity) @@ -514,10 +514,10 @@ const shapeApplyLocalTransforms = intents => shape => { const cumulativeTransformIntents = flatten( intents .map( - intent => + (intent) => intent.cumulativeTransforms && intent.cumulativeTransforms.length && - intent.shapes.find(id => id === shape.id) && + intent.shapes.find((id) => id === shape.id) && intent.cumulativeTransforms.map(fromScreen(shape.localTransformMatrix)) ) .filter(identity) @@ -525,10 +525,10 @@ const shapeApplyLocalTransforms = intents => shape => { const cumulativeSizeIntents = flatten( intents .map( - intent => + (intent) => intent.cumulativeSizes && intent.cumulativeSizes.length && - intent.shapes.find(id => id === shape.id) && + intent.shapes.find((id) => id === shape.id) && intent.cumulativeSizes ) .filter(identity) @@ -581,7 +581,7 @@ const getUpstreamTransforms = (shapes, shape) => shape.parent ? getUpstreamTransforms( shapes, - shapes.find(s => s.id === shape.parent) + shapes.find((s) => s.id === shape.parent) ).concat([shape.localTransformMatrix]) : [shape.localTransformMatrix]; @@ -589,19 +589,19 @@ const getUpstreams = (shapes, shape) => shape.parent ? getUpstreams( shapes, - shapes.find(s => s.id === shape.parent) + shapes.find((s) => s.id === shape.parent) ).concat([shape]) : [shape]; -const snappedA = shape => shape.a + (shape.snapResizeVector ? shape.snapResizeVector[0] : 0); -const snappedB = shape => shape.b + (shape.snapResizeVector ? shape.snapResizeVector[1] : 0); +const snappedA = (shape) => shape.a + (shape.snapResizeVector ? shape.snapResizeVector[0] : 0); +const snappedB = (shape) => shape.b + (shape.snapResizeVector ? shape.snapResizeVector[1] : 0); const cascadeUnsnappedTransforms = (shapes, shape) => { if (!shape.parent) { return shape.localTransformMatrix; } // boost for common case of toplevel shape const upstreams = getUpstreams(shapes, shape); - const upstreamTransforms = upstreams.map(s => { + const upstreamTransforms = upstreams.map((s) => { return s.localTransformMatrix; }); const cascadedTransforms = reduceTransforms(upstreamTransforms); @@ -609,7 +609,7 @@ const cascadeUnsnappedTransforms = (shapes, shape) => { }; const cascadeTransforms = (shapes, shape) => { - const cascade = s => + const cascade = (s) => s.snapDeltaMatrix ? multiply(s.localTransformMatrix, s.snapDeltaMatrix) : s.localTransformMatrix; @@ -622,7 +622,7 @@ const cascadeTransforms = (shapes, shape) => { return cascadedTransforms; }; -const shapeCascadeProperties = shapes => shape => { +const shapeCascadeProperties = (shapes) => (shape) => { return { ...shape, transformMatrix: cascadeTransforms(shapes, shape), @@ -631,7 +631,7 @@ const shapeCascadeProperties = shapes => shape => { }; }; -export const cascadeProperties = shapes => shapes.map(shapeCascadeProperties(shapes)); +export const cascadeProperties = (shapes) => shapes.map(shapeCascadeProperties(shapes)); const alignmentGuides = (config, shapes, guidedShapes, draggedShape) => { const result = {}; @@ -747,8 +747,8 @@ const alignmentGuides = (config, shapes, guidedShapes, draggedShape) => { return Object.values(result); }; -const isHorizontal = constraint => constraint.dimension === 'horizontal'; -const isVertical = constraint => constraint.dimension === 'vertical'; +const isHorizontal = (constraint) => constraint.dimension === 'horizontal'; +const isVertical = (constraint) => constraint.dimension === 'vertical'; const closestConstraint = (prev = { distance: Infinity }, next) => next.distance < prev.distance ? { constraint: next, distance: next.distance } : prev; @@ -760,7 +760,7 @@ const directionalConstraint = (constraints, filterFun) => { }; const rotationAnnotation = (config, shapes, selectedShapes, shape, i) => { - const foundShape = shapes.find(s => shape.id === s.id); + const foundShape = shapes.find((s) => shape.id === s.id); if (!foundShape) { return false; } @@ -770,7 +770,7 @@ const rotationAnnotation = (config, shapes, selectedShapes, shape, i) => { config, shapes, selectedShapes, - shapes.find(s => foundShape.parent === s.id), + shapes.find((s) => foundShape.parent === s.id), i ); } @@ -857,11 +857,11 @@ const resizeEdgeAnnotations = (config, parent, a, b) => ([[x0, y0], [x1, y1]]) = }; }; -const groupedShape = properShape => shape => shape.parent === properShape.id; +const groupedShape = (properShape) => (shape) => shape.parent === properShape.id; const magic = (config, shape, shapes) => { const epsilon = config.rotationEpsilon; const integralOf = Math.PI * 2; - const isIntegerMultiple = s => { + const isIntegerMultiple = (s) => { const zRotation = matrixToAngle(s.localTransformMatrix); const ratio = zRotation / integralOf; return Math.abs(Math.round(ratio) - ratio) < epsilon; @@ -879,11 +879,11 @@ const magic = (config, shape, shapes) => { }; function resizeAnnotation(config, shapes, selectedShapes, shape) { - const foundShape = shapes.find(s => shape.id === s.id); + const foundShape = shapes.find((s) => shape.id === s.id); const properShape = foundShape && (foundShape.subtype === config.resizeHandleName - ? shapes.find(s => shape.parent === s.id) + ? shapes.find((s) => shape.parent === s.id) : foundShape); if (!foundShape) { return []; @@ -894,7 +894,7 @@ function resizeAnnotation(config, shapes, selectedShapes, shape) { const result = foundShape.interactive ? resizeAnnotationsFunction(config, { shapes, - selectedShapes: [shapes.find(s => shape.parent === s.id)], + selectedShapes: [shapes.find((s) => shape.parent === s.id)], }) : []; return result; @@ -904,7 +904,7 @@ function resizeAnnotation(config, shapes, selectedShapes, shape) { config, shapes, selectedShapes, - shapes.find(s => foundShape.parent === s.id) + shapes.find((s) => foundShape.parent === s.id) ); } @@ -917,7 +917,7 @@ function resizeAnnotation(config, shapes, selectedShapes, shape) { magic( config, properShape, - shapes.filter(s => s.type !== 'annotation') + shapes.filter((s) => s.type !== 'annotation') )); const resizeVertices = allowResize ? resizeVertexTuples : []; const resizePoints = resizeVertices.map(resizePointAnnotations(config, shape, a, b)); @@ -929,14 +929,14 @@ export function resizeAnnotationsFunction(config, { shapes, selectedShapes }) { const shapesToAnnotate = selectedShapes; return flatten( shapesToAnnotate - .map(shape => { + .map((shape) => { return resizeAnnotation(config, shapes, selectedShapes, shape); }) .filter(identity) ); } -const crystallizeConstraint = shape => { +const crystallizeConstraint = (shape) => { const result = { ...shape }; if (shape.snapDeltaMatrix) { result.localTransformMatrix = multiply(shape.localTransformMatrix, shape.snapDeltaMatrix); @@ -950,7 +950,9 @@ const crystallizeConstraint = shape => { return result; }; -const translateShapeSnap = (horizontalConstraint, verticalConstraint, draggedElement) => shape => { +const translateShapeSnap = (horizontalConstraint, verticalConstraint, draggedElement) => ( + shape +) => { const constrainedX = horizontalConstraint && horizontalConstraint.constrained === shape.id; const constrainedY = verticalConstraint && verticalConstraint.constrained === shape.id; const snapOffsetX = constrainedX ? -horizontalConstraint.signedDistance : 0; @@ -983,7 +985,7 @@ const resizeShapeSnap = ( symmetric, horizontalPosition, verticalPosition -) => shape => { +) => (shape) => { const constrainedShape = draggedElement && shape.id === draggedElement.id; const constrainedX = horizontalConstraint && horizontalConstraint.constrained === shape.id; const constrainedY = verticalConstraint && verticalConstraint.constrained === shape.id; @@ -1025,10 +1027,10 @@ const resizeShapeSnap = ( const dissolveGroups = (groupsToDissolve, shapes, selectedShapes) => { return { shapes: shapes - .filter(s => !groupsToDissolve.find(g => s.id === g.id)) - .map(shape => { + .filter((s) => !groupsToDissolve.find((g) => s.id === g.id)) + .map((shape) => { const preexistingGroupParent = groupsToDissolve.find( - groupShape => groupShape.id === shape.parent + (groupShape) => groupShape.id === shape.parent ); // if linked, dissociate from group parent return preexistingGroupParent @@ -1037,29 +1039,29 @@ const dissolveGroups = (groupsToDissolve, shapes, selectedShapes) => { parent: null, localTransformMatrix: multiply( // pulling preexistingGroupParent from `shapes` to get fresh matrices - shapes.find(s => s.id === preexistingGroupParent.id).localTransformMatrix, // reinstate the group offset onto the child + shapes.find((s) => s.id === preexistingGroupParent.id).localTransformMatrix, // reinstate the group offset onto the child shape.localTransformMatrix ), } : shape; }), - selectedShapes: selectedShapes.filter(s => !groupsToDissolve.find(g => g.id === s.id)), + selectedShapes: selectedShapes.filter((s) => !groupsToDissolve.find((g) => g.id === s.id)), }; }; -const hasNoParentWithin = shapes => shape => !shapes.some(g => shape.parent === g.id); +const hasNoParentWithin = (shapes) => (shape) => !shapes.some((g) => shape.parent === g.id); const asYetUngroupedShapes = (preexistingAdHocGroups, selectedShapes) => selectedShapes.filter(hasNoParentWithin(preexistingAdHocGroups)); -const idMatch = shape => s => s.id === shape.id; +const idMatch = (shape) => (s) => s.id === shape.id; -const idsMatch = selectedShapes => shape => selectedShapes.find(idMatch(shape)); +const idsMatch = (selectedShapes) => (shape) => selectedShapes.find(idMatch(shape)); const axisAlignedBoundingBoxShape = (config, shapesToBox) => { const axisAlignedBoundingBox = shapesAABB(shapesToBox); const { a, b, localTransformMatrix, rigTransform } = projectAABB(axisAlignedBoundingBox); - const id = getId(config.groupName, shapesToBox.map(s => s.id).join('|')); + const id = getId(config.groupName, shapesToBox.map((s) => s.id).join('|')); const aabbShape = { id, type: config.groupName, @@ -1073,7 +1075,7 @@ const axisAlignedBoundingBoxShape = (config, shapesToBox) => { return aabbShape; }; -const resetChild = s => { +const resetChild = (s) => { if (s.childBaseAB) { s.childBaseAB = null; s.baseLocalTransformMatrix = null; @@ -1089,14 +1091,14 @@ const childScaler = ({ a, b }, baseAB) => { return groupScale; }; -const resizeChild = groupScale => s => { +const resizeChild = (groupScale) => (s) => { const childBaseAB = s.childBaseAB || [s.a, s.b]; const impliedScale = scale(...childBaseAB, 1); const inverseImpliedScale = invert(impliedScale); const baseLocalTransformMatrix = s.baseLocalTransformMatrix || s.localTransformMatrix; const normalizedBaseLocalTransformMatrix = multiply(baseLocalTransformMatrix, impliedScale); const T = multiply(groupScale, normalizedBaseLocalTransformMatrix); - const backScaler = groupScale.map(d => Math.abs(d)); + const backScaler = groupScale.map((d) => Math.abs(d)); const inverseBackScaler = invert(backScaler); const abTuple = mvMultiply(multiply(backScaler, impliedScale), [1, 1, 1, 1]); s.localTransformMatrix = multiply(T, multiply(inverseImpliedScale, inverseBackScaler)); @@ -1113,14 +1115,14 @@ const resizeGroup = (shapes, rootElement) => { } const depths = {}; - const ancestorsLength = shape => (shape.parent ? ancestorsLength(idMap[shape.parent]) + 1 : 0); + const ancestorsLength = (shape) => (shape.parent ? ancestorsLength(idMap[shape.parent]) + 1 : 0); for (const shape of shapes) { depths[shape.id] = ancestorsLength(shape); } const resizedParents = { [rootElement.id]: rootElement }; const sortedShapes = shapes.slice().sort((a, b) => depths[a.id] - depths[b.id]); - const parentResized = s => Boolean(s.childBaseAB || s.baseAB); + const parentResized = (s) => Boolean(s.childBaseAB || s.baseAB); for (const shape of sortedShapes) { const parent = resizedParents[shape.parent]; if (parent) { @@ -1137,32 +1139,32 @@ const resizeGroup = (shapes, rootElement) => { const getLeafs = (descendCondition, allShapes, shapes) => removeDuplicates( - s => s.id, + (s) => s.id, flatten( - shapes.map(shape => - descendCondition(shape) ? allShapes.filter(s => s.parent === shape.id) : shape + shapes.map((shape) => + descendCondition(shape) ? allShapes.filter((s) => s.parent === shape.id) : shape ) ) ); const preserveCurrentGroups = (shapes, selectedShapes) => ({ shapes, selectedShapes }); -export const getConfiguration = scene => scene.configuration; +export const getConfiguration = (scene) => scene.configuration; -export const getShapes = scene => scene.shapes; +export const getShapes = (scene) => scene.shapes; export const getHoveredShapes = (config, shapes, cursorPosition) => shapesAt( shapes.filter( // second AND term excludes intra-group element hover (and therefore drag & drop), todo: remove this current limitation - s => + (s) => (s.type !== 'annotation' || s.interactive) && (config.intraGroupManipulation || !s.parent || s.type === 'annotation') ), cursorPosition ); -export const getHoveredShape = hoveredShapes => (hoveredShapes.length ? hoveredShapes[0] : null); +export const getHoveredShape = (hoveredShapes) => (hoveredShapes.length ? hoveredShapes[0] : null); const singleSelect = (prev, config, hoveredShapes, metaHeld, uid, _, boxHighlightedShapes) => { // cycle from top ie. from zero after the cursor position changed ie. !sameLocation @@ -1193,7 +1195,7 @@ const multiSelect = ( ) => { const shapes = hoveredShapes.length > 0 - ? disjunctiveUnion(shape => shape.id, selectedShapeObjects, hoveredShapes.slice(0, 1)) // ie. depthIndex of 0, if any + ? disjunctiveUnion((shape) => shape.id, selectedShapeObjects, hoveredShapes.slice(0, 1)) // ie. depthIndex of 0, if any : []; return { shapes, @@ -1205,16 +1207,16 @@ const multiSelect = ( }; export const getGroupingTuple = (config, shapes, selectedShapes) => { - const childOfGroup = shape => shape.parent && shape.parent.startsWith(config.groupName); - const isAdHocGroup = shape => + const childOfGroup = (shape) => shape.parent && shape.parent.startsWith(config.groupName); + const isAdHocGroup = (shape) => shape.type === config.groupName && shape.subtype === config.adHocGroupName; const preexistingAdHocGroups = shapes.filter(isAdHocGroup); const matcher = idsMatch(selectedShapes); - const selectedFn = shape => matcher(shape) && shape.type !== 'annotation'; + const selectedFn = (shape) => matcher(shape) && shape.type !== 'annotation'; const freshSelectedShapes = shapes.filter(selectedFn); const freshNonSelectedShapes = shapes.filter(not(selectedFn)); - const isGroup = shape => shape.type === config.groupName; - const isOrBelongsToGroup = shape => isGroup(shape) || childOfGroup(shape); + const isGroup = (shape) => shape.type === config.groupName; + const isOrBelongsToGroup = (shape) => isGroup(shape) || childOfGroup(shape); const someSelectedShapesAreGrouped = selectedShapes.some(isOrBelongsToGroup); const selectionOutsideGroup = !someSelectedShapesAreGrouped; return { @@ -1234,16 +1236,16 @@ export const getGrouping = (config, shapes, selectedShapes, groupAction, tuple) } = tuple; if (groupAction === 'group') { const selectedAdHocGroupsToPersist = selectedShapes.filter( - s => s.subtype === config.adHocGroupName + (s) => s.subtype === config.adHocGroupName ); return { - shapes: shapes.map(s => + shapes: shapes.map((s) => s.subtype === config.adHocGroupName ? { ...s, subtype: config.persistentGroupName } : s ), selectedShapes: selectedShapes - .filter(selected => selected.subtype !== config.adHocGroupName) + .filter((selected) => selected.subtype !== config.adHocGroupName) .concat( - selectedAdHocGroupsToPersist.map(shape => ({ + selectedAdHocGroupsToPersist.map((shape) => ({ ...shape, subtype: config.persistentGroupName, })) @@ -1253,7 +1255,7 @@ export const getGrouping = (config, shapes, selectedShapes, groupAction, tuple) if (groupAction === 'ungroup') { return dissolveGroups( - selectedShapes.filter(s => s.subtype === config.persistentGroupName), + selectedShapes.filter((s) => s.subtype === config.persistentGroupName), shapes, asYetUngroupedShapes(preexistingAdHocGroups, freshSelectedShapes) ); @@ -1277,10 +1279,10 @@ export const getGrouping = (config, shapes, selectedShapes, groupAction, tuple) ? { shapes: [ ...resizeGroup( - shapes.filter(s => s.type !== 'annotation'), + shapes.filter((s) => s.type !== 'annotation'), elements[0] ), - ...shapes.filter(s => s.type === 'annotation'), + ...shapes.filter((s) => s.type === 'annotation'), ], selectedShapes, } @@ -1294,21 +1296,21 @@ export const getGrouping = (config, shapes, selectedShapes, groupAction, tuple) // group together the multiple items const group = axisAlignedBoundingBoxShape(config, freshSelectedShapes); const selectedLeafShapes = getLeafs( - shape => shape.subtype === config.adHocGroupName, + (shape) => shape.subtype === config.adHocGroupName, shapes, freshSelectedShapes ); - const parentedSelectedShapes = selectedLeafShapes.map(shape => ({ + const parentedSelectedShapes = selectedLeafShapes.map((shape) => ({ ...shape, parent: group.id, localTransformMatrix: multiply(group.rigTransform, shape.transformMatrix), })); - const nonGroupGraphConstituent = s => - s.subtype !== config.adHocGroupName && !parentedSelectedShapes.find(ss => s.id === ss.id); - const dissociateFromParentIfAny = s => + const nonGroupGraphConstituent = (s) => + s.subtype !== config.adHocGroupName && !parentedSelectedShapes.find((ss) => s.id === ss.id); + const dissociateFromParentIfAny = (s) => s.parent && s.parent.startsWith(config.groupName) && - preexistingAdHocGroups.find(ahg => ahg.id === s.parent) + preexistingAdHocGroups.find((ahg) => ahg.id === s.parent) ? { ...s, parent: null } : s; const allTerminalShapes = parentedSelectedShapes.concat( @@ -1338,7 +1340,7 @@ export const getCursor = (config, shape, draggedPrimaryShape) => { } }; -export const getSelectedShapesPrev = scene => scene.selectionState; +export const getSelectedShapesPrev = (scene) => scene.selectionState; export const getSelectionStateFull = ( prev, @@ -1362,7 +1364,7 @@ export const getSelectionStateFull = ( up && prev.boxHighlightedShapes.length ? prev.shapes .concat(prev.boxHighlightedShapes) - .filter((d, i, a) => a.findIndex(dd => dd.id === d.id) === i) + .filter((d, i, a) => a.findIndex((dd) => dd.id === d.id) === i) : prev.shapes, down, uid, @@ -1382,7 +1384,7 @@ export const getSelectionStateFull = ( ); }; -export const getSelectedShapes = selectionTuple => selectionTuple.shapes; +export const getSelectedShapes = (selectionTuple) => selectionTuple.shapes; export const getSelectionState = ({ uid, depthIndex, down, metaHeld, boxHighlightedShapes }) => ({ uid, @@ -1392,7 +1394,7 @@ export const getSelectionState = ({ uid, depthIndex, down, metaHeld, boxHighligh boxHighlightedShapes, }); -export const getSelectedPrimaryShapeIds = shapes => shapes.map(primaryShape); +export const getSelectedPrimaryShapeIds = (shapes) => shapes.map(primaryShape); export const getResizeManipulator = (config, toggle) => (toggle ? centeredResizeManipulation : asymmetricResizeManipulation)(config); @@ -1407,12 +1409,12 @@ export const getTransformIntents = ( manipulator ) => [ ...directShapeTranslateManipulation( - transformGestures.map(g => g.cumulativeTransform), + transformGestures.map((g) => g.cumulativeTransform), directShapes ), ...rotationAnnotationManipulation( config, - transformGestures.map(g => g.transform), + transformGestures.map((g) => g.transform), directShapes, shapes, cursorPosition, @@ -1422,14 +1424,14 @@ export const getTransformIntents = ( ]; export const getDraggedPrimaryShape = (shapes, draggedShape) => - draggedShape && shapes.find(shape => shape.id === primaryShape(draggedShape)); + draggedShape && shapes.find((shape) => shape.id === primaryShape(draggedShape)); export const getAlignmentGuideAnnotations = (config, shapes, draggedPrimaryShape, draggedShape) => { const guidedShapes = draggedPrimaryShape - ? [shapes.find(s => s.id === draggedPrimaryShape.id)].filter(identity) + ? [shapes.find((s) => s.id === draggedPrimaryShape.id)].filter(identity) : []; return guidedShapes.length - ? alignmentGuides(config, shapes, guidedShapes, draggedShape).map(shape => ({ + ? alignmentGuides(config, shapes, guidedShapes, draggedShape).map((shape) => ({ ...shape, id: config.alignmentGuideName + '_' + shape.id, type: 'annotation', @@ -1441,7 +1443,7 @@ export const getAlignmentGuideAnnotations = (config, shapes, draggedPrimaryShape : []; }; -const borderAnnotation = (subtype, lift) => shape => ({ +const borderAnnotation = (subtype, lift) => (shape) => ({ ...shape, id: subtype + '_' + shape.id, type: 'annotation', @@ -1452,16 +1454,18 @@ const borderAnnotation = (subtype, lift) => shape => ({ }); export const getAdHocChildrenAnnotations = (config, { shapes }) => { - const adHocGroups = shapes.filter(s => s.subtype === config.adHocGroupName); + const adHocGroups = shapes.filter((s) => s.subtype === config.adHocGroupName); return shapes - .filter(s => s.type !== 'annotation' && s.parent && adHocGroups.find(p => p.id === s.parent)) + .filter( + (s) => s.type !== 'annotation' && s.parent && adHocGroups.find((p) => p.id === s.parent) + ) .map(borderAnnotation(config.getAdHocChildAnnotationName, config.hoverLift)); }; export const getHoverAnnotations = (config, shapes, selectedPrimaryShapeIds, draggedShape) => shapes .filter( - shape => + (shape) => shape && shape.type !== 'annotation' && selectedPrimaryShapeIds.indexOf(shape.id) === -1 && @@ -1481,7 +1485,7 @@ export const getSnappedShapes = ( alterSnapGesture, symmetricManipulation ) => { - const contentShapes = shapes.filter(shape => shape.type !== 'annotation'); + const contentShapes = shapes.filter((shape) => shape.type !== 'annotation'); const subtype = draggedShape && draggedShape.subtype; // snapping doesn't come into play if there's no dragging, or it's not a resize drag or translate drag on a // leaf element or a group element: @@ -1513,7 +1517,7 @@ export const getSnappedShapes = ( }; export const getConstrainedShapesWithPreexistingAnnotations = (snapped, transformed) => - snapped.concat(transformed.filter(s => s.type === 'annotation')); + snapped.concat(transformed.filter((s) => s.type === 'annotation')); export const getGroupAction = (action, mouseIsDown) => { const event = action && action.event; @@ -1567,14 +1571,16 @@ const distributions = { distributeVertically: { type: 'distributeVerticallyAction', horizontal: false }, }; -export const getAlignAction = action => alignments[action && action.event] || null; -export const getDistributeAction = action => distributions[action && action.event] || null; +export const getAlignAction = (action) => alignments[action && action.event] || null; +export const getDistributeAction = (action) => distributions[action && action.event] || null; export const getGroupedSelectedShapes = ({ selectedShapes }) => selectedShapes; -export const getGroupedSelectedPrimaryShapeIds = selectedShapes => selectedShapes.map(primaryShape); +export const getGroupedSelectedPrimaryShapeIds = (selectedShapes) => + selectedShapes.map(primaryShape); -export const getGroupedSelectedShapeIds = selectedShapes => selectedShapes.map(shape => shape.id); +export const getGroupedSelectedShapeIds = (selectedShapes) => + selectedShapes.map((shape) => shape.id); export const getRotationAnnotations = (config, { shapes, selectedShapes }) => { const shapesToAnnotate = selectedShapes; @@ -1598,7 +1604,7 @@ export const getDragboxHighlighted = (box, shapes) => { } const filter = insideAABB(box); return shapes.filter( - s => s.type !== 'annotation' && !s.parent && filter(s.transformMatrix, s.a, s.b) + (s) => s.type !== 'annotation' && !s.parent && filter(s.transformMatrix, s.a, s.b) ); }; @@ -1639,7 +1645,7 @@ export const getAnnotatedShapes = ( dragBoxAnnotation ); // remove preexisting annotations - const contentShapes = shapes.filter(shape => shape.type !== 'annotation'); + const contentShapes = shapes.filter((shape) => shape.type !== 'annotation'); return contentShapes.concat(annotations); // add current annotations }; // collection of shapes themselves diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/select.ts b/x-pack/plugins/canvas/public/lib/aeroelastic/select.ts index a7dfa91bc3e11..ee8a2b21f3cef 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/select.ts +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/select.ts @@ -10,5 +10,5 @@ export const select = (fun: PlainFun): Selector => (...fns: Resolve[]) => { let prevId: ActionId = NaN; let cache: Json = null; const old = (object: State): boolean => prevId === (prevId = object.primaryUpdate.payload.uid); - return (obj: State) => (old(obj) ? cache : (cache = fun(...fns.map(f => f(obj))))); + return (obj: State) => (old(obj) ? cache : (cache = fun(...fns.map((f) => f(obj))))); }; diff --git a/x-pack/plugins/canvas/public/lib/arg_helpers.js b/x-pack/plugins/canvas/public/lib/arg_helpers.js index b674e158f3e34..9e4c93ed5f274 100644 --- a/x-pack/plugins/canvas/public/lib/arg_helpers.js +++ b/x-pack/plugins/canvas/public/lib/arg_helpers.js @@ -19,7 +19,7 @@ import { getType } from '@kbn/interpreter/common'; const allowedTypes = ['string', 'number', 'boolean']; const badType = () => new Error(`Arg setting helpers only support ${allowedTypes.join(',')}`); -const isAllowed = type => includes(allowedTypes, type); +const isAllowed = (type) => includes(allowedTypes, type); export function validateArg(value) { const type = getType(value); @@ -33,7 +33,7 @@ export function getSimpleArg(name, args) { if (!args[name]) { return []; } - return args[name].map(astVal => { + return args[name].map((astVal) => { if (!isAllowed(getType(astVal))) { throw badType(); } diff --git a/x-pack/plugins/canvas/public/lib/build_bool_array.js b/x-pack/plugins/canvas/public/lib/build_bool_array.js index 2dc6447753526..f1cab93ceebbb 100644 --- a/x-pack/plugins/canvas/public/lib/build_bool_array.js +++ b/x-pack/plugins/canvas/public/lib/build_bool_array.js @@ -6,11 +6,11 @@ import { getESFilter } from './get_es_filter'; -const compact = arr => (Array.isArray(arr) ? arr.filter(val => Boolean(val)) : []); +const compact = (arr) => (Array.isArray(arr) ? arr.filter((val) => Boolean(val)) : []); export function buildBoolArray(canvasQueryFilterArray) { return compact( - canvasQueryFilterArray.map(clause => { + canvasQueryFilterArray.map((clause) => { try { return getESFilter(clause); } catch (e) { diff --git a/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts b/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts index 9aa7477523c9a..c847bfb6516bf 100644 --- a/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts +++ b/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts @@ -18,7 +18,7 @@ const TimeFilterType = 'time'; function getTimeRangeFromFilters(filters: ExpressionValueFilter[]): TimeRange | undefined { const timeFilter = filters.find( - filter => filter.filterType !== undefined && filter.filterType === TimeFilterType + (filter) => filter.filterType !== undefined && filter.filterType === TimeFilterType ); return timeFilter !== undefined && timeFilter.from !== undefined && timeFilter.to !== undefined @@ -30,7 +30,7 @@ function getTimeRangeFromFilters(filters: ExpressionValueFilter[]): TimeRange | } export function getQueryFilters(filters: ExpressionValueFilter[]): DataFilter[] { - const dataFilters = filters.map(filter => ({ ...filter, type: filter.filterType })); + const dataFilters = filters.map((filter) => ({ ...filter, type: filter.filterType })); return buildBoolArray(dataFilters).map(esFilters.buildQueryFilter); } diff --git a/x-pack/plugins/canvas/public/lib/clone_subgraphs.js b/x-pack/plugins/canvas/public/lib/clone_subgraphs.js index 101492657b47b..e4dfa1cefcaba 100644 --- a/x-pack/plugins/canvas/public/lib/clone_subgraphs.js +++ b/x-pack/plugins/canvas/public/lib/clone_subgraphs.js @@ -7,13 +7,13 @@ import { arrayToMap } from './aeroelastic/functional'; import { getId } from './get_id'; -export const cloneSubgraphs = nodes => { - const idMap = arrayToMap(nodes.map(n => n.id)); +export const cloneSubgraphs = (nodes) => { + const idMap = arrayToMap(nodes.map((n) => n.id)); // We simultaneously provide unique id values for all elements (across all pages) // AND ensure that parent-child relationships are retained (via matching id values within page) - Object.keys(idMap).forEach(key => (idMap[key] = getId(key.split('-')[0]))); // new group names to which we can map + Object.keys(idMap).forEach((key) => (idMap[key] = getId(key.split('-')[0]))); // new group names to which we can map // must return elements in the same order, for several reasons - const newNodes = nodes.map(element => ({ + const newNodes = nodes.map((element) => ({ ...element, id: idMap[element.id], position: { diff --git a/x-pack/plugins/canvas/public/lib/custom_element_service.ts b/x-pack/plugins/canvas/public/lib/custom_element_service.ts index 8952802dc2f2b..25c3b78a2746e 100644 --- a/x-pack/plugins/canvas/public/lib/custom_element_service.ts +++ b/x-pack/plugins/canvas/public/lib/custom_element_service.ts @@ -10,7 +10,7 @@ import { fetch } from '../../common/lib/fetch'; import { CustomElement } from '../../types'; import { platformService } from '../services'; -const getApiPath = function() { +const getApiPath = function () { const basePath = platformService.getService().coreStart.http.basePath.get(); return `${basePath}${API_ROUTE_CUSTOM_ELEMENT}`; }; diff --git a/x-pack/plugins/canvas/public/lib/doc_title.js b/x-pack/plugins/canvas/public/lib/doc_title.js index 3f75d18df14d2..ce31a58fa359c 100644 --- a/x-pack/plugins/canvas/public/lib/doc_title.js +++ b/x-pack/plugins/canvas/public/lib/doc_title.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const setDocTitle = title => (document.title = `${title} - Kibana`); +export const setDocTitle = (title) => (document.title = `${title} - Kibana`); diff --git a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts index a8744b4820842..d280c62888df0 100644 --- a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts +++ b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts @@ -95,8 +95,9 @@ export const basicHandlerCreators = { ) .catch((error: Error) => notifyService.getService().warning(error, { - title: `Custom element '${customElement.displayName || - customElement.id}' was not saved`, + title: `Custom element '${ + customElement.displayName || customElement.id + }' was not saved`, }) ); } @@ -186,7 +187,7 @@ export const layerHandlerCreators = { export const positionHandlerCreators = { shiftUp: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.top -= ELEMENT_SHIFT_OFFSET; return element; }) @@ -194,7 +195,7 @@ export const positionHandlerCreators = { }, shiftDown: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.top += ELEMENT_SHIFT_OFFSET; return element; }) @@ -202,7 +203,7 @@ export const positionHandlerCreators = { }, shiftLeft: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.left -= ELEMENT_SHIFT_OFFSET; return element; }) @@ -210,7 +211,7 @@ export const positionHandlerCreators = { }, shiftRight: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.left += ELEMENT_SHIFT_OFFSET; return element; }) @@ -218,7 +219,7 @@ export const positionHandlerCreators = { }, nudgeUp: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.top -= ELEMENT_NUDGE_OFFSET; return element; }) @@ -226,7 +227,7 @@ export const positionHandlerCreators = { }, nudgeDown: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.top += ELEMENT_NUDGE_OFFSET; return element; }) @@ -234,7 +235,7 @@ export const positionHandlerCreators = { }, nudgeLeft: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.left -= ELEMENT_NUDGE_OFFSET; return element; }) @@ -242,7 +243,7 @@ export const positionHandlerCreators = { }, nudgeRight: ({ selectedNodes, setMultiplePositions }: Props) => (): void => { setMultiplePositions( - selectedNodes.map(element => { + selectedNodes.map((element) => { element.position.left += ELEMENT_NUDGE_OFFSET; return element; }) diff --git a/x-pack/plugins/canvas/public/lib/es_service.ts b/x-pack/plugins/canvas/public/lib/es_service.ts index 184f4f3c8af7c..496751a874b21 100644 --- a/x-pack/plugins/canvas/public/lib/es_service.ts +++ b/x-pack/plugins/canvas/public/lib/es_service.ts @@ -15,16 +15,16 @@ import { platformService } from '../services'; const { esService: strings } = ErrorStrings; -const getApiPath = function() { +const getApiPath = function () { const basePath = platformService.getService().coreStart.http.basePath.get(); return basePath + API_ROUTE; }; -const getSavedObjectsClient = function() { +const getSavedObjectsClient = function () { return platformService.getService().coreStart.savedObjects.client; }; -const getAdvancedSettings = function() { +const getAdvancedSettings = function () { return platformService.getService().coreStart.uiSettings; }; @@ -33,7 +33,7 @@ export const getFields = (index = '_all') => { .get(`${getApiPath()}/es_fields?index=${index}`) .then(({ data: mapping }: { data: object }) => Object.keys(mapping) - .filter(field => !field.startsWith('_')) // filters out meta fields + .filter((field) => !field.startsWith('_')) // filters out meta fields .sort() ) .catch((err: Error) => @@ -51,8 +51,8 @@ export const getIndices = () => searchFields: ['title'], perPage: 1000, }) - .then(resp => { - return resp.savedObjects.map(savedObject => { + .then((resp) => { + return resp.savedObjects.map((savedObject) => { return savedObject.attributes.title; }); }) @@ -66,8 +66,8 @@ export const getDefaultIndex = () => { return defaultIndexId ? getSavedObjectsClient() .get('index-pattern', defaultIndexId) - .then(defaultIndex => defaultIndex.attributes.title) - .catch(err => + .then((defaultIndex) => defaultIndex.attributes.title) + .catch((err) => notifyService .getService() .error(err, { title: strings.getDefaultIndexFetchErrorMessage() }) diff --git a/x-pack/plugins/canvas/public/lib/extract_search.js b/x-pack/plugins/canvas/public/lib/extract_search.js index 9ad0a6cd8f0c3..1d472b1c9dbb4 100644 --- a/x-pack/plugins/canvas/public/lib/extract_search.js +++ b/x-pack/plugins/canvas/public/lib/extract_search.js @@ -6,12 +6,12 @@ // EUI helper // extracts search text and array of selected tags from EuiSearchBar -export const extractSearch = queryText => { +export const extractSearch = (queryText) => { const filterTags = []; const searchTerms = []; const parts = queryText.split(' '); - parts.forEach(part => { + parts.forEach((part) => { if (part.indexOf(':') >= 0) { const [key, value] = part.split(':'); if (key === 'tag') { diff --git a/x-pack/plugins/canvas/public/lib/find_existing_asset.js b/x-pack/plugins/canvas/public/lib/find_existing_asset.js index 470d7797e6feb..2a6f48c31cd24 100644 --- a/x-pack/plugins/canvas/public/lib/find_existing_asset.js +++ b/x-pack/plugins/canvas/public/lib/find_existing_asset.js @@ -6,7 +6,7 @@ export const findExistingAsset = (type, content, assets) => { const existingId = Object.keys(assets).find( - assetId => assets[assetId].type === type && assets[assetId].value === content + (assetId) => assets[assetId].type === type && assets[assetId].value === content ); return existingId; }; diff --git a/x-pack/plugins/canvas/public/lib/find_expression_type.js b/x-pack/plugins/canvas/public/lib/find_expression_type.js index 2cd7c5efb74e9..4b09ba1ac8dd4 100644 --- a/x-pack/plugins/canvas/public/lib/find_expression_type.js +++ b/x-pack/plugins/canvas/public/lib/find_expression_type.js @@ -13,7 +13,7 @@ const expressionTypes = ['view', 'model', 'transform', 'datasource']; export function findExpressionType(name, type) { const checkTypes = expressionTypes.filter( - expressionType => type == null || expressionType === type + (expressionType) => type == null || expressionType === type ); const matches = checkTypes.reduce((acc, checkType) => { diff --git a/x-pack/plugins/canvas/public/lib/history_provider.js b/x-pack/plugins/canvas/public/lib/history_provider.js index 4ff9f0b9d4605..649f101126012 100644 --- a/x-pack/plugins/canvas/public/lib/history_provider.js +++ b/x-pack/plugins/canvas/public/lib/history_provider.js @@ -125,7 +125,7 @@ function wrapHistoryInstance(history) { const prevLocationObj = locationFormat(prevLocation, action, wrappedHistory.parse); // execute all listeners - historyState.onChange.forEach(fn => fn.call(null, locationObj, prevLocationObj)); + historyState.onChange.forEach((fn) => fn.call(null, locationObj, prevLocationObj)); // track the updated location historyState.prevLocation = wrappedHistory.getLocation(); @@ -136,7 +136,7 @@ function wrapHistoryInstance(history) { let instances = new WeakMap(); -const getHistoryInstance = win => { +const getHistoryInstance = (win) => { // if no window object, use memory module if (typeof win === 'undefined' || !win.history) { return createMemoryHistory(); diff --git a/x-pack/plugins/canvas/public/lib/keymap.ts b/x-pack/plugins/canvas/public/lib/keymap.ts index 7976c8ed5c5da..7ca93f440087e 100644 --- a/x-pack/plugins/canvas/public/lib/keymap.ts +++ b/x-pack/plugins/canvas/public/lib/keymap.ts @@ -37,20 +37,20 @@ const getShortcuts = ( // handle shift modifier if (modifiers.includes('shift')) { - macShortcuts = macShortcuts.map(shortcut => `shift+${shortcut}`); - shortcuts = shortcuts.map(shortcut => `shift+${shortcut}`); + macShortcuts = macShortcuts.map((shortcut) => `shift+${shortcut}`); + shortcuts = shortcuts.map((shortcut) => `shift+${shortcut}`); } // handle alt modifier if (modifiers.includes('alt') || modifiers.includes('option')) { - macShortcuts = macShortcuts.map(shortcut => `option+${shortcut}`); - shortcuts = shortcuts.map(shortcut => `alt+${shortcut}`); + macShortcuts = macShortcuts.map((shortcut) => `option+${shortcut}`); + shortcuts = shortcuts.map((shortcut) => `alt+${shortcut}`); } // handle ctrl modifier if (modifiers.includes('ctrl') || modifiers.includes('command')) { - macShortcuts = macShortcuts.map(shortcut => `command+${shortcut}`); - shortcuts = shortcuts.map(shortcut => `ctrl+${shortcut}`); + macShortcuts = macShortcuts.map((shortcut) => `command+${shortcut}`); + shortcuts = shortcuts.map((shortcut) => `ctrl+${shortcut}`); } return { diff --git a/x-pack/plugins/canvas/public/lib/load_expression_types.js b/x-pack/plugins/canvas/public/lib/load_expression_types.js index 82699eb5b88fa..23a14d079c091 100644 --- a/x-pack/plugins/canvas/public/lib/load_expression_types.js +++ b/x-pack/plugins/canvas/public/lib/load_expression_types.js @@ -9,5 +9,5 @@ import { argTypeRegistry } from '../expression_types'; export function loadExpressionTypes() { // register default args, arg types, and expression types - argTypeSpecs.forEach(expFn => argTypeRegistry.register(expFn)); + argTypeSpecs.forEach((expFn) => argTypeRegistry.register(expFn)); } diff --git a/x-pack/plugins/canvas/public/lib/load_transitions.js b/x-pack/plugins/canvas/public/lib/load_transitions.js index 915d63142bbf7..c4d91533435b8 100644 --- a/x-pack/plugins/canvas/public/lib/load_transitions.js +++ b/x-pack/plugins/canvas/public/lib/load_transitions.js @@ -8,5 +8,5 @@ import { transitions } from '../transitions'; import { transitionsRegistry } from './transitions_registry'; export function loadTransitions() { - transitions.forEach(spec => transitionsRegistry.register(spec)); + transitions.forEach((spec) => transitionsRegistry.register(spec)); } diff --git a/x-pack/plugins/canvas/public/lib/monaco_language_def.ts b/x-pack/plugins/canvas/public/lib/monaco_language_def.ts index 5dac7fcc9bc50..5b88658ddcd63 100644 --- a/x-pack/plugins/canvas/public/lib/monaco_language_def.ts +++ b/x-pack/plugins/canvas/public/lib/monaco_language_def.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { monaco } from '@kbn/ui-shared-deps/monaco'; +import { monaco } from '@kbn/monaco'; import { ExpressionFunction } from '../../types'; export const LANGUAGE_ID = 'canvas-expression'; @@ -95,7 +95,7 @@ export const language: Language = { }; export function registerLanguage(functions: ExpressionFunction[]) { - language.keywords = functions.map(fn => fn.name); + language.keywords = functions.map((fn) => fn.name); monaco.languages.register({ id: LANGUAGE_ID }); monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, language); diff --git a/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js b/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js index fb67379550b1d..4b23ba0941e88 100644 --- a/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js +++ b/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js @@ -17,7 +17,7 @@ export function parseSingleFunctionChain(filterString) { throw new Error('Could not find function name in chain'); } - const args = mapValues(get(ast, 'chain[0].arguments'), val => { + const args = mapValues(get(ast, 'chain[0].arguments'), (val) => { // TODO Check for literals only return map(val, 'value'); }); diff --git a/x-pack/plugins/canvas/public/lib/router_provider.js b/x-pack/plugins/canvas/public/lib/router_provider.js index 42960baa00de1..b518241585a10 100644 --- a/x-pack/plugins/canvas/public/lib/router_provider.js +++ b/x-pack/plugins/canvas/public/lib/router_provider.js @@ -23,7 +23,7 @@ export function routerProvider(routes) { const componentListeners = []; // assume any string starting with a / is a path - const isPath = str => typeof str === 'string' && str.substr(0, 1) === '/'; + const isPath = (str) => typeof str === 'string' && str.substr(0, 1) === '/'; // helper to get the current state in history const getState = (name, params, state) => { @@ -36,7 +36,7 @@ export function routerProvider(routes) { // helper to append appState to a given url path const appendAppState = (path, appState = getCurrentAppState()) => { - const newUrl = modifyUrl(path, parts => { + const newUrl = modifyUrl(path, (parts) => { parts.query = assignAppState(parts.query, appState); }); @@ -78,7 +78,7 @@ export function routerProvider(routes) { history[method](currentState, newPath); }, onPathChange(fn) { - const execOnMatch = location => { + const execOnMatch = (location) => { const { pathname } = location; const match = this.match(pathname); diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/plugins/canvas/public/lib/run_interpreter.ts index df338f40e08d9..07c0ca4b1ce15 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.ts @@ -6,32 +6,7 @@ import { fromExpression, getType } from '@kbn/interpreter/common'; import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public'; -import { notifyService } from '../services'; - -import { CanvasStartDeps, CanvasSetupDeps } from '../plugin'; - -let expressionsStarting: Promise | undefined; - -export const initInterpreter = function( - expressionsStart: CanvasStartDeps['expressions'], - expressionsSetup: CanvasSetupDeps['expressions'] -) { - expressionsStarting = startExpressions(expressionsStart, expressionsSetup); - - return expressionsStarting; -}; - -async function startExpressions( - expressionsStart: CanvasStartDeps['expressions'], - expressionsSetup: CanvasSetupDeps['expressions'] -) { - await expressionsSetup.__LEGACY.loadLegacyServerFunctionWrappers(); - return expressionsStart; -} - -export const resetInterpreter = function() { - expressionsStarting = undefined; -}; +import { notifyService, expressionsService } from '../services'; interface Options { castToRender?: boolean; @@ -41,12 +16,7 @@ interface Options { * Meant to be a replacement for plugins/interpreter/interpretAST */ export async function interpretAst(ast: ExpressionAstExpression): Promise { - if (!expressionsStarting) { - throw new Error('Interpreter has not been initialized'); - } - - const expressions = await expressionsStarting; - return await expressions.execute(ast).getData(); + return await expressionsService.getService().execute(ast).getData(); } /** @@ -63,14 +33,8 @@ export async function runInterpreter( input: ExpressionValue, options: Options = {} ): Promise { - if (!expressionsStarting) { - throw new Error('Interpreter has not been initialized'); - } - - const expressions = await expressionsStarting; - try { - const renderable = await expressions.execute(ast, input).getData(); + const renderable = await expressionsService.getService().execute(ast, input).getData(); if (getType(renderable) === 'render') { return renderable; diff --git a/x-pack/plugins/canvas/public/lib/template.js b/x-pack/plugins/canvas/public/lib/template.js index b7bf4c608d18d..d548d707e7aa0 100644 --- a/x-pack/plugins/canvas/public/lib/template.js +++ b/x-pack/plugins/canvas/public/lib/template.js @@ -25,7 +25,7 @@ export function Template(config) { // Tags for categorizing the template this.tags = config.tags || []; - this.tags.forEach(tag => { + this.tags.forEach((tag) => { if (!tagsRegistry.get(tag)) { tagsRegistry.register(() => ({ name: tag, color: '#666666' })); } diff --git a/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx b/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx index f5059dfa04b95..17e2712c44b8d 100644 --- a/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx +++ b/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx @@ -16,7 +16,7 @@ interface Props { } export const templateFromReactComponent = (Component: ComponentType) => { - const WrappedComponent: FunctionComponent = props => ( + const WrappedComponent: FunctionComponent = (props) => ( {({ error }: { error: Error }) => { if (error) { diff --git a/x-pack/plugins/canvas/public/lib/window_error_handler.js b/x-pack/plugins/canvas/public/lib/window_error_handler.js index 75307816b4371..b4dd850b56c44 100644 --- a/x-pack/plugins/canvas/public/lib/window_error_handler.js +++ b/x-pack/plugins/canvas/public/lib/window_error_handler.js @@ -15,7 +15,7 @@ function showError(err) { const close = document.createElement('a'); close.textContent = 'close'; - close.onclick = ev => { + close.onclick = (ev) => { ev.preventDefault(); body.removeChild(notice); }; @@ -29,11 +29,7 @@ function showError(err) { if (err.stack) { const stack = document.createElement('pre'); - stack.textContent = err.stack - .split('\n') - .slice(0, 2) - .concat('...') - .join('\n'); + stack.textContent = err.stack.split('\n').slice(0, 2).concat('...').join('\n'); notice.appendChild(stack); } @@ -54,7 +50,7 @@ window.canvasInitErrorHandler = () => { console.log(message); const isKnownError = message.includes('ResizeObserver loop') || - Object.keys(knownErrors).find(errorName => { + Object.keys(knownErrors).find((errorName) => { return err.constructor.name === errorName || message.indexOf(errorName) >= 0; }); if (isKnownError) { diff --git a/x-pack/plugins/canvas/public/lib/workpad_service.js b/x-pack/plugins/canvas/public/lib/workpad_service.js index e6628399f53c2..1ac2aa222927b 100644 --- a/x-pack/plugins/canvas/public/lib/workpad_service.js +++ b/x-pack/plugins/canvas/public/lib/workpad_service.js @@ -30,7 +30,7 @@ const validKeys = [ 'width', ]; -const sanitizeWorkpad = function(workpad) { +const sanitizeWorkpad = function (workpad) { const workpadKeys = Object.keys(workpad); for (const key of workpadKeys) { @@ -42,17 +42,17 @@ const sanitizeWorkpad = function(workpad) { return workpad; }; -const getApiPath = function() { +const getApiPath = function () { const basePath = platformService.getService().coreStart.http.basePath.get(); return `${basePath}${API_ROUTE_WORKPAD}`; }; -const getApiPathStructures = function() { +const getApiPathStructures = function () { const basePath = platformService.getService().coreStart.http.basePath.get(); return `${basePath}${API_ROUTE_WORKPAD_STRUCTURES}`; }; -const getApiPathAssets = function() { +const getApiPathAssets = function () { const basePath = platformService.getService().coreStart.http.basePath.get(); return `${basePath}${API_ROUTE_WORKPAD_ASSETS}`; }; diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index 1265bfbb69b70..9d2a6b3fdf4f4 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -28,7 +28,6 @@ import { Start as InspectorStart } from '../../../../src/plugins/inspector/publi import { argTypeSpecs } from './expression_types/arg_types'; import { transitions } from './transitions'; import { getPluginApi, CanvasApi } from './plugin_api'; -import { initFunctions } from './functions'; import { CanvasSrcPlugin } from '../canvas_plugin_src/plugin'; export { CoreStart, CoreSetup }; @@ -117,14 +116,6 @@ export class CanvasPlugin plugins.home.featureCatalogue.register(featureCatalogueEntry); - // Register core canvas stuff - canvasApi.addFunctions( - initFunctions({ - timefilter: plugins.data.query.timefilter.timefilter, - prependBasePath: core.http.basePath.prepend, - typesRegistry: plugins.expressions.__LEGACY.types, - }) - ); canvasApi.addArgumentUIs(argTypeSpecs); canvasApi.addTransitions(transitions); diff --git a/x-pack/plugins/canvas/public/plugin_api.ts b/x-pack/plugins/canvas/public/plugin_api.ts index 738b319d29c0b..4314c77456527 100644 --- a/x-pack/plugins/canvas/public/plugin_api.ts +++ b/x-pack/plugins/canvas/public/plugin_api.ts @@ -57,32 +57,32 @@ export function getPluginApi( const api: CanvasApi = { // Functions, types and renderers are registered directly to expression plugin - addFunctions: fns => { - fns.forEach(fn => { + addFunctions: (fns) => { + fns.forEach((fn) => { expressionsPluginSetup.registerFunction(fn); }); }, - addTypes: types => { - types.forEach(type => { + addTypes: (types) => { + types.forEach((type) => { expressionsPluginSetup.registerType(type as any); }); }, - addRenderers: renderers => { + addRenderers: (renderers) => { renderers.forEach((r: any) => { expressionsPluginSetup.registerRenderer(r); }); }, // All these others are local to canvas, and they will only register on start - addElements: elements => registries.elements.push(...elements), - addTransformUIs: transforms => registries.transformUIs.push(...transforms), - addDatasourceUIs: datasources => registries.datasourceUIs.push(...datasources), - addModelUIs: models => registries.modelUIs.push(...models), - addViewUIs: views => registries.viewUIs.push(...views), - addArgumentUIs: args => registries.argumentUIs.push(...args), - addTemplates: templates => registries.templates.push(...templates), - addTagUIs: tags => registries.tagUIs.push(...tags), - addTransitions: transitions => registries.transitions.push(...transitions), + addElements: (elements) => registries.elements.push(...elements), + addTransformUIs: (transforms) => registries.transformUIs.push(...transforms), + addDatasourceUIs: (datasources) => registries.datasourceUIs.push(...datasources), + addModelUIs: (models) => registries.modelUIs.push(...models), + addViewUIs: (views) => registries.viewUIs.push(...views), + addArgumentUIs: (args) => registries.argumentUIs.push(...args), + addTemplates: (templates) => registries.templates.push(...templates), + addTagUIs: (tags) => registries.tagUIs.push(...tags), + addTransitions: (transitions) => registries.transitions.push(...transitions), }; return { api, registries }; diff --git a/x-pack/plugins/canvas/public/services/expressions.ts b/x-pack/plugins/canvas/public/services/expressions.ts new file mode 100644 index 0000000000000..16f939a9c97fc --- /dev/null +++ b/x-pack/plugins/canvas/public/services/expressions.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CanvasServiceFactory } from '.'; +import { ExpressionsService } from '../../../../../src/plugins/expressions/common'; + +export const expressionsServiceFactory: CanvasServiceFactory = async ( + coreSetup, + coreStart, + setupPlugins, + startPlugins +) => { + await setupPlugins.expressions.__LEGACY.loadLegacyServerFunctionWrappers(); + + return setupPlugins.expressions.fork(); +}; diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts index 42176f953c331..a929b4639d3e4 100644 --- a/x-pack/plugins/canvas/public/services/index.ts +++ b/x-pack/plugins/canvas/public/services/index.ts @@ -10,6 +10,7 @@ import { CanvasSetupDeps, CanvasStartDeps } from '../plugin'; import { notifyServiceFactory } from './notify'; import { platformServiceFactory } from './platform'; import { navLinkServiceFactory } from './nav_link'; +import { expressionsServiceFactory } from './expressions'; export type CanvasServiceFactory = ( coreSetup: CoreSetup, @@ -17,7 +18,7 @@ export type CanvasServiceFactory = ( canvasSetupPlugins: CanvasSetupDeps, canvasStartPlugins: CanvasStartDeps, appUpdater: BehaviorSubject -) => Service; +) => Service | Promise; class CanvasServiceProvider { private factory: CanvasServiceFactory; @@ -27,14 +28,14 @@ class CanvasServiceProvider { this.factory = factory; } - start( + async start( coreSetup: CoreSetup, coreStart: CoreStart, canvasSetupPlugins: CanvasSetupDeps, canvasStartPlugins: CanvasStartDeps, appUpdater: BehaviorSubject ) { - this.service = this.factory( + this.service = await this.factory( coreSetup, coreStart, canvasSetupPlugins, @@ -59,27 +60,31 @@ class CanvasServiceProvider { export type ServiceFromProvider

= P extends CanvasServiceProvider ? T : never; export const services = { + expressions: new CanvasServiceProvider(expressionsServiceFactory), notify: new CanvasServiceProvider(notifyServiceFactory), platform: new CanvasServiceProvider(platformServiceFactory), navLink: new CanvasServiceProvider(navLinkServiceFactory), }; export interface CanvasServices { + expressions: ServiceFromProvider; notify: ServiceFromProvider; platform: ServiceFromProvider; navLink: ServiceFromProvider; } -export const startServices = ( +export const startServices = async ( coreSetup: CoreSetup, coreStart: CoreStart, canvasSetupPlugins: CanvasSetupDeps, canvasStartPlugins: CanvasStartDeps, appUpdater: BehaviorSubject ) => { - Object.entries(services).forEach(([key, provider]) => + const startPromises = Object.values(services).map((provider) => provider.start(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins, appUpdater) ); + + await Promise.all(startPromises); }; export const stopServices = () => { @@ -90,4 +95,5 @@ export const { notify: notifyService, platform: platformService, navLink: navLinkService, + expressions: expressionsService, } = services; diff --git a/x-pack/plugins/canvas/public/state/actions/app.js b/x-pack/plugins/canvas/public/state/actions/app.js index 1ca50eefe864c..b21fd5946339b 100644 --- a/x-pack/plugins/canvas/public/state/actions/app.js +++ b/x-pack/plugins/canvas/public/state/actions/app.js @@ -9,3 +9,4 @@ import { createAction } from 'redux-actions'; // actions to set the application state export const appReady = createAction('appReady'); export const appError = createAction('appError'); +export const appUnload = createAction('appUnload'); diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js index 5ec8eb6137f2b..47fbc782f90d3 100644 --- a/x-pack/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/plugins/canvas/public/state/actions/elements.js @@ -55,7 +55,7 @@ function getBareElement(el, includeId = false) { export const elementLayer = createAction('elementLayer'); -export const setMultiplePositions = createAction('setMultiplePosition', repositionedElements => ({ +export const setMultiplePositions = createAction('setMultiplePosition', (repositionedElements) => ({ repositionedElements, })); @@ -103,7 +103,7 @@ export const fetchContext = createThunk( chain: astChain, }, prevContextValue - ).then(value => { + ).then((value) => { dispatch( args.setValue({ path: contextPath, @@ -122,17 +122,17 @@ const fetchRenderableWithContextFn = ({ dispatch }, element, ast, context) => { }) ); - const getAction = renderable => + const getAction = (renderable) => args.setValue({ path: argumentPath, value: renderable, }); return runInterpreter(ast, context, { castToRender: true }) - .then(renderable => { + .then((renderable) => { dispatch(getAction(renderable)); }) - .catch(err => { + .catch((err) => { services.notify.getService().error(err); dispatch(getAction(err)); }); @@ -162,25 +162,25 @@ export const fetchAllRenderables = createThunk( function fetchElementsOnPages(pages) { const elements = []; - pages.forEach(page => { - page.elements.forEach(element => { + pages.forEach((page) => { + page.elements.forEach((element) => { elements.push(element); }); }); - const renderablePromises = elements.map(element => { + const renderablePromises = elements.map((element) => { const ast = element.ast || safeElementFromExpression(element.expression); const argumentPath = [element.id, 'expressionRenderable']; return runInterpreter(ast, null, { castToRender: true }) - .then(renderable => ({ path: argumentPath, value: renderable })) - .catch(err => { + .then((renderable) => ({ path: argumentPath, value: renderable })) + .catch((err) => { services.notify.getService().error(err); return { path: argumentPath, value: err }; }); }); - return Promise.all(renderablePromises).then(renderables => { + return Promise.all(renderablePromises).then((renderables) => { dispatch(args.setValues(renderables)); }); } @@ -203,17 +203,17 @@ export const insertNodes = createThunk('insertNodes', ({ dispatch, type }, eleme const _insertNodes = createAction(type); const newElements = elements.map(cloneDeep); // move the root element so users can see that it was added - newElements.forEach(newElement => { + newElements.forEach((newElement) => { newElement.position.top = newElement.position.top + 10; newElement.position.left = newElement.position.left + 10; }); dispatch(_insertNodes({ pageId, elements: newElements })); // refresh all elements just once per `insertNodes call` if there's a filter on any, otherwise just render the new element - if (elements.some(element => element.filter)) { + if (elements.some((element) => element.filter)) { dispatch(fetchAllRenderables()); } else { - newElements.forEach(newElement => dispatch(fetchRenderable(newElement))); + newElements.forEach((newElement) => dispatch(fetchRenderable(newElement))); } }); @@ -224,15 +224,17 @@ export const removeElements = createThunk( // todo consider doing the group membership collation in aeroelastic, or the Redux reducer, when adding templates const allElements = getNodes(state, pageId); - const allRoots = rootElementIds.map(id => allElements.find(e => id === e.id)).filter(d => d); + const allRoots = rootElementIds + .map((id) => allElements.find((e) => id === e.id)) + .filter((d) => d); const elementIds = subMultitree( - e => e.id, - e => e.position.parent, + (e) => e.id, + (e) => e.position.parent, allElements, allRoots - ).map(e => e.id); + ).map((e) => e.id); - const shouldRefresh = elementIds.some(elementId => { + const shouldRefresh = elementIds.some((elementId) => { const element = getNodeById(state, elementId, pageId); const filterIsApplied = element.filter && element.filter.length > 0; return filterIsApplied; @@ -275,7 +277,7 @@ function setExpressionFn({ dispatch, getState }, expression, elementId, pageId, // TODO: find a way to extract a list of filter renderers from the functions registry if ( updatedElement.filter && - !['dropdownControl', 'timefilterControl', 'exactly'].some(filter => + !['dropdownControl', 'timefilterControl', 'exactly'].some((filter) => updatedElement.expression.includes(filter) ) ) { diff --git a/x-pack/plugins/canvas/public/state/actions/embeddable.ts b/x-pack/plugins/canvas/public/state/actions/embeddable.ts index 3604d7e3c2141..e2cf588ec20a9 100644 --- a/x-pack/plugins/canvas/public/state/actions/embeddable.ts +++ b/x-pack/plugins/canvas/public/state/actions/embeddable.ts @@ -24,12 +24,12 @@ export const updateEmbeddableExpression = createAction State }, elementId: string) => { - const pageWithElement = getState().persistent.workpad.pages.find(page => { - return page.elements.find(element => element.id === elementId) !== undefined; + const pageWithElement = getState().persistent.workpad.pages.find((page) => { + return page.elements.find((element) => element.id === elementId) !== undefined; }); if (pageWithElement) { - const element = pageWithElement.elements.find(el => el.id === elementId); + const element = pageWithElement.elements.find((el) => el.id === elementId); dispatch(fetchRenderable(element)); } } diff --git a/x-pack/plugins/canvas/public/state/initial_state.js b/x-pack/plugins/canvas/public/state/initial_state.js index bfa68b33908e0..13021893e72e8 100644 --- a/x-pack/plugins/canvas/public/state/initial_state.js +++ b/x-pack/plugins/canvas/public/state/initial_state.js @@ -8,7 +8,7 @@ import { get } from 'lodash'; import { platformService } from '../services'; import { getDefaultWorkpad } from './defaults'; -export const getInitialState = path => { +export const getInitialState = (path) => { const state = { app: {}, // Kibana stuff in here assets: {}, // assets end up here diff --git a/x-pack/plugins/canvas/public/state/middleware/app_ready.js b/x-pack/plugins/canvas/public/state/middleware/app_ready.js index a86f67b5e1cc4..0b2fce4227197 100644 --- a/x-pack/plugins/canvas/public/state/middleware/app_ready.js +++ b/x-pack/plugins/canvas/public/state/middleware/app_ready.js @@ -7,7 +7,7 @@ import { isAppReady } from '../selectors/app'; import { appReady as readyAction } from '../actions/app'; -export const appReady = ({ dispatch, getState }) => next => action => { +export const appReady = ({ dispatch, getState }) => (next) => (action) => { // execute the action next(action); diff --git a/x-pack/plugins/canvas/public/state/middleware/breadcrumbs.js b/x-pack/plugins/canvas/public/state/middleware/breadcrumbs.js index b949e7025acbf..864e61b5a8f96 100644 --- a/x-pack/plugins/canvas/public/state/middleware/breadcrumbs.js +++ b/x-pack/plugins/canvas/public/state/middleware/breadcrumbs.js @@ -7,7 +7,7 @@ import { getWorkpad } from '../selectors/workpad'; import { getBaseBreadcrumb, getWorkpadBreadcrumb, setBreadcrumb } from '../../lib/breadcrumbs'; -export const breadcrumbs = ({ getState }) => next => action => { +export const breadcrumbs = ({ getState }) => (next) => (action) => { // capture the current workpad const currentWorkpad = getWorkpad(getState()); diff --git a/x-pack/plugins/canvas/public/state/middleware/element_stats.js b/x-pack/plugins/canvas/public/state/middleware/element_stats.js index 17ab9393734e0..1c19f26102996 100644 --- a/x-pack/plugins/canvas/public/state/middleware/element_stats.js +++ b/x-pack/plugins/canvas/public/state/middleware/element_stats.js @@ -7,7 +7,7 @@ import { setElementStats } from '../actions/transient'; import { getAllElements, getElementCounts, getElementStats } from '../selectors/workpad'; -export const elementStats = ({ dispatch, getState }) => next => action => { +export const elementStats = ({ dispatch, getState }) => (next) => (action) => { // execute the action next(action); diff --git a/x-pack/plugins/canvas/public/state/middleware/es_persist.js b/x-pack/plugins/canvas/public/state/middleware/es_persist.js index a197cdf893244..e0db595552198 100644 --- a/x-pack/plugins/canvas/public/state/middleware/es_persist.js +++ b/x-pack/plugins/canvas/public/state/middleware/es_persist.js @@ -40,9 +40,9 @@ export const esPersistMiddleware = ({ getState }) => { setRefreshInterval, // used to set refresh time interval which is a transient value ...Object.values(resolvedArgsActions), // no resolved args affect persisted values ...Object.values(transientActions), // no transient actions cause persisted state changes - ].map(a => a.toString()); + ].map((a) => a.toString()); - return next => action => { + return (next) => (action) => { // if the action is in the skipped list, do not persist if (skippedActions.indexOf(action.type) >= 0) { return next(action); @@ -58,7 +58,7 @@ export const esPersistMiddleware = ({ getState }) => { return; } - const notifyError = err => { + const notifyError = (err) => { const statusCode = err.response && err.response.status; switch (statusCode) { case 400: diff --git a/x-pack/plugins/canvas/public/state/middleware/fullscreen.js b/x-pack/plugins/canvas/public/state/middleware/fullscreen.js index 92ac35173b196..dfaf0a5e87b32 100644 --- a/x-pack/plugins/canvas/public/state/middleware/fullscreen.js +++ b/x-pack/plugins/canvas/public/state/middleware/fullscreen.js @@ -9,7 +9,7 @@ import { setFullscreen as setAppStateFullscreen } from '../../lib/app_state'; import { setFullscreen as setFullscreenAction } from '../actions/transient'; import { getFullscreen } from '../selectors/app'; -export const fullscreen = ({ getState }) => next => action => { +export const fullscreen = ({ getState }) => (next) => (action) => { // execute the default action next(action); diff --git a/x-pack/plugins/canvas/public/state/middleware/history.js b/x-pack/plugins/canvas/public/state/middleware/history.js index 206c86e93de91..28eb12e4b68da 100644 --- a/x-pack/plugins/canvas/public/state/middleware/history.js +++ b/x-pack/plugins/canvas/public/state/middleware/history.js @@ -22,8 +22,8 @@ function getHistoryState(state) { export const historyMiddleware = ({ dispatch, getState }) => { // iterate over routes, injecting redux to action handlers - const reduxInject = routes => { - return routes.map(route => { + const reduxInject = (routes) => { + return routes.map((route) => { if (route.children) { return { ...route, @@ -96,7 +96,7 @@ export const historyMiddleware = ({ dispatch, getState }) => { } }); - return next => action => { + return (next) => (action) => { const oldState = getState(); // deal with history actions diff --git a/x-pack/plugins/canvas/public/state/middleware/resolved_args.js b/x-pack/plugins/canvas/public/state/middleware/resolved_args.js index 7dd0404cd5a19..d47a320392039 100644 --- a/x-pack/plugins/canvas/public/state/middleware/resolved_args.js +++ b/x-pack/plugins/canvas/public/state/middleware/resolved_args.js @@ -11,17 +11,17 @@ import { clearValues } from '../actions/resolved_args'; * This middleware is responsible for keeping the resolved_args collection in transient state * synced with the elements represented by the workpad. */ -export const resolvedArgs = ({ dispatch, getState }) => next => action => { +export const resolvedArgs = ({ dispatch, getState }) => (next) => (action) => { // Get the Element IDs that are present before the action. - const startElementIds = getAllElements(getState()).map(element => element.id); + const startElementIds = getAllElements(getState()).map((element) => element.id); // execute the action next(action); // Get the Element IDs after the action... - const resolvedElementIds = getAllElements(getState()).map(element => element.id); + const resolvedElementIds = getAllElements(getState()).map((element) => element.id); // ...and get a list of IDs that are no longer present. - const deadIds = startElementIds.filter(id => !resolvedElementIds.includes(id)); + const deadIds = startElementIds.filter((id) => !resolvedElementIds.includes(id)); // If we have some dead elements, we need to clear them from resolved_args collection // in transient state. diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts index 700905213f54c..dd484521c1b35 100644 --- a/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts +++ b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts @@ -10,11 +10,13 @@ import { getFullscreen } from '../selectors/app'; import { getInFlight } from '../selectors/resolved_args'; import { getWorkpad, getPages, getSelectedPageIndex, getAutoplay } from '../selectors/workpad'; // @ts-ignore untyped local +import { appUnload } from '../actions/app'; +// @ts-ignore Untyped Local import { routerProvider } from '../../lib/router_provider'; import { setAutoplayInterval } from '../../lib/app_state'; import { createTimeInterval } from '../../lib/time_interval'; -export const workpadAutoplay: Middleware<{}, State> = ({ getState }) => next => { +export const workpadAutoplay: Middleware<{}, State> = ({ getState }) => (next) => { let playTimeout: number | undefined; let displayInterval = 0; @@ -62,7 +64,7 @@ export const workpadAutoplay: Middleware<{}, State> = ({ getState }) => next => } } - return action => { + return (action) => { next(action); const isFullscreen = getFullscreen(getState()); @@ -83,5 +85,9 @@ export const workpadAutoplay: Middleware<{}, State> = ({ getState }) => next => } else { stopAutoUpdate(); } + + if (action.type === appUnload.toString()) { + stopAutoUpdate(); + } }; }; diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts b/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts index f638c42ec2de0..96a84b22cfccc 100644 --- a/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts +++ b/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts @@ -10,13 +10,15 @@ import { State } from '../../../types'; import { fetchAllRenderables } from '../actions/elements'; // @ts-ignore Untyped Local import { setRefreshInterval } from '../actions/workpad'; +// @ts-ignore Untyped Local +import { appUnload } from '../actions/app'; import { inFlightComplete } from '../actions/resolved_args'; import { getInFlight } from '../selectors/resolved_args'; import { getRefreshInterval } from '../selectors/workpad'; import { setRefreshInterval as setAppStateRefreshInterval } from '../../lib/app_state'; import { createTimeInterval } from '../../lib/time_interval'; -export const workpadRefresh: Middleware<{}, State> = ({ dispatch, getState }) => next => { +export const workpadRefresh: Middleware<{}, State> = ({ dispatch, getState }) => (next) => { let refreshTimeout: number | undefined; let refreshInterval = 0; @@ -52,7 +54,7 @@ export const workpadRefresh: Middleware<{}, State> = ({ dispatch, getState }) => } } - return action => { + return (action) => { const previousRefreshInterval = getRefreshInterval(getState()); next(action); @@ -81,5 +83,9 @@ export const workpadRefresh: Middleware<{}, State> = ({ dispatch, getState }) => startDelayedUpdate(); } } + + if (action.type === appUnload.toString()) { + cancelDelayedUpdate(); + } }; }; diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_update.js b/x-pack/plugins/canvas/public/state/middleware/workpad_update.js index 76d699c68a19d..88c5b502b3bad 100644 --- a/x-pack/plugins/canvas/public/state/middleware/workpad_update.js +++ b/x-pack/plugins/canvas/public/state/middleware/workpad_update.js @@ -11,7 +11,7 @@ import { getWorkpadName, isWriteable } from '../selectors/workpad'; import { getWindow } from '../../lib/get_window'; import { setDocTitle } from '../../lib/doc_title'; -export const workpadUpdate = ({ dispatch, getState }) => next => action => { +export const workpadUpdate = ({ dispatch, getState }) => (next) => (action) => { const oldIsWriteable = isWriteable(getState()); const oldName = getWorkpadName(getState()); diff --git a/x-pack/plugins/canvas/public/state/reducers/__tests__/fixtures/action_creator.js b/x-pack/plugins/canvas/public/state/reducers/__tests__/fixtures/action_creator.js index b95186cdce99e..1e734c8261e4d 100644 --- a/x-pack/plugins/canvas/public/state/reducers/__tests__/fixtures/action_creator.js +++ b/x-pack/plugins/canvas/public/state/reducers/__tests__/fixtures/action_creator.js @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const actionCreator = type => { +export const actionCreator = (type) => { return (payload, error = null, meta = null) => ({ type, payload, error, meta }); }; diff --git a/x-pack/plugins/canvas/public/state/reducers/app.js b/x-pack/plugins/canvas/public/state/reducers/app.js index 8027bac4e1b09..18c8ba995ac69 100644 --- a/x-pack/plugins/canvas/public/state/reducers/app.js +++ b/x-pack/plugins/canvas/public/state/reducers/app.js @@ -9,7 +9,7 @@ import { appReady, appError } from '../actions/app'; export const appReducer = handleActions( { - [appReady]: appState => ({ ...appState, ready: true }), + [appReady]: (appState) => ({ ...appState, ready: true }), [appError]: (appState, { payload }) => ({ ...appState, ready: payload }), }, {} diff --git a/x-pack/plugins/canvas/public/state/reducers/elements.js b/x-pack/plugins/canvas/public/state/reducers/elements.js index 630694a860aad..5d6bba1ab7406 100644 --- a/x-pack/plugins/canvas/public/state/reducers/elements.js +++ b/x-pack/plugins/canvas/public/state/reducers/elements.js @@ -11,28 +11,28 @@ import * as actions from '../actions/elements'; const { assign, push, del, set } = immutable; -const getLocation = type => (type === 'group' ? 'groups' : 'elements'); +const getLocation = (type) => (type === 'group' ? 'groups' : 'elements'); const firstOccurrence = (element, index, array) => array.indexOf(element) === index; const getLocationFromIds = (workpadState, pageId, nodeId) => { - const page = workpadState.pages.find(p => p.id === pageId); + const page = workpadState.pages.find((p) => p.id === pageId); const groups = page == null ? [] : page.groups || []; - return groups.find(e => e.id === nodeId) ? 'groups' : 'elements'; + return groups.find((e) => e.id === nodeId) ? 'groups' : 'elements'; }; function getPageIndexById(workpadState, pageId) { - return get(workpadState, 'pages', []).findIndex(page => page.id === pageId); + return get(workpadState, 'pages', []).findIndex((page) => page.id === pageId); } function getNodeIndexById(page, nodeId, location) { - return page[location].findIndex(node => node.id === nodeId); + return page[location].findIndex((node) => node.id === nodeId); } export function assignNodeProperties(workpadState, pageId, nodeId, props) { const pageIndex = getPageIndexById(workpadState, pageId); const location = getLocationFromIds(workpadState, pageId, nodeId); const nodesPath = `pages.${pageIndex}.${location}`; - const nodeIndex = get(workpadState, nodesPath, []).findIndex(node => node.id === nodeId); + const nodeIndex = get(workpadState, nodesPath, []).findIndex((node) => node.id === nodeId); if (pageIndex === -1 || nodeIndex === -1) { return workpadState; @@ -47,7 +47,7 @@ function moveNodeLayer(workpadState, pageId, nodeId, movement, location) { const nodes = get(workpadState, ['pages', pageIndex, location]); const from = nodeIndex; - const to = (function() { + const to = (function () { if (movement < Infinity && movement > -Infinity) { return nodeIndex + movement; } @@ -88,8 +88,8 @@ const trimElement = ({ id, position, expression, filter }) => ({ }); const getPageWithElementId = (workpad, elementId) => { - const matchingPage = workpad.pages.find(page => - page.elements.map(element => element.id).includes(elementId) + const matchingPage = workpad.pages.find((page) => + page.elements.map((element) => element.id).includes(elementId) ); if (matchingPage) { @@ -131,7 +131,7 @@ export const elementsReducer = handleActions( if ( // don't add a group that is already persisted workpadState.pages[pageIndex][getLocation(element.position.type)].find( - e => e.id === element.id + (e) => e.id === element.id ) ) { return workpadState; @@ -165,7 +165,7 @@ export const elementsReducer = handleActions( const nodeIndices = elementIds .filter(firstOccurrence) - .map(nodeId => { + .map((nodeId) => { const location = getLocationFromIds(workpadState, pageId, nodeId); return { location, diff --git a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts index 9969c38cfa767..8642239fa10d3 100644 --- a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts +++ b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts @@ -29,15 +29,15 @@ export const embeddableReducer = handleActions< const { elementId, embeddableExpression } = payload; // Find the element - const pageWithElement = workpadState.pages.find(page => { - return page.elements.find(element => element.id === elementId) !== undefined; + const pageWithElement = workpadState.pages.find((page) => { + return page.elements.find((element) => element.id === elementId) !== undefined; }); if (!pageWithElement) { return workpadState; } - const element = pageWithElement.elements.find(elem => elem.id === elementId); + const element = pageWithElement.elements.find((elem) => elem.id === elementId); if (!element) { return workpadState; @@ -48,7 +48,7 @@ export const embeddableReducer = handleActions< const searchForFunction = newAst.chain[0].function; // Find the first matching function in the existing ASt - const existingAstFunction = existingAst.chain.find(f => f.function === searchForFunction); + const existingAstFunction = existingAst.chain.find((f) => f.function === searchForFunction); if (!existingAstFunction) { return workpadState; diff --git a/x-pack/plugins/canvas/public/state/reducers/pages.js b/x-pack/plugins/canvas/public/state/reducers/pages.js index 50a28475ef5bc..afcca8f54b5aa 100644 --- a/x-pack/plugins/canvas/public/state/reducers/pages.js +++ b/x-pack/plugins/canvas/public/state/reducers/pages.js @@ -22,7 +22,7 @@ const setPageIndex = (workpadState, index) => : set(workpadState, 'page', index); function getPageIndexById(workpadState, id) { - return workpadState.pages.findIndex(page => page.id === id); + return workpadState.pages.findIndex((page) => page.id === id); } function addPage(workpadState, payload, srcIndex = workpadState.pages.length - 1) { @@ -39,8 +39,8 @@ function clonePage(page) { return { ...page, id: getId('page'), - groups: newNodes.filter(n => isGroupId(n.id)), - elements: newNodes.filter(n => !isGroupId(n.id)), + groups: newNodes.filter((n) => isGroupId(n.id)), + elements: newNodes.filter((n) => !isGroupId(n.id)), }; } @@ -59,7 +59,7 @@ export const pagesReducer = handleActions( }, [actions.duplicatePage]: (workpadState, { payload }) => { - const srcPage = workpadState.pages.find(page => page.id === payload); + const srcPage = workpadState.pages.find((page) => page.id === payload); // if the page id is invalid, don't change the state if (!srcPage) { @@ -103,7 +103,7 @@ export const pagesReducer = handleActions( // adjust the selected page index and return the new state const selectedId = workpadState.pages[workpadState.page].id; - const newSelectedIndex = newState.pages.findIndex(page => page.id === selectedId); + const newSelectedIndex = newState.pages.findIndex((page) => page.id === selectedId); newState = set(newState, 'page', newSelectedIndex); // changes to the page require navigation @@ -144,12 +144,12 @@ export const pagesReducer = handleActions( }, [actions.stylePage]: (workpadState, { payload }) => { - const pageIndex = workpadState.pages.findIndex(page => page.id === payload.pageId); + const pageIndex = workpadState.pages.findIndex((page) => page.id === payload.pageId); return set(workpadState, `pages.${pageIndex}.style`, payload.style); }, [actions.setPageTransition]: (workpadState, { payload }) => { - const pageIndex = workpadState.pages.findIndex(page => page.id === payload.pageId); + const pageIndex = workpadState.pages.findIndex((page) => page.id === payload.pageId); return set(workpadState, `pages.${pageIndex}.transition`, payload.transition); }, }, diff --git a/x-pack/plugins/canvas/public/state/reducers/resolved_args.js b/x-pack/plugins/canvas/public/state/reducers/resolved_args.js index fa0be7702765d..0b020069aa8bc 100644 --- a/x-pack/plugins/canvas/public/state/reducers/resolved_args.js +++ b/x-pack/plugins/canvas/public/state/reducers/resolved_args.js @@ -102,11 +102,11 @@ export const resolvedArgsReducer = handleActions( }, transientState); }, - [actions.inFlightActive]: transientState => { + [actions.inFlightActive]: (transientState) => { return set(transientState, 'inFlight', true); }, - [actions.inFlightComplete]: transientState => { + [actions.inFlightComplete]: (transientState) => { return set(transientState, 'inFlight', false); }, diff --git a/x-pack/plugins/canvas/public/state/reducers/transient.js b/x-pack/plugins/canvas/public/state/reducers/transient.js index 0b89dfd3c9564..c6a4466eeb145 100644 --- a/x-pack/plugins/canvas/public/state/reducers/transient.js +++ b/x-pack/plugins/canvas/public/state/reducers/transient.js @@ -18,14 +18,14 @@ export const transientReducer = handleActions( { // clear all the resolved args when restoring the history // TODO: we shouldn't need to reset the resolved args for history - [restoreHistory]: transientState => set(transientState, 'resolvedArgs', {}), + [restoreHistory]: (transientState) => set(transientState, 'resolvedArgs', {}), [removeElements]: (transientState, { payload: { elementIds } }) => { const { selectedToplevelNodes } = transientState; return del( { ...transientState, - selectedToplevelNodes: selectedToplevelNodes.filter(n => elementIds.indexOf(n) < 0), + selectedToplevelNodes: selectedToplevelNodes.filter((n) => elementIds.indexOf(n) < 0), }, ['resolvedArgs', elementIds] ); @@ -57,15 +57,15 @@ export const transientReducer = handleActions( }; }, - [pageActions.setPage]: transientState => { + [pageActions.setPage]: (transientState) => { return { ...transientState, selectedToplevelNodes: [] }; }, - [pageActions.addPage]: transientState => { + [pageActions.addPage]: (transientState) => { return { ...transientState, selectedToplevelNodes: [] }; }, - [pageActions.duplicatePage]: transientState => { + [pageActions.duplicatePage]: (transientState) => { return { ...transientState, selectedToplevelNodes: [] }; }, diff --git a/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js b/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js index cf22a2c81c35f..5fdc662c592cc 100644 --- a/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js +++ b/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js @@ -175,7 +175,7 @@ describe('workpad selectors', () => { it('returns all elements on the page', () => { const { elements } = state.persistent.workpad.pages[0]; - const expected = elements.map(element => ({ + const expected = elements.map((element) => ({ ...element, ast: asts[element.id], })); diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/plugins/canvas/public/state/selectors/workpad.ts index ae5c0fee52062..4b3431e59a34e 100644 --- a/x-pack/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/plugins/canvas/public/state/selectors/workpad.ts @@ -74,12 +74,12 @@ export function getPages(state: State): State['persistent']['workpad']['pages'] export function getPageById(state: State, id: string): CanvasPage | undefined { const pages = getPages(state); - return pages.find(page => page.id === id); + return pages.find((page) => page.id === id); } export function getPageIndexById(state: State, id: string): number { const pages = getPages(state); - return pages.findIndex(page => page.id === id); + return pages.findIndex((page) => page.id === id); } export function getWorkpadName(state: State): string { @@ -151,7 +151,7 @@ export function getElementCounts(state: State) { .filter( (maybeResolvedArg): maybeResolvedArg is ResolvedArgType => maybeResolvedArg !== undefined ) - .forEach(resolvedArg => { + .forEach((resolvedArg) => { const { expressionRenderable } = resolvedArg; if (!expressionRenderable) { @@ -224,7 +224,7 @@ function extractFilterGroups( if (fn === 'filters') { // we have a filter function, extract groups from args return groups.concat( - buildGroupValues(args, argValue => { + buildGroupValues(args, (argValue) => { // this only handles simple values if (argValue !== null && typeof argValue !== 'object') { return argValue; @@ -234,7 +234,7 @@ function extractFilterGroups( } else { // dig into other functions, looking for filters function return groups.concat( - buildGroupValues(args, argValue => { + buildGroupValues(args, (argValue) => { // recursively collect filter groups if (argValue !== null && typeof argValue === 'object' && argValue.type === 'expression') { return extractFilterGroups(argValue); @@ -268,7 +268,7 @@ export function getGlobalFilterGroups(state: State) { | ExpressionAstFunction | ExpressionAstExpression; const groups = extractFilterGroups(expressionAst); - groups.forEach(group => { + groups.forEach((group) => { if (!acc.includes(String(group))) { acc.push(String(group)); } @@ -318,7 +318,7 @@ export function getElements( // due to https://github.com/elastic/kibana-canvas/issues/260 // TODO: remove this once it's been in the wild a bit if (!withAst) { - return elements.map(el => omit(el, ['ast'])); + return elements.map((el) => omit(el, ['ast'])); } return elements.map(appendAst); @@ -354,7 +354,7 @@ export function getNodesForPage(page: CanvasPage, withAst: boolean): CanvasEleme // due to https://github.com/elastic/kibana-canvas/issues/260 // TODO: remove this once it's been in the wild a bit if (!withAst) { - return elements.map(el => omit(el, ['ast'])); + return elements.map((el) => omit(el, ['ast'])); } return elements.map(appendAst); @@ -384,7 +384,7 @@ export function getElementById( id: string | null, pageId?: string ): PositionedElement | undefined { - const element = getElements(state, pageId, true).find(el => el.id === id); + const element = getElements(state, pageId, true).find((el) => el.id === id); if (element) { return appendAst(element); } @@ -396,7 +396,7 @@ export function getNodeById( pageId: string ): PositionedElement | undefined { // do we need to pass a truthy empty array instead of `true`? - const group = getNodes(state, pageId, true).find(el => el.id === id); + const group = getNodes(state, pageId, true).find((el) => el.id === id); if (group) { return appendAst(group); } @@ -437,11 +437,11 @@ export function getAutoplay(state: State): State['transient']['autoplay'] { export function getRenderedWorkpad(state: State) { const currentPages = getPages(state); const args = state.transient.resolvedArgs; - const renderedPages = currentPages.map(page => { + const renderedPages = currentPages.map((page) => { const { elements, ...rest } = page; return { ...rest, - elements: elements.map(element => { + elements: elements.map((element) => { const { id, position } = element; const arg = args[id]; if (!arg) { @@ -470,8 +470,8 @@ export function getRenderedWorkpadExpressions(state: State) { const { pages } = workpad; const expressions: string[] = []; - pages.forEach(page => - page.elements.forEach(element => { + pages.forEach((page) => + page.elements.forEach((element) => { if (element && element.expressionRenderable) { const { value } = element.expressionRenderable; if (value) { diff --git a/x-pack/plugins/canvas/public/state/store.js b/x-pack/plugins/canvas/public/state/store.js index 891f30ca4114f..e44026b3b0a61 100644 --- a/x-pack/plugins/canvas/public/state/store.js +++ b/x-pack/plugins/canvas/public/state/store.js @@ -11,6 +11,16 @@ import { getRootReducer } from './reducers'; let store; +export function getStore() { + return store; +} + +export function cloneStore() { + const state = store.getState(); + store = undefined; + return createStore(state); +} + export function createStore(initialState) { if (typeof store !== 'undefined') { throw new Error('Redux store can only be initialized once'); @@ -29,7 +39,7 @@ export function createStore(initialState) { export function destroyStore() { if (store) { // Replace reducer so that anything that gets fired after navigating away doesn't really do anything - store.replaceReducer(state => state); + store.replaceReducer((state) => state); } store = undefined; } diff --git a/x-pack/plugins/canvas/public/store.ts b/x-pack/plugins/canvas/public/store.ts index 8560e612de4db..81edec6ec539c 100644 --- a/x-pack/plugins/canvas/public/store.ts +++ b/x-pack/plugins/canvas/public/store.ts @@ -4,8 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore Untyped local -import { createStore as createReduxStore, destroyStore as destroy } from './state/store'; +import { + createStore as createReduxStore, + destroyStore as destroy, + getStore, + cloneStore, + // @ts-ignore Untyped local +} from './state/store'; // @ts-ignore Untyped local import { getInitialState } from './state/initial_state'; @@ -13,6 +18,14 @@ import { CoreSetup } from '../../../../src/core/public'; import { CanvasSetupDeps } from './plugin'; export async function createStore(core: CoreSetup, plugins: CanvasSetupDeps) { + if (getStore()) { + return cloneStore(); + } + + return createFreshStore(core, plugins); +} + +export async function createFreshStore(core: CoreSetup, plugins: CanvasSetupDeps) { const initialState = getInitialState(); const basePath = core.http.basePath.get(); diff --git a/x-pack/plugins/canvas/scripts/_helpers.js b/x-pack/plugins/canvas/scripts/_helpers.js index 0f4b07d988308..7f386edc49117 100644 --- a/x-pack/plugins/canvas/scripts/_helpers.js +++ b/x-pack/plugins/canvas/scripts/_helpers.js @@ -6,19 +6,19 @@ const { resolve } = require('path'); -exports.runGulpTask = function(name) { +exports.runGulpTask = function (name) { process.chdir(resolve(__dirname, '../../..')); process.argv.splice(1, 1, require.resolve('gulp/bin/gulp'), name); require('gulp/bin/gulp'); }; -exports.runKibanaScript = function(name, args = []) { +exports.runKibanaScript = function (name, args = []) { process.chdir(resolve(__dirname, '../../../..')); process.argv.splice(2, 0, ...args); require('../../../../scripts/' + name); // eslint-disable-line import/no-dynamic-require }; -exports.runXPackScript = function(name, args = []) { +exports.runXPackScript = function (name, args = []) { process.chdir(resolve(__dirname, '../../..')); process.argv.splice(2, 0, ...args); require('../../../scripts/' + name); // eslint-disable-line import/no-dynamic-require diff --git a/x-pack/plugins/canvas/server/collectors/collector.ts b/x-pack/plugins/canvas/server/collectors/collector.ts index 88e5e43dc2af1..e266e9826a47d 100644 --- a/x-pack/plugins/canvas/server/collectors/collector.ts +++ b/x-pack/plugins/canvas/server/collectors/collector.ts @@ -35,7 +35,7 @@ export function registerCanvasUsageCollector( isReady: () => true, fetch: async (callCluster: CallCluster) => { const collectorResults = await Promise.all( - collectors.map(collector => collector(kibanaIndex, callCluster)) + collectors.map((collector) => collector(kibanaIndex, callCluster)) ); return collectorResults.reduce( diff --git a/x-pack/plugins/canvas/server/collectors/collector_helpers.ts b/x-pack/plugins/canvas/server/collectors/collector_helpers.ts index 73de691dae05f..a6b04661f3da1 100644 --- a/x-pack/plugins/canvas/server/collectors/collector_helpers.ts +++ b/x-pack/plugins/canvas/server/collectors/collector_helpers.ts @@ -27,8 +27,8 @@ export function collectFns(ast: ExpressionAstNode, cb: (functionName: string) => cb(cFunction); // recurse the arguments and update the set along the way - Object.keys(cArguments).forEach(argName => { - cArguments[argName].forEach(subAst => { + Object.keys(cArguments).forEach((argName) => { + cArguments[argName].forEach((subAst) => { if (subAst != null) { collectFns(subAst, cb); } diff --git a/x-pack/plugins/canvas/server/collectors/custom_element_collector.test.ts b/x-pack/plugins/canvas/server/collectors/custom_element_collector.test.ts index f09bb704b09e3..ae65eb42bb20d 100644 --- a/x-pack/plugins/canvas/server/collectors/custom_element_collector.test.ts +++ b/x-pack/plugins/canvas/server/collectors/custom_element_collector.test.ts @@ -10,7 +10,7 @@ import { TelemetryCustomElementDocument } from '../../types'; function mockCustomElement(...nodeExpressions: string[]): TelemetryCustomElementDocument { return { content: JSON.stringify({ - selectedNodes: nodeExpressions.map(expression => ({ + selectedNodes: nodeExpressions.map((expression) => ({ expression, })), }), diff --git a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts index ae71600d24e4b..3ada8e7b4efdc 100644 --- a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts @@ -60,7 +60,7 @@ export function summarizeCustomElements( const functionSet = new Set(); const parsedContents: TelemetryCustomElement[] = customElements - .map(element => element.content) + .map((element) => element.content) .map(parseJsonOrNull) .filter(isCustomElement); @@ -76,8 +76,8 @@ export function summarizeCustomElements( let totalElements = 0; - parsedContents.map(contents => { - contents.selectedNodes.map(node => { + parsedContents.map((contents) => { + contents.selectedNodes.map((node) => { const ast = parseExpression(node.expression); collectFns(ast, (cFunction: string) => { functionSet.add(cFunction); @@ -114,7 +114,7 @@ const customElementCollector: TelemetryCollector = async function customElementC const esResponse = await callCluster('search', customElementParams); if (get(esResponse, 'hits.hits.length') > 0) { - const customElements = esResponse.hits.hits.map(hit => hit._source[CUSTOM_ELEMENT_TYPE]); + const customElements = esResponse.hits.hits.map((hit) => hit._source[CUSTOM_ELEMENT_TYPE]); return summarizeCustomElements(customElements); } diff --git a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts index b14a35ded4781..3d394afaeba50 100644 --- a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts @@ -59,7 +59,7 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr } // make a summary of info about each workpad - const workpadsInfo = workpadDocs.map(workpad => { + const workpadsInfo = workpadDocs.map((workpad) => { let pages = { count: 0 }; try { pages = { count: workpad.pages.length }; @@ -72,9 +72,9 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr [] ); const functionCounts = workpad.pages.reduce((accum, page) => { - return page.elements.map(element => { + return page.elements.map((element) => { const ast = parseExpression(element.expression); - collectFns(ast, cFunction => { + collectFns(ast, (cFunction) => { functionSet.add(cFunction); }); return ast.chain.length; // get the number of parts in the expression @@ -159,7 +159,7 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr }; } -const workpadCollector: TelemetryCollector = async function(kibanaIndex, callCluster) { +const workpadCollector: TelemetryCollector = async function (kibanaIndex, callCluster) { const searchParams: SearchParams = { size: 10000, // elasticsearch index.max_result_window default value index: kibanaIndex, @@ -171,7 +171,7 @@ const workpadCollector: TelemetryCollector = async function(kibanaIndex, callClu const esResponse = await callCluster('search', searchParams); if (get(esResponse, 'hits.hits.length') > 0) { - const workpads = esResponse.hits.hits.map(hit => hit._source[CANVAS_TYPE]); + const workpads = esResponse.hits.hits.map((hit) => hit._source[CANVAS_TYPE]); return summarizeWorkpads(workpads); } diff --git a/x-pack/plugins/canvas/server/lib/build_bool_array.js b/x-pack/plugins/canvas/server/lib/build_bool_array.js index 2dc6447753526..f1cab93ceebbb 100644 --- a/x-pack/plugins/canvas/server/lib/build_bool_array.js +++ b/x-pack/plugins/canvas/server/lib/build_bool_array.js @@ -6,11 +6,11 @@ import { getESFilter } from './get_es_filter'; -const compact = arr => (Array.isArray(arr) ? arr.filter(val => Boolean(val)) : []); +const compact = (arr) => (Array.isArray(arr) ? arr.filter((val) => Boolean(val)) : []); export function buildBoolArray(canvasQueryFilterArray) { return compact( - canvasQueryFilterArray.map(clause => { + canvasQueryFilterArray.map((clause) => { try { return getESFilter(clause); } catch (e) { diff --git a/x-pack/plugins/canvas/server/lib/format_response.js b/x-pack/plugins/canvas/server/lib/format_response.js index d52e2819465fe..a317ca8be56f8 100644 --- a/x-pack/plugins/canvas/server/lib/format_response.js +++ b/x-pack/plugins/canvas/server/lib/format_response.js @@ -5,7 +5,7 @@ */ import boom from 'boom'; -export const formatResponse = esErrors => resp => { +export const formatResponse = (esErrors) => (resp) => { if (resp.isBoom) { return resp; } // can't wrap it if it's already a boom error diff --git a/x-pack/plugins/canvas/server/lib/normalize_type.js b/x-pack/plugins/canvas/server/lib/normalize_type.js index 07f1cac0f7819..fda2fbe631646 100644 --- a/x-pack/plugins/canvas/server/lib/normalize_type.js +++ b/x-pack/plugins/canvas/server/lib/normalize_type.js @@ -24,7 +24,7 @@ export function normalizeType(type) { null: ['null'], }; - const normalizedType = Object.keys(normalTypes).find(t => normalTypes[t].includes(type)); + const normalizedType = Object.keys(normalTypes).find((t) => normalTypes[t].includes(type)); if (normalizedType) { return normalizedType; diff --git a/x-pack/plugins/canvas/server/lib/query_es_sql.js b/x-pack/plugins/canvas/server/lib/query_es_sql.js index f7907e2cffb26..442703b00ea3a 100644 --- a/x-pack/plugins/canvas/server/lib/query_es_sql.js +++ b/x-pack/plugins/canvas/server/lib/query_es_sql.js @@ -24,12 +24,12 @@ export const queryEsSQL = (elasticsearchClient, { count, query, filter, timezone }, }, }) - .then(res => { + .then((res) => { const columns = res.columns.map(({ name, type }) => { return { name: sanitizeName(name), type: normalizeType(type) }; }); const columnNames = map(columns, 'name'); - const rows = res.rows.map(row => zipObject(columnNames, row)); + const rows = res.rows.map((row) => zipObject(columnNames, row)); if (!!res.cursor) { elasticsearchClient('transport.request', { @@ -38,7 +38,7 @@ export const queryEsSQL = (elasticsearchClient, { count, query, filter, timezone body: { cursor: res.cursor, }, - }).catch(e => { + }).catch((e) => { throw new Error(`Unexpected error from Elasticsearch: ${e.message}`); }); } @@ -49,7 +49,7 @@ export const queryEsSQL = (elasticsearchClient, { count, query, filter, timezone rows, }; }) - .catch(e => { + .catch((e) => { if (e.message.indexOf('parsing_exception') > -1) { throw new Error( `Couldn't parse Elasticsearch SQL query. You may need to add double quotes to names containing special characters. Check your query and try again. Error: ${e.message}` diff --git a/x-pack/plugins/canvas/server/lib/sanitize_name.js b/x-pack/plugins/canvas/server/lib/sanitize_name.js index 623b64ca04ae8..295315c3ceb2e 100644 --- a/x-pack/plugins/canvas/server/lib/sanitize_name.js +++ b/x-pack/plugins/canvas/server/lib/sanitize_name.js @@ -7,7 +7,7 @@ export function sanitizeName(name) { // blacklisted characters const blacklist = ['(', ')']; - const pattern = blacklist.map(v => escapeRegExp(v)).join('|'); + const pattern = blacklist.map((v) => escapeRegExp(v)).join('|'); const regex = new RegExp(pattern, 'g'); return name.replace(regex, '_'); } diff --git a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts index 4717d8762ffe2..6c684449be5ca 100644 --- a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts +++ b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts @@ -8,7 +8,7 @@ import { RequestHandler } from 'src/core/server'; export const catchErrorHandler: ( fn: RequestHandler -) => RequestHandler = fn => { +) => RequestHandler = (fn) => { return async (context, request, response) => { try { return await fn(context, request, response); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/find.ts b/x-pack/plugins/canvas/server/routes/custom_elements/find.ts index 964618adf165d..bd5f226bda57c 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/find.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/find.ts @@ -50,7 +50,7 @@ export function initializeFindCustomElementsRoute(deps: RouteInitializerDeps) { return response.ok({ body: { total: customElements.total, - customElements: customElements.saved_objects.map(hit => ({ + customElements: customElements.saved_objects.map((hit) => ({ id: hit.id, ...hit.attributes, })), diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts index c96856d09256b..c3588957ff68e 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -20,7 +20,7 @@ import { const mockRouteContext = ({ core: { - elasticsearch: { dataClient: elasticsearchServiceMock.createScopedClusterClient() }, + elasticsearch: { legacy: { client: elasticsearchServiceMock.createScopedClusterClient() } }, }, } as unknown) as RequestHandlerContext; @@ -76,7 +76,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockResolvedValueOnce(mockResults); @@ -104,7 +104,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockResolvedValueOnce(mockResults); @@ -132,7 +132,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockResolvedValueOnce(mockResults); @@ -152,7 +152,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockRejectedValueOnce(new Error('Index not found')); diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts index 5ebf20095bf3e..8f3ced13895f6 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts @@ -28,7 +28,7 @@ export function initializeESFieldsRoute(deps: RouteInitializerDeps) { }, }, catchErrorHandler(async (context, request, response) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const { index, fields } = request.query; const config = { @@ -36,8 +36,8 @@ export function initializeESFieldsRoute(deps: RouteInitializerDeps) { fields: fields || '*', }; - const esFields = await callAsCurrentUser('fieldCaps', config).then(resp => { - return mapValues(resp.fields, types => { + const esFields = await callAsCurrentUser('fieldCaps', config).then((resp) => { + return mapValues(resp.fields, (types) => { if (keys(types).length > 1) { return 'conflict'; } diff --git a/x-pack/plugins/canvas/server/routes/workpad/find.ts b/x-pack/plugins/canvas/server/routes/workpad/find.ts index ec5c068a1fa24..b17d5d7d4bb39 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/find.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/find.ts @@ -41,7 +41,7 @@ export function initializeFindWorkpadsRoute(deps: RouteInitializerDeps) { return response.ok({ body: { total: workpads.total, - workpads: workpads.saved_objects.map(hit => ({ id: hit.id, ...hit.attributes })), + workpads: workpads.saved_objects.map((hit) => ({ id: hit.id, ...hit.attributes })), }, }); } catch (error) { diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.ts b/x-pack/plugins/canvas/server/routes/workpad/get.ts index 7dc1252063e90..e4c0d162782f0 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/get.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/get.ts @@ -34,7 +34,7 @@ export function initializeGetWorkpadRoute(deps: RouteInitializerDeps) { workpad.attributes.pages && workpad.attributes.pages.length ) { - workpad.attributes.pages.forEach(page => { + workpad.attributes.pages.forEach((page) => { const elements = (page.elements || []).filter( ({ id: pageId }) => !pageId.startsWith('group') ); diff --git a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts index 6eda02ef41722..f58111000859a 100644 --- a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts +++ b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts @@ -19,7 +19,7 @@ export function loadSampleData( // @ts-ignore: Untyped local function updateCanvasWorkpadTimestamps(savedObjects) { // @ts-ignore: Untyped local - return savedObjects.map(savedObject => { + return savedObjects.map((savedObject) => { if (savedObject.type === 'canvas-workpad') { savedObject.attributes['@timestamp'] = nowTimestamp; savedObject.attributes['@created'] = nowTimestamp; diff --git a/x-pack/plugins/canvas/server/saved_objects/migrations/remove_attributes_id.ts b/x-pack/plugins/canvas/server/saved_objects/migrations/remove_attributes_id.ts index 893a73d7b5913..306224ce05bbf 100644 --- a/x-pack/plugins/canvas/server/saved_objects/migrations/remove_attributes_id.ts +++ b/x-pack/plugins/canvas/server/saved_objects/migrations/remove_attributes_id.ts @@ -6,7 +6,7 @@ import { SavedObjectMigrationFn } from 'src/core/server'; -export const removeAttributesId: SavedObjectMigrationFn = doc => { +export const removeAttributesId: SavedObjectMigrationFn = (doc) => { if (typeof doc.attributes === 'object' && doc.attributes !== null) { delete (doc.attributes as any).id; } diff --git a/x-pack/plugins/canvas/shareable_runtime/__mocks__/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/__mocks__/supported_renderers.js index 051082107a2e6..6792568583617 100644 --- a/x-pack/plugins/canvas/shareable_runtime/__mocks__/supported_renderers.js +++ b/x-pack/plugins/canvas/shareable_runtime/__mocks__/supported_renderers.js @@ -27,12 +27,12 @@ const renderers = [ * Mock all of the render functions to return a `div` containing * a predictable string. */ -export const renderFunctions = renderers.map(fn => () => ({ +export const renderFunctions = renderers.map((fn) => () => ({ name: fn, displayName: fn, help: fn, reuseDomNode: true, - render: domNode => { + render: (domNode) => { ReactDOM.render(

{fn} mock
, domNode); }, })); diff --git a/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx b/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx index a55c87c2d74a2..d99c9b190f83d 100644 --- a/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx @@ -14,7 +14,7 @@ jest.mock('../../supported_renderers'); describe('Canvas Shareable Workpad API', () => { // Mock the AJAX load of the workpad. - beforeEach(function() { + beforeEach(function () { // @ts-ignore Applying a global in Jest is alright. global.fetch = jest.fn().mockImplementation(() => { const p = new Promise((resolve, _reject) => { diff --git a/x-pack/plugins/canvas/shareable_runtime/api/shareable.tsx b/x-pack/plugins/canvas/shareable_runtime/api/shareable.tsx index 11289d3c37970..96acee401d52d 100644 --- a/x-pack/plugins/canvas/shareable_runtime/api/shareable.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/api/shareable.tsx @@ -63,7 +63,7 @@ const VALID_ATTRIBUTES = ['url', 'page', 'height', 'width', 'autoplay', 'interva // Collect and then remove valid data attributes. const getAttributes = (element: Element, attributes: string[]) => { const result: { [key: string]: string } = {}; - attributes.forEach(attribute => { + attributes.forEach((attribute) => { const key = `${PREFIX}-${attribute}`; const value = element.getAttribute(key); @@ -150,7 +150,7 @@ const updateArea = async (area: Element) => { export const share = () => { const shareAreas = document.querySelectorAll(`[${SHAREABLE}]`); const validAreas = Array.from(shareAreas).filter( - area => area.getAttribute(SHAREABLE) === 'canvas' + (area) => area.getAttribute(SHAREABLE) === 'canvas' ); validAreas.forEach(updateArea); diff --git a/x-pack/plugins/canvas/shareable_runtime/components/app.tsx b/x-pack/plugins/canvas/shareable_runtime/components/app.tsx index 1779ec4846cb7..fdc64d32cf8e5 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/app.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/app.tsx @@ -29,7 +29,7 @@ interface Props { export const App: FC = ({ workpad, stage }) => { const renderers: { [key: string]: RendererSpec } = {}; - renderFunctions.forEach(fn => { + renderFunctions.forEach((fn) => { const func = fn(); renderers[func.name] = func; }); diff --git a/x-pack/plugins/canvas/shareable_runtime/components/canvas.tsx b/x-pack/plugins/canvas/shareable_runtime/components/canvas.tsx index 7d7067da09ee6..b1eb9af6fc4a1 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/canvas.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/canvas.tsx @@ -66,8 +66,9 @@ export const CanvasComponent = ({ const { height: stageHeight, width: stageWidth, page } = stage; const { height: workpadHeight, width: workpadWidth } = workpad; const ratio = Math.max(workpadWidth / stageWidth, workpadHeight / stageHeight); - const transform = `scale3d(${stageHeight / (stageHeight * ratio)}, ${stageWidth / - (stageWidth * ratio)}, 1)`; + const transform = `scale3d(${stageHeight / (stageHeight * ratio)}, ${ + stageWidth / (stageWidth * ratio) + }, 1)`; const pageStyle = { height: workpadHeight, diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx index 66515eb3421d5..34dacc7956253 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx @@ -77,9 +77,7 @@ describe('', () => { expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot(); // Click the Hide Toolbar switch - portal(wrapper) - .find('button[data-test-subj="hideToolbarSwitch"]') - .simulate('click'); + portal(wrapper).find('button[data-test-subj="hideToolbarSwitch"]').simulate('click'); portal(wrapper).update(); diff --git a/x-pack/plugins/canvas/shareable_runtime/index.ts b/x-pack/plugins/canvas/shareable_runtime/index.ts index 8b29b23c4442f..11ada0a468cb2 100644 --- a/x-pack/plugins/canvas/shareable_runtime/index.ts +++ b/x-pack/plugins/canvas/shareable_runtime/index.ts @@ -15,7 +15,7 @@ const css = require.context( true, /\.\/plugins\/(?!canvas).*light\.css/ ); -css.keys().forEach(filename => { +css.keys().forEach((filename) => { css(filename); }); @@ -24,4 +24,4 @@ const uiStyles = require.context( false, /[\/\\](?!mixins|variables|_|\.|bootstrap_(light|dark))[^\/\\]+\.less/ ); -uiStyles.keys().forEach(key => uiStyles(key)); +uiStyles.keys().forEach((key) => uiStyles(key)); diff --git a/x-pack/plugins/canvas/shareable_runtime/postcss.config.js b/x-pack/plugins/canvas/shareable_runtime/postcss.config.js index 2e83c8b33e26e..10baaddfc9b05 100644 --- a/x-pack/plugins/canvas/shareable_runtime/postcss.config.js +++ b/x-pack/plugins/canvas/shareable_runtime/postcss.config.js @@ -12,7 +12,7 @@ module.exports = { plugins: [ prefixer({ prefix: '.kbnCanvas', - transform: function(prefix, selector, prefixedSelector) { + transform: function (prefix, selector, prefixedSelector) { if (selector === 'body' || selector === 'html') { return prefix; } else { diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js index 526bf3c9d6fb9..6238aaf5c2fe4 100644 --- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js +++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js @@ -43,4 +43,4 @@ export const renderFunctions = [ text, ]; -export const renderFunctionNames = renderFunctions.map(fn => fn.name); +export const renderFunctionNames = renderFunctions.map((fn) => fn.name); diff --git a/x-pack/plugins/canvas/shareable_runtime/test/context.tsx b/x-pack/plugins/canvas/shareable_runtime/test/context.tsx index 9361655d4af6e..b9738359ba5b5 100644 --- a/x-pack/plugins/canvas/shareable_runtime/test/context.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/test/context.tsx @@ -59,7 +59,7 @@ export const Context = ({ }: Props) => { const renderers: { [key: string]: RendererSpec } = {}; - renderFunctions.forEach(rendererFn => { + renderFunctions.forEach((rendererFn) => { const renderer = rendererFn(); renderers[renderer.name] = renderer; }); diff --git a/x-pack/plugins/canvas/shareable_runtime/test/interactions.ts b/x-pack/plugins/canvas/shareable_runtime/test/interactions.ts index 1c5b78929aaa5..360b9fdce51a1 100644 --- a/x-pack/plugins/canvas/shareable_runtime/test/interactions.ts +++ b/x-pack/plugins/canvas/shareable_runtime/test/interactions.ts @@ -8,7 +8,7 @@ import { ReactWrapper } from 'enzyme'; import { getSettingsTrigger, getPortal, getContextMenuItems } from './selectors'; import { waitFor } from './utils'; -export const openSettings = async function(wrapper: ReactWrapper) { +export const openSettings = async function (wrapper: ReactWrapper) { getSettingsTrigger(wrapper).simulate('click'); try { @@ -16,19 +16,15 @@ export const openSettings = async function(wrapper: ReactWrapper) { await waitFor(() => { wrapper.update(); - return getPortal(wrapper) - .find('EuiPanel') - .exists(); + return getPortal(wrapper).find('EuiPanel').exists(); }); } catch (e) { throw new Error('Settings Panel did not open in given time'); } }; -export const selectMenuItem = async function(wrapper: ReactWrapper, menuItemIndex: number) { - getContextMenuItems(wrapper) - .at(menuItemIndex) - .simulate('click'); +export const selectMenuItem = async function (wrapper: ReactWrapper, menuItemIndex: number) { + getContextMenuItems(wrapper).at(menuItemIndex).simulate('click'); try { // When the menu item is clicked, wait for all of the context menus to be there diff --git a/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts b/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts index 852c4ec9aadac..8068ccbbab83a 100644 --- a/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts +++ b/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts @@ -19,22 +19,16 @@ export const getToolbarPanel = (wrapper: ReactWrapper) => wrapper.find('ToolbarSettings > ToolbarSettingsComponent'); export const getToolbarCheckbox = (wrapper: ReactWrapper) => - getToolbarPanel(wrapper) - .find('EuiSwitch') - .find('button'); + getToolbarPanel(wrapper).find('EuiSwitch').find('button'); export const getAutoplayPanel = (wrapper: ReactWrapper) => wrapper.find('AutoplaySettings > AutoplaySettingsComponent'); export const getAutoplayCheckbox = (wrapper: ReactWrapper) => - getAutoplayPanel(wrapper) - .find('EuiSwitch') - .find('button'); + getAutoplayPanel(wrapper).find('EuiSwitch').find('button'); export const getAutoplayTextField = (wrapper: ReactWrapper) => - getAutoplayPanel(wrapper) - .find('EuiFieldText') - .find('input[type="text"]'); + getAutoplayPanel(wrapper).find('EuiFieldText').find('input[type="text"]'); export const getAutoplaySubmit = (wrapper: ReactWrapper) => getAutoplayPanel(wrapper).find('EuiButton'); diff --git a/x-pack/plugins/canvas/shareable_runtime/test/utils.ts b/x-pack/plugins/canvas/shareable_runtime/test/utils.ts index 4e18f2af1b06a..fe3c1be9ba154 100644 --- a/x-pack/plugins/canvas/shareable_runtime/test/utils.ts +++ b/x-pack/plugins/canvas/shareable_runtime/test/utils.ts @@ -8,7 +8,7 @@ import { ReactWrapper } from 'enzyme'; import { Component } from 'react'; export const tick = (ms = 0) => - new Promise(resolve => { + new Promise((resolve) => { setTimeout(resolve, ms); }); diff --git a/x-pack/plugins/canvas/tasks/mocks/downloadWorkpad.js b/x-pack/plugins/canvas/tasks/mocks/downloadWorkpad.js index 3571448c11aa9..ca8d1c10d870f 100644 --- a/x-pack/plugins/canvas/tasks/mocks/downloadWorkpad.js +++ b/x-pack/plugins/canvas/tasks/mocks/downloadWorkpad.js @@ -3,11 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -export const downloadWorkpad = async workpadId => console.log(`Download workpad ${workpadId}`); +export const downloadWorkpad = async (workpadId) => console.log(`Download workpad ${workpadId}`); -export const downloadRenderedWorkpad = async renderedWorkpad => +export const downloadRenderedWorkpad = async (renderedWorkpad) => console.log(`Download workpad ${renderedWorkpad.id}`); -export const downloadRuntime = async basePath => console.log(`Download run time at ${basePath}`); +export const downloadRuntime = async (basePath) => console.log(`Download run time at ${basePath}`); -export const downloadZippedRuntime = async data => console.log(`Downloading data ${data}`); +export const downloadZippedRuntime = async (data) => console.log(`Downloading data ${data}`); diff --git a/x-pack/plugins/canvas/tasks/mocks/uiNotify.js b/x-pack/plugins/canvas/tasks/mocks/uiNotify.js index ad313bc69da45..d29e66e8287eb 100644 --- a/x-pack/plugins/canvas/tasks/mocks/uiNotify.js +++ b/x-pack/plugins/canvas/tasks/mocks/uiNotify.js @@ -5,9 +5,9 @@ */ const notifierProto = { - error: msg => `error: ${msg}`, - warning: msg => `warning: ${msg}`, - info: msg => `info: ${msg}`, + error: (msg) => `error: ${msg}`, + warning: (msg) => `warning: ${msg}`, + info: (msg) => `info: ${msg}`, }; export class Notifier { diff --git a/x-pack/plugins/canvas/tasks/mocks/uiNotifyFormatMsg.js b/x-pack/plugins/canvas/tasks/mocks/uiNotifyFormatMsg.js index 480a750e0a9ce..4df51dd284933 100644 --- a/x-pack/plugins/canvas/tasks/mocks/uiNotifyFormatMsg.js +++ b/x-pack/plugins/canvas/tasks/mocks/uiNotifyFormatMsg.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const formatMsg = str => str; +export const formatMsg = (str) => str; diff --git a/x-pack/plugins/case/common/api/saved_object.ts b/x-pack/plugins/case/common/api/saved_object.ts index fac8edd0ebea1..73fe767dd717e 100644 --- a/x-pack/plugins/case/common/api/saved_object.ts +++ b/x-pack/plugins/case/common/api/saved_object.ts @@ -12,7 +12,7 @@ export const NumberFromString = new rt.Type( 'NumberFromString', rt.number.is, (u, c) => - either.chain(rt.string.validate(u, c), s => { + either.chain(rt.string.validate(u, c), (s) => { const n = +s; return isNaN(n) ? rt.failure(u, c, 'cannot parse to a number') : rt.success(n); }), diff --git a/x-pack/plugins/case/common/constants.ts b/x-pack/plugins/case/common/constants.ts index 855a5c3d63507..819d4110e168d 100644 --- a/x-pack/plugins/case/common/constants.ts +++ b/x-pack/plugins/case/common/constants.ts @@ -25,7 +25,7 @@ export const CASE_USER_ACTIONS_URL = `${CASE_DETAILS_URL}/user_actions`; * Action routes */ -export const ACTION_URL = '/api/action'; -export const ACTION_TYPES_URL = '/api/action/types'; +export const ACTION_URL = '/api/actions'; +export const ACTION_TYPES_URL = '/api/actions/list_action_types'; export const SUPPORTED_CONNECTORS = ['.servicenow', '.jira']; diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 670e6ec797a9f..9cf045da3e700 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -21,7 +21,7 @@ import { import { CaseConfigureService, CaseService, CaseUserActionService } from './services'; function createConfig$(context: PluginInitializerContext) { - return context.config.create().pipe(map(config => config)); + return context.config.create().pipe(map((config) => config)); } export interface PluginsSetup { @@ -36,9 +36,7 @@ export class CasePlugin { } public async setup(core: CoreSetup, plugins: PluginsSetup) { - const config = await createConfig$(this.initializerContext) - .pipe(first()) - .toPromise(); + const config = await createConfig$(this.initializerContext).pipe(first()).toPromise(); if (!config.enabled) { return; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts index e83dafc68ee69..eade232593c8e 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -31,13 +31,13 @@ export const createMockSavedObjectsRepository = ({ return { saved_objects: objects.map(({ id, type }) => { if (type === CASE_COMMENT_SAVED_OBJECT) { - const result = caseCommentSavedObject.filter(s => s.id === id); + const result = caseCommentSavedObject.filter((s) => s.id === id); if (!result.length) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } return result; } - const result = caseSavedObject.filter(s => s.id === id); + const result = caseSavedObject.filter((s) => s.id === id); if (!result.length) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } @@ -49,11 +49,11 @@ export const createMockSavedObjectsRepository = ({ return { saved_objects: objects.map(({ id, type, attributes }) => { if (type === CASE_COMMENT_SAVED_OBJECT) { - if (!caseCommentSavedObject.find(s => s.id === id)) { + if (!caseCommentSavedObject.find((s) => s.id === id)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } } else if (type === CASE_SAVED_OBJECT) { - if (!caseSavedObject.find(s => s.id === id)) { + if (!caseSavedObject.find((s) => s.id === id)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } } @@ -70,20 +70,20 @@ export const createMockSavedObjectsRepository = ({ }), get: jest.fn((type, id) => { if (type === CASE_COMMENT_SAVED_OBJECT) { - const result = caseCommentSavedObject.filter(s => s.id === id); + const result = caseCommentSavedObject.filter((s) => s.id === id); if (!result.length) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } return result[0]; } - const result = caseSavedObject.filter(s => s.id === id); + const result = caseSavedObject.filter((s) => s.id === id); if (!result.length) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } return result[0]; }), - find: jest.fn(findArgs => { + find: jest.fn((findArgs) => { if (findArgs.hasReference && findArgs.hasReference.id === 'bad-guy') { throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); } @@ -169,7 +169,7 @@ export const createMockSavedObjectsRepository = ({ }), update: jest.fn((type, id, attributes) => { if (type === CASE_COMMENT_SAVED_OBJECT) { - if (!caseCommentSavedObject.find(s => s.id === id)) { + if (!caseCommentSavedObject.find((s) => s.id === id)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } caseCommentSavedObject = [ @@ -183,7 +183,7 @@ export const createMockSavedObjectsRepository = ({ }, ]; } else if (type === CASE_SAVED_OBJECT) { - if (!caseSavedObject.find(s => s.id === id)) { + if (!caseSavedObject.find((s) => s.id === id)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } } @@ -207,14 +207,14 @@ export const createMockSavedObjectsRepository = ({ }; }), delete: jest.fn((type: string, id: string) => { - let result = caseSavedObject.filter(s => s.id === id); + let result = caseSavedObject.filter((s) => s.id === id); if (type === CASE_COMMENT_SAVED_OBJECT) { - result = caseCommentSavedObject.filter(s => s.id === id); + result = caseCommentSavedObject.filter((s) => s.id === id); } if (type === CASE_CONFIGURE_SAVED_OBJECT) { - result = caseConfigureSavedObject.filter(s => s.id === id); + result = caseConfigureSavedObject.filter((s) => s.id === id); } if (type === CASE_COMMENT_SAVED_OBJECT && id === 'bad-guy') { diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts index e9bcb9690ebd8..e06b3a33dfc72 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts @@ -32,7 +32,7 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic caseId: request.params.case_id, }); await Promise.all( - comments.saved_objects.map(comment => + comments.saved_objects.map((comment) => caseService.deleteComment({ client, commentId: comment.id, @@ -42,7 +42,7 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic await userActionService.postUserActions({ client, - actions: comments.saved_objects.map(comment => + actions: comments.saved_objects.map((comment) => buildCommentUserActionItem({ action: 'delete', actionAt: deleteDate, diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts index 72ef400415d0f..df08af025df03 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts @@ -39,7 +39,7 @@ export function initDeleteCommentApi({ caseService, router, userActionService }: throw Boom.notFound(`This comment ${request.params.comment_id} does not exist anymore.`); } - const caseRef = myComment.references.find(c => c.type === CASE_SAVED_OBJECT); + const caseRef = myComment.references.find((c) => c.type === CASE_SAVED_OBJECT); if (caseRef == null || (caseRef != null && caseRef.id !== request.params.case_id)) { throw Boom.notFound( `This comment ${request.params.comment_id} does not exist in ${request.params.case_id}).` diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts index b5a7d6367ea4b..24a03b217ab7c 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts @@ -41,7 +41,7 @@ describe('GET comment', () => { const response = await routeHandler(theContext, request, kibanaResponseFactory); expect(response.status).toEqual(200); - const myPayload = mockCaseComments.find(s => s.id === 'mock-comment-1'); + const myPayload = mockCaseComments.find((s) => s.id === 'mock-comment-1'); expect(myPayload).not.toBeUndefined(); if (myPayload != null) { expect(response.payload).toEqual(flattenCommentSavedObject(myPayload)); diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts index 90661a7d3897d..1aca27bbf1853 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts @@ -57,7 +57,7 @@ export function initPatchCommentApi({ throw Boom.notFound(`This comment ${query.id} does not exist anymore.`); } - const caseRef = myComment.references.find(c => c.type === CASE_SAVED_OBJECT); + const caseRef = myComment.references.find((c) => c.type === CASE_SAVED_OBJECT); if (caseRef == null || (caseRef != null && caseRef.id !== caseId)) { throw Boom.notFound(`This comment ${query.id} does not exist in ${caseId}).`); } diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts index 09692ff73b94b..d7a01ef069867 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts @@ -39,7 +39,7 @@ describe('GET connectors', () => { const res = await routeHandler(context, req, kibanaResponseFactory); expect(res.status).toEqual(200); expect(res.payload).toEqual( - getActions().filter(action => action.actionTypeId === '.servicenow') + getActions().filter((action) => action.actionTypeId === '.servicenow') ); }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts index 43167d56de015..d86e1777e920d 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts @@ -31,7 +31,7 @@ export function initCaseConfigureGetActionConnector({ caseService, router }: Rou throw Boom.notFound('Action client have not been found'); } - const results = (await actionsClient.getAll()).filter(action => + const results = (await actionsClient.getAll()).filter((action) => SUPPORTED_CONNECTORS.includes(action.actionTypeId) ); return response.ok({ body: results }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts index 5c1693e728c37..a49a6c9ec5b76 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts @@ -38,7 +38,7 @@ export function initPostCaseConfigure({ caseConfigureService, caseService, route if (myCaseConfigure.saved_objects.length > 0) { await Promise.all( - myCaseConfigure.saved_objects.map(cc => + myCaseConfigure.saved_objects.map((cc) => caseConfigureService.delete({ client, caseConfigureId: cc.id }) ) ); diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts index 20591637a6c23..9f57663c85f6f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts @@ -25,7 +25,7 @@ export function initDeleteCasesApi({ caseService, router, userActionService }: R try { const client = context.core.savedObjects.client; await Promise.all( - request.query.ids.map(id => + request.query.ids.map((id) => caseService.deleteCase({ client, caseId: id, @@ -33,7 +33,7 @@ export function initDeleteCasesApi({ caseService, router, userActionService }: R ) ); const comments = await Promise.all( - request.query.ids.map(id => + request.query.ids.map((id) => caseService.getAllCaseComments({ client, caseId: id, @@ -41,9 +41,9 @@ export function initDeleteCasesApi({ caseService, router, userActionService }: R ) ); - if (comments.some(c => c.saved_objects.length > 0)) { + if (comments.some((c) => c.saved_objects.length > 0)) { await Promise.all( - comments.map(c => + comments.map((c) => Promise.all( c.saved_objects.map(({ id }) => caseService.deleteComment({ @@ -60,7 +60,7 @@ export function initDeleteCasesApi({ caseService, router, userActionService }: R await userActionService.postUserActions({ client, - actions: request.query.ids.map(id => + actions: request.query.ids.map((id) => buildCaseUserActionItem({ action: 'create', actionAt: deleteDate, diff --git a/x-pack/plugins/case/server/routes/api/cases/find_cases.ts b/x-pack/plugins/case/server/routes/api/cases/find_cases.ts index cbe26ebe2f642..db57315388c5e 100644 --- a/x-pack/plugins/case/server/routes/api/cases/find_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/find_cases.ts @@ -19,7 +19,7 @@ import { CASES_URL } from '../../../../common/constants'; import { getConnectorId } from './helpers'; const combineFilters = (filters: string[], operator: 'OR' | 'AND'): string => - filters?.filter(i => i !== '').join(` ${operator} `); + filters?.filter((i) => i !== '').join(` ${operator} `); const getStatusFilter = (status: 'open' | 'closed', appendFilter?: string) => `${CASE_SAVED_OBJECT}.attributes.status: ${status}${ @@ -35,7 +35,7 @@ const buildFilter = ( ? Array.isArray(filters) ? // Be aware of the surrounding parenthesis (as string inside literal) around filters. `(${filters - .map(filter => `${CASE_SAVED_OBJECT}.attributes.${field}: ${filter}`) + .map((filter) => `${CASE_SAVED_OBJECT}.attributes.${field}: ${filter}`) ?.join(` ${operator} `)})` : `${CASE_SAVED_OBJECT}.attributes.${field}: ${filters}` : ''; @@ -102,7 +102,7 @@ export function initFindCasesApi({ caseService, caseConfigureService, router }: caseConfigureService.find({ client }), ]); const totalCommentsFindByCases = await Promise.all( - cases.saved_objects.map(c => + cases.saved_objects.map((c) => caseService.getAllCaseComments({ client, caseId: c.id, @@ -119,8 +119,8 @@ export function initFindCasesApi({ caseService, caseConfigureService, router }: (acc, itemFind) => { if (itemFind.saved_objects.length > 0) { const caseId = - itemFind.saved_objects[0].references.find(r => r.type === CASE_SAVED_OBJECT)?.id ?? - null; + itemFind.saved_objects[0].references.find((r) => r.type === CASE_SAVED_OBJECT) + ?.id ?? null; if (caseId != null) { return [...acc, { caseId, totalComments: itemFind.total }]; } diff --git a/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts index 6c0b5bdff418d..ed291c2cbf726 100644 --- a/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts @@ -45,7 +45,7 @@ describe('GET case', () => { ); const response = await routeHandler(theContext, request, kibanaResponseFactory); - const savedObject = (mockCases.find(s => s.id === 'mock-id-1') as unknown) as SavedObject< + const savedObject = (mockCases.find((s) => s.id === 'mock-id-1') as unknown) as SavedObject< CaseAttributes >; expect(response.status).toEqual(200); diff --git a/x-pack/plugins/case/server/routes/api/cases/helpers.ts b/x-pack/plugins/case/server/routes/api/cases/helpers.ts index b02bc0b4e10a2..78b108b00d4a7 100644 --- a/x-pack/plugins/case/server/routes/api/cases/helpers.ts +++ b/x-pack/plugins/case/server/routes/api/cases/helpers.ts @@ -24,12 +24,12 @@ export const compareArrays = ({ addedItems: [], deletedItems: [], }; - originalValue.forEach(origVal => { + originalValue.forEach((origVal) => { if (!updatedValue.includes(origVal)) { result.deletedItems = [...result.deletedItems, origVal]; } }); - updatedValue.forEach(updatedVal => { + updatedValue.forEach((updatedVal) => { if (!originalValue.includes(updatedVal)) { result.addedItems = [...result.addedItems, updatedVal]; } diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts index 6d2a5f943cea9..0c722cf56ada3 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts @@ -46,15 +46,15 @@ export function initPatchCasesApi({ const [myCases, myCaseConfigure] = await Promise.all([ caseService.getCases({ client, - caseIds: query.cases.map(q => q.id), + caseIds: query.cases.map((q) => q.id), }), caseConfigureService.find({ client }), ]); const caseConfigureConnectorId = getConnectorId(myCaseConfigure); let nonExistingCases: CasePatchRequest[] = []; - const conflictedCases = query.cases.filter(q => { - const myCase = myCases.saved_objects.find(c => c.id === q.id); + const conflictedCases = query.cases.filter((q) => { + const myCase = myCases.saved_objects.find((c) => c.id === q.id); if (myCase && myCase.error) { nonExistingCases = [...nonExistingCases, q]; @@ -65,24 +65,24 @@ export function initPatchCasesApi({ if (nonExistingCases.length > 0) { throw Boom.notFound( `These cases ${nonExistingCases - .map(c => c.id) + .map((c) => c.id) .join(', ')} do not exist. Please check you have the correct ids.` ); } if (conflictedCases.length > 0) { throw Boom.conflict( `These cases ${conflictedCases - .map(c => c.id) + .map((c) => c.id) .join(', ')} has been updated. Please refresh before saving additional updates.` ); } - const updateCases: CasePatchRequest[] = query.cases.map(thisCase => { - const currentCase = myCases.saved_objects.find(c => c.id === thisCase.id); + const updateCases: CasePatchRequest[] = query.cases.map((thisCase) => { + const currentCase = myCases.saved_objects.find((c) => c.id === thisCase.id); return currentCase != null ? getCaseToUpdate(currentCase.attributes, thisCase) : { id: thisCase.id, version: thisCase.version }; }); - const updateFilterCases = updateCases.filter(updateCase => { + const updateFilterCases = updateCases.filter((updateCase) => { const { id, version, ...updateCaseAttributes } = updateCase; return Object.keys(updateCaseAttributes).length > 0; }); @@ -91,7 +91,7 @@ export function initPatchCasesApi({ const updatedDt = new Date().toISOString(); const updatedCases = await caseService.patchCases({ client, - cases: updateFilterCases.map(thisCase => { + cases: updateFilterCases.map((thisCase) => { const { id: caseId, version, ...updateCaseAttributes } = thisCase; let closedInfo = {}; if (updateCaseAttributes.status && updateCaseAttributes.status === 'closed') { @@ -119,11 +119,11 @@ export function initPatchCasesApi({ }); const returnUpdatedCase = myCases.saved_objects - .filter(myCase => - updatedCases.saved_objects.some(updatedCase => updatedCase.id === myCase.id) + .filter((myCase) => + updatedCases.saved_objects.some((updatedCase) => updatedCase.id === myCase.id) ) - .map(myCase => { - const updatedCase = updatedCases.saved_objects.find(c => c.id === myCase.id); + .map((myCase) => { + const updatedCase = updatedCases.saved_objects.find((c) => c.id === myCase.id); return flattenCaseSavedObject({ savedObject: { ...myCase, diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.ts b/x-pack/plugins/case/server/routes/api/cases/push_case.ts index 871e78495c5dd..3379bbd318d5b 100644 --- a/x-pack/plugins/case/server/routes/api/cases/push_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/push_case.ts @@ -100,7 +100,7 @@ export function initPushCaseUserActionApi({ connector_id: myCase.attributes.connector_id ?? caseConfigureConnectorId, }; - if (!connectors.some(connector => connector.id === updateConnectorId.connector_id)) { + if (!connectors.some((connector) => connector.id === updateConnectorId.connector_id)) { throw Boom.notFound('Connector not found or set to none'); } @@ -127,8 +127,8 @@ export function initPushCaseUserActionApi({ caseService.patchComments({ client, comments: comments.saved_objects - .filter(comment => comment.attributes.pushed_at == null) - .map(comment => ({ + .filter((comment) => comment.attributes.pushed_at == null) + .map((comment) => ({ commentId: comment.id, updatedAttributes: { pushed_at: pushedDate, @@ -174,9 +174,9 @@ export function initPushCaseUserActionApi({ attributes: { ...myCase.attributes, ...updatedCase?.attributes }, references: myCase.references, }, - comments: comments.saved_objects.map(origComment => { + comments: comments.saved_objects.map((origComment) => { const updatedComment = updatedComments.saved_objects.find( - c => c.id === origComment.id + (c) => c.id === origComment.id ); return { ...origComment, diff --git a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts index c90979f60d23f..eb51582900252 100644 --- a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts @@ -31,11 +31,12 @@ export function initGetAllUserActionsApi({ userActionService, router }: RouteDep }); return response.ok({ body: CaseUserActionsResponseRt.encode( - userActions.saved_objects.map(ua => ({ + userActions.saved_objects.map((ua) => ({ ...ua.attributes, action_id: ua.id, - case_id: ua.references.find(r => r.type === CASE_SAVED_OBJECT)?.id ?? '', - comment_id: ua.references.find(r => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null, + case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '', + comment_id: + ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null, })) ), }); diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index b65205734d569..fb199442f9425 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -111,7 +111,7 @@ export const flattenCaseSavedObjects = ( flattenCaseSavedObject({ savedObject, totalComment: - totalCommentByCase.find(tc => tc.caseId === savedObject.id)?.totalComments ?? 0, + totalCommentByCase.find((tc) => tc.caseId === savedObject.id)?.totalComments ?? 0, caseConfigureConnectorId, }), ]; diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts index cdc5fd21a8138..2ca3e4e9ec223 100644 --- a/x-pack/plugins/case/server/services/index.ts +++ b/x-pack/plugins/case/server/services/index.ts @@ -150,7 +150,7 @@ export class CaseService { try { this.log.debug(`Attempting to GET cases ${caseIds.join(', ')}`); return await client.bulkGet( - caseIds.map(caseId => ({ type: CASE_SAVED_OBJECT, id: caseId })) + caseIds.map((caseId) => ({ type: CASE_SAVED_OBJECT, id: caseId })) ); } catch (error) { this.log.debug(`Error on GET cases ${caseIds.join(', ')}: ${error}`); @@ -264,9 +264,9 @@ export class CaseService { }, patchCases: async ({ client, cases }: PatchCasesArgs) => { try { - this.log.debug(`Attempting to UPDATE case ${cases.map(c => c.caseId).join(', ')}`); + this.log.debug(`Attempting to UPDATE case ${cases.map((c) => c.caseId).join(', ')}`); return await client.bulkUpdate( - cases.map(c => ({ + cases.map((c) => ({ type: CASE_SAVED_OBJECT, id: c.caseId, attributes: c.updatedAttributes, @@ -274,7 +274,7 @@ export class CaseService { })) ); } catch (error) { - this.log.debug(`Error on UPDATE case ${cases.map(c => c.caseId).join(', ')}: ${error}`); + this.log.debug(`Error on UPDATE case ${cases.map((c) => c.caseId).join(', ')}: ${error}`); throw error; } }, @@ -297,10 +297,10 @@ export class CaseService { patchComments: async ({ client, comments }: PatchComments) => { try { this.log.debug( - `Attempting to UPDATE comments ${comments.map(c => c.commentId).join(', ')}` + `Attempting to UPDATE comments ${comments.map((c) => c.commentId).join(', ')}` ); return await client.bulkUpdate( - comments.map(c => ({ + comments.map((c) => ({ type: CASE_COMMENT_SAVED_OBJECT, id: c.commentId, attributes: c.updatedAttributes, @@ -309,7 +309,7 @@ export class CaseService { ); } catch (error) { this.log.debug( - `Error on UPDATE comments ${comments.map(c => c.commentId).join(', ')}: ${error}` + `Error on UPDATE comments ${comments.map((c) => c.commentId).join(', ')}: ${error}` ); throw error; } diff --git a/x-pack/plugins/case/server/services/reporters/read_reporters.ts b/x-pack/plugins/case/server/services/reporters/read_reporters.ts index 4af5b41fc4dd4..2d09dbf7b9890 100644 --- a/x-pack/plugins/case/server/services/reporters/read_reporters.ts +++ b/x-pack/plugins/case/server/services/reporters/read_reporters.ts @@ -16,7 +16,7 @@ export const convertToReporters = (caseObjects: Array item.username === caseObj.attributes.created_by.username) + !accum.some((item) => item.username === caseObj.attributes.created_by.username) ) { return [...accum, caseObj.attributes.created_by]; } else { diff --git a/x-pack/plugins/case/server/services/user_actions/helpers.ts b/x-pack/plugins/case/server/services/user_actions/helpers.ts index af50b3b394325..228b42b4c638f 100644 --- a/x-pack/plugins/case/server/services/user_actions/helpers.ts +++ b/x-pack/plugins/case/server/services/user_actions/helpers.ts @@ -138,11 +138,11 @@ export const buildCaseUserActions = ({ updatedCases: Array>; }): UserActionItem[] => updatedCases.reduce((acc, updatedItem) => { - const originalItem = originalCases.find(oItem => oItem.id === updatedItem.id); + const originalItem = originalCases.find((oItem) => oItem.id === updatedItem.id); if (originalItem != null) { let userActions: UserActionItem[] = []; const updatedFields = Object.keys(updatedItem.attributes) as UserActionField; - updatedFields.forEach(field => { + updatedFields.forEach((field) => { if (userActionFieldsAllowed.includes(field)) { const origValue = get(originalItem, ['attributes', field]); const updatedValue = get(updatedItem, ['attributes', field]); diff --git a/x-pack/plugins/case/server/services/user_actions/index.ts b/x-pack/plugins/case/server/services/user_actions/index.ts index 0e9babf9d81af..476a151498c34 100644 --- a/x-pack/plugins/case/server/services/user_actions/index.ts +++ b/x-pack/plugins/case/server/services/user_actions/index.ts @@ -66,7 +66,7 @@ export class CaseUserActionService { try { this.log.debug(`Attempting to POST a new case user action`); return await client.bulkCreate( - actions.map(action => ({ type: CASE_USER_ACTION_SAVED_OBJECT, ...action })) + actions.map((action) => ({ type: CASE_USER_ACTION_SAVED_OBJECT, ...action })) ); } catch (error) { this.log.debug(`Error on POST a new case user action: ${error}`); diff --git a/x-pack/plugins/console_extensions/server/plugin.ts b/x-pack/plugins/console_extensions/server/plugin.ts index 2a08c258f4bbd..8e95d618b30ab 100644 --- a/x-pack/plugins/console_extensions/server/plugin.ts +++ b/x-pack/plugins/console_extensions/server/plugin.ts @@ -32,7 +32,7 @@ export class ConsoleExtensionsServerPlugin implements Plugin addProcessorDefinition(processor)); + processors.forEach((processor) => addProcessorDefinition(processor)); this.log.debug('Added processor definition extensions.'); } } diff --git a/x-pack/plugins/cross_cluster_replication/README.md b/x-pack/plugins/cross_cluster_replication/README.md new file mode 100644 index 0000000000000..8baccf4f7333e --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/README.md @@ -0,0 +1,23 @@ +# Cross-Cluster Replication + +## Quick steps for testing cross-cluster replication + +You can run a local cluster and simulate a remote cluster within a single Kibana directory. + +1. Run `yarn es snapshot --license=trial` and kill the process once the snapshot has been installed. +2. Duplicate the ES installation by running `cp -aR .es/8.0.0 .es/8.0.0-2`. +3. Start your "local" cluster by running `.es/8.0.0/bin/elasticsearch` and starting Kibana. +4. Start your "remote" cluster by running `.es/8.0.0-2/bin/elasticsearch -E cluster.name=europe -E transport.port=9400`. +5. Index a document into your remote cluster by running `curl -X PUT http://elastic:changeme@localhost:9201/my-leader-index --data '{"settings":{"number_of_shards":1,"soft_deletes.enabled":true}}' --header "Content-Type: application/json"`. +Note that these settings are required for testing auto-follow pattern conflicts errors (see below). + +Now you can create follower indices and auto-follow patterns to replicate the `my-leader-index` +index on the remote cluster that's available at `127.0.0.1:9400`. + +### Auto-follow pattern conflict errors + +You can view conflict errors by creating two auto-follow patterns with overlapping patterns (e.g. `my*` and `my-*`) that will both capture the `my-leader-index` index on your remote cluster. Run the curl command to create `my-leader-index2` on your remote cluster, since auto-follow patterns don't replicate existing indices. + +Now, when you open the details flyout of one of the auto-follow patterns you will see a list of recent errors. + +![image](https://user-images.githubusercontent.com/1238659/79623769-e879b800-80d2-11ea-906d-0b2d6637c3a3.png) \ No newline at end of file diff --git a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts index 96884cf4bead8..0574cb9b2dd6d 100644 --- a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts +++ b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts @@ -24,8 +24,7 @@ export const APPS = { }; export const MANAGEMENT_ID = 'cross_cluster_replication'; -export const BASE_PATH = `/management/data/${MANAGEMENT_ID}`; -export const BASE_PATH_REMOTE_CLUSTERS = '/management/data/remote_clusters'; +export const BASE_PATH_REMOTE_CLUSTERS = 'data/remote_clusters'; export const API_BASE_PATH = '/api/cross_cluster_replication'; export const API_REMOTE_CLUSTERS_BASE_PATH = '/api/remote_clusters'; export const API_INDEX_MANAGEMENT_BASE_PATH = '/api/index_management'; diff --git a/x-pack/plugins/cross_cluster_replication/common/services/utils.ts b/x-pack/plugins/cross_cluster_replication/common/services/utils.ts index dda6732254cc3..7171c50e52f88 100644 --- a/x-pack/plugins/cross_cluster_replication/common/services/utils.ts +++ b/x-pack/plugins/cross_cluster_replication/common/services/utils.ts @@ -11,7 +11,7 @@ export const arrify = (val: any): any[] => (Array.isArray(val) ? val : [val]); * @param {number} time Time in millisecond to wait */ export const wait = (time = 1000) => (data: any): Promise => { - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => resolve(data), time); }); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_add.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_add.test.js index db1430d157183..728a9921ea903 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_add.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_add.test.js @@ -195,7 +195,7 @@ describe('Create Auto-follow pattern', () => { }); test('should not allow invalid characters', () => { - const expectInvalidChar = char => { + const expectInvalidChar = (char) => { form.setComboBoxValue('indexPatternInput', `with${char}space`); expect(form.getErrorsMessages()).toContain( `Remove the character ${char} from the index pattern.` diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js index 0a7eaf647b020..0da241c4c1357 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -274,11 +274,7 @@ describe('', () => { test('should have a "settings" section', () => { actions.clickAutoFollowPatternAt(0); - expect( - find('settingsSection') - .find('h3') - .text() - ).toEqual('Settings'); + expect(find('settingsSection').find('h3').text()).toEqual('Settings'); expect(exists('settingsValues')).toBe(true); }); @@ -369,7 +365,7 @@ describe('', () => { expect(exists('autoFollowPatternDetail.errors')).toBe(true); expect(exists('autoFollowPatternDetail.titleErrors')).toBe(true); - expect(find('autoFollowPatternDetail.recentError').map(error => error.text())).toEqual([ + expect(find('autoFollowPatternDetail.recentError').map((error) => error.text())).toEqual([ 'April 16th, 2020 8:00:00 PM: bar', ]); }); diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_add.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_add.test.js index 4c99339e16952..f5305f43e7f8c 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_add.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_add.test.js @@ -120,7 +120,7 @@ describe('Create Follower index', () => { test('should not allow invalid characters', () => { actions.clickSaveForm(); // Make all errors visible - const expectInvalidChar = char => { + const expectInvalidChar = (char) => { form.setInputValue('leaderIndexInput', `with${char}`); expect(form.getErrorsMessages()).toContain( `Remove the characters ${char} from your leader index.` @@ -149,7 +149,7 @@ describe('Create Follower index', () => { test('should not allow invalid characters', () => { actions.clickSaveForm(); // Make all errors visible - const expectInvalidChar = char => { + const expectInvalidChar = (char) => { form.setInputValue('followerIndexInput', `with${char}`); expect(form.getErrorsMessages()).toContain( `Remove the characters ${char} from your name.` @@ -235,7 +235,7 @@ describe('Create Follower index', () => { }; test('should have a toggle to activate advanced settings', () => { - const expectDoesNotExist = testSubject => { + const expectDoesNotExist = (testSubject) => { try { expect(exists(testSubject)).toBe(false); } catch { @@ -243,7 +243,7 @@ describe('Create Follower index', () => { } }; - const expectDoesExist = testSubject => { + const expectDoesExist = (testSubject) => { try { expect(exists(testSubject)).toBe(true); } catch { diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index ad9f2db2ce91c..e96beda7243d6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -190,7 +190,7 @@ describe('', () => { expect(contextMenu.length).toBe(1); const contextMenuButtons = contextMenu.find('button'); - const buttonsLabel = contextMenuButtons.map(btn => btn.text()); + const buttonsLabel = contextMenuButtons.map((btn) => btn.text()); expect(buttonsLabel).toEqual([ 'Pause replication', @@ -206,7 +206,7 @@ describe('', () => { const contextMenu = find('contextMenu'); const contextMenuButtons = contextMenu.find('button'); - const buttonsLabel = contextMenuButtons.map(btn => btn.text()); + const buttonsLabel = contextMenuButtons.map((btn) => btn.text()); expect(buttonsLabel).toEqual([ 'Resume replication', 'Edit follower index', @@ -250,7 +250,7 @@ describe('', () => { const buttonLabels = component .find('.euiContextMenuPanel') .find('.euiContextMenuItem') - .map(button => button.text()); + .map((button) => button.text()); expect(buttonLabels).toEqual([ 'Pause replication', @@ -266,7 +266,7 @@ describe('', () => { const buttonLabels = component .find('.euiContextMenuPanel') .find('.euiContextMenuItem') - .map(button => button.text()); + .map((button) => button.text()); expect(buttonLabels).toEqual([ 'Resume replication', @@ -326,11 +326,7 @@ describe('', () => { test('should have a "settings" section', () => { actions.clickFollowerIndexAt(0); - expect( - find('followerIndexDetail.settingsSection') - .find('h3') - .text() - ).toEqual('Settings'); + expect(find('followerIndexDetail.settingsSection').find('h3').text()).toEqual('Settings'); expect(exists('followerIndexDetail.settingsValues')).toBe(true); }); diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js index 1cb4e7c7725df..a4edee7268a23 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js @@ -12,13 +12,17 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: router => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), }, }; const initTestBed = registerTestBed(AutoFollowPatternAdd, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); // User actions diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js index 9cad61893c409..ea372d534d817 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js @@ -14,7 +14,11 @@ import { AUTO_FOLLOW_PATTERN_EDIT_NAME } from './constants'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: router => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), // The auto-follow pattern id to fetch is read from the router ":id" param // so we first set it in our initial entries initialEntries: [`/${AUTO_FOLLOW_PATTERN_EDIT_NAME}`], @@ -25,7 +29,7 @@ const testBedConfig = { const initTestBed = registerTestBed(AutoFollowPatternEdit, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); // User actions diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js index 2c2ab642e83c8..550e178a1c733 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js @@ -12,13 +12,24 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: router => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + history: { + ...router.history, + parentHistory: { + createHref: () => '', + push: () => {}, + }, + }, + getUrlForApp: () => '', + }), }, }; const initTestBed = registerTestBed(AutoFollowPatternList, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); const EUI_TABLE = 'autoFollowPatternListTable'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js index 856b09f3f3cba..31f3844f57fb5 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js @@ -12,13 +12,17 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: router => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), }, }; const initTestBed = registerTestBed(FollowerIndexAdd, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); // User actions diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js index 893d01f151bc2..8fc2811e58cdb 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js @@ -14,7 +14,11 @@ import { FOLLOWER_INDEX_EDIT_NAME } from './constants'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: router => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), // The follower index id to fetch is read from the router ":id" param // so we first set it in our initial entries initialEntries: [`/${FOLLOWER_INDEX_EDIT_NAME}`], @@ -25,7 +29,7 @@ const testBedConfig = { const initTestBed = registerTestBed(FollowerIndexEdit, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); // User actions diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js index 5e9f7d1263cf7..65be10b9d272e 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js @@ -12,13 +12,23 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: router => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + history: { + ...router.history, + parentHistory: { + createHref: () => '', + push: () => {}, + }, + }, + getUrlForApp: () => '', + }), }, }; const initTestBed = registerTestBed(FollowerIndicesList, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); const EUI_TABLE = 'followerIndexListTable'; @@ -39,10 +49,7 @@ export const setup = props => { const clickContextMenuButtonAt = (index = 0) => { const contextMenu = testBed.find('contextMenu'); - contextMenu - .find('button') - .at(index) - .simulate('click'); + contextMenu.find('button').at(index).simulate('click'); }; const openTableRowContextMenuAt = (index = 0) => { diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js index 56dfa765bfa4f..e51c444a6b370 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js @@ -5,7 +5,6 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { BASE_PATH } from '../../../../common/constants'; import { CrossClusterReplicationHome } from '../../../app/sections/home/home'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; @@ -13,9 +12,9 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - initialEntries: [`${BASE_PATH}/follower_indices`], - componentRoutePath: `${BASE_PATH}/:section`, - onRouter: router => (routing.reactRouter = router), + initialEntries: [`/follower_indices`], + componentRoutePath: `/:section`, + onRouter: (router) => (routing.reactRouter = router), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/http_requests.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/http_requests.js index e2bd54a92a1f1..e37ec0d40ee5b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/http_requests.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/http_requests.js @@ -7,14 +7,14 @@ import sinon from 'sinon'; // Register helpers to mock HTTP Requests -const registerHttpRequestMockHelpers = server => { +const registerHttpRequestMockHelpers = (server) => { const mockResponse = (defaultResponse, response) => [ 200, { 'Content-Type': 'application/json' }, JSON.stringify({ ...defaultResponse, ...response }), ]; - const setLoadFollowerIndicesResponse = response => { + const setLoadFollowerIndicesResponse = (response) => { const defaultResponse = { indices: [] }; server.respondWith( @@ -24,7 +24,7 @@ const registerHttpRequestMockHelpers = server => { ); }; - const setLoadAutoFollowPatternsResponse = response => { + const setLoadAutoFollowPatternsResponse = (response) => { const defaultResponse = { patterns: [] }; server.respondWith( @@ -34,7 +34,7 @@ const registerHttpRequestMockHelpers = server => { ); }; - const setDeleteAutoFollowPatternResponse = response => { + const setDeleteAutoFollowPatternResponse = (response) => { const defaultResponse = { errors: [], itemsDeleted: [] }; server.respondWith( @@ -44,7 +44,7 @@ const registerHttpRequestMockHelpers = server => { ); }; - const setAutoFollowStatsResponse = response => { + const setAutoFollowStatsResponse = (response) => { const defaultResponse = { numberOfFailedFollowIndices: 0, numberOfFailedRemoteClusterStateRequests: 0, @@ -82,7 +82,7 @@ const registerHttpRequestMockHelpers = server => { } }; - const setGetAutoFollowPatternResponse = response => { + const setGetAutoFollowPatternResponse = (response) => { const defaultResponse = {}; server.respondWith( @@ -100,7 +100,7 @@ const registerHttpRequestMockHelpers = server => { ]); }; - const setGetFollowerIndexResponse = response => { + const setGetFollowerIndexResponse = (response) => { const defaultResponse = {}; server.respondWith( diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts index 016e259343285..5460b482cd579 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts @@ -8,6 +8,6 @@ jest.mock('../../../app/services/track_ui_metric', () => ({ ...jest.requireActual('../../../app/services/track_ui_metric'), trackUiMetric: jest.fn(), trackUserRequest: (request: Promise) => { - return request.then(response => response); + return request.then((response) => response); }, })); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/app.tsx b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx index ec349ccd6f2c7..288da20c353d2 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/app.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx @@ -5,8 +5,8 @@ */ import React, { Component, Fragment } from 'react'; -import { Route, Switch, Redirect, withRouter, RouteComponentProps } from 'react-router-dom'; -import { History } from 'history'; +import { Route, Switch, Router, Redirect } from 'react-router-dom'; +import { ScopedHistory, ApplicationStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -20,7 +20,6 @@ import { EuiTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../common/constants'; import { getFatalErrors } from './services/notifications'; import { SectionError } from './components'; import { routing } from './services/routing'; @@ -37,8 +36,8 @@ import { } from './sections'; interface AppProps { - history: History; - location: any; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; } interface AppState { @@ -48,7 +47,7 @@ interface AppState { missingClusterPrivileges: any[]; } -class AppComponent extends Component { +class AppComponent extends Component { constructor(props: any) { super(props); this.registerRouter(); @@ -99,12 +98,13 @@ class AppComponent extends Component { } registerRouter() { - const { history, location } = this.props; + const { history, getUrlForApp } = this.props; routing.reactRouter = { history, route: { - location, + location: history.location, }, + getUrlForApp, }; } @@ -189,30 +189,18 @@ class AppComponent extends Component { } return ( -
+ - - - - - - + + + + + + -
+ ); } } -export const App = withRouter(AppComponent); +export const App = AppComponent; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx index 5474708f313c8..1d8f8bacc8c84 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx @@ -99,7 +99,7 @@ const AutoFollowPatternActionMenuUI: FunctionComponent = ({ }), icon: , onClick: () => { - window.location.hash = routing.getAutoFollowPatternPath(patterns[0].name); + routing.navigate(routing.getAutoFollowPatternPath(patterns[0].name)); }, } : null, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js index f9c03165dcf97..4ed4bc45c7b19 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js @@ -19,13 +19,13 @@ class AutoFollowPatternDeleteProviderUi extends PureComponent { ids: null, }; - onMouseOverModal = event => { + onMouseOverModal = (event) => { // This component can sometimes be used inside of an EuiToolTip, in which case mousing over // the modal can trigger the tooltip. Stopping propagation prevents this. event.stopPropagation(); }; - deleteAutoFollowPattern = id => { + deleteAutoFollowPattern = (id) => { this.setState({ isModalOpen: true, ids: arrify(id) }); }; @@ -91,7 +91,7 @@ class AutoFollowPatternDeleteProviderUi extends PureComponent { />

    - {ids.map(id => ( + {ids.map((id) => (
  • {id}
  • ))}
@@ -115,8 +115,8 @@ class AutoFollowPatternDeleteProviderUi extends PureComponent { } } -const mapDispatchToProps = dispatch => ({ - deleteAutoFollowPattern: id => dispatch(deleteAutoFollowPattern(id)), +const mapDispatchToProps = (dispatch) => ({ + deleteAutoFollowPattern: (id) => dispatch(deleteAutoFollowPattern(id)), }); export const AutoFollowPatternDeleteProvider = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js index c817637ae1854..7874f6ac649eb 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js @@ -107,7 +107,7 @@ export class AutoFollowPatternForm extends PureComponent { })); }; - onFieldsChange = fields => { + onFieldsChange = (fields) => { this.setState(({ autoFollowPattern }) => ({ autoFollowPattern: { ...autoFollowPattern, @@ -119,14 +119,14 @@ export class AutoFollowPatternForm extends PureComponent { this.onFieldsErrorChange(errors); }; - onFieldsErrorChange = errors => + onFieldsErrorChange = (errors) => this.setState(({ fieldsErrors }) => updateFormErrors(errors, fieldsErrors)); - onClusterChange = remoteCluster => { + onClusterChange = (remoteCluster) => { this.onFieldsChange({ remoteCluster }); }; - onCreateLeaderIndexPattern = indexPattern => { + onCreateLeaderIndexPattern = (indexPattern) => { const error = validateLeaderIndexPattern(indexPattern); if (error) { @@ -152,13 +152,13 @@ export class AutoFollowPatternForm extends PureComponent { this.onFieldsChange({ leaderIndexPatterns: newLeaderIndexPatterns }); }; - onLeaderIndexPatternChange = indexPatterns => { + onLeaderIndexPatternChange = (indexPatterns) => { this.onFieldsChange({ leaderIndexPatterns: indexPatterns.map(({ label }) => label), }); }; - onLeaderIndexPatternInputChange = leaderIndexPattern => { + onLeaderIndexPatternInputChange = (leaderIndexPattern) => { const isEmpty = !leaderIndexPattern || !leaderIndexPattern.trim(); const { autoFollowPattern: { leaderIndexPatterns }, @@ -205,7 +205,7 @@ export class AutoFollowPatternForm extends PureComponent { isFormValid() { return Object.values(this.state.fieldsErrors).every( - error => error === undefined || error === null + (error) => error === undefined || error === null ); } @@ -306,7 +306,7 @@ export class AutoFollowPatternForm extends PureComponent { this.onFieldsChange({ name: e.target.value })} + onChange={(e) => this.onFieldsChange({ name: e.target.value })} fullWidth disabled={!isNew} data-test-subj="nameInput" @@ -329,7 +329,7 @@ export class AutoFollowPatternForm extends PureComponent { defaultMessage="Auto-follow patterns capture indices on remote clusters." /> ), - remoteClusterNotConnectedNotEditable: name => ({ + remoteClusterNotConnectedNotEditable: (name) => ({ title: ( ), }), - remoteClusterDoesNotExist: name => ( + remoteClusterDoesNotExist: (name) => ( @@ -547,7 +551,9 @@ export class AutoFollowPatternForm extends PureComponent { this.onFieldsChange({ followIndexPatternSuffix: e.target.value })} + onChange={(e) => + this.onFieldsChange({ followIndexPatternSuffix: e.target.value }) + } fullWidth data-test-subj="suffixInput" /> diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/advanced_settings_fields.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/advanced_settings_fields.js index 81c7ca396c511..d423dd0b1f58d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/advanced_settings_fields.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/advanced_settings_fields.js @@ -309,7 +309,7 @@ export const emptyAdvancedSettings = advancedSettingsFields.reduce((obj, advance }, {}); export function areAdvancedSettingsEdited(followerIndex) { - return advancedSettingsFields.some(advancedSetting => { + return advancedSettingsFields.some((advancedSetting) => { const { field } = advancedSetting; return followerIndex[field] !== emptyAdvancedSettings[field]; }); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js index d824efd56cef1..28673c55fd031 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js @@ -71,7 +71,7 @@ const getEmptyFollowerIndex = (remoteClusterName = '') => ({ /** * State transitions: fields update */ -export const updateFields = fields => ({ followerIndex }) => ({ +export const updateFields = (fields) => ({ followerIndex }) => ({ followerIndex: { ...followerIndex, ...fields, @@ -81,7 +81,7 @@ export const updateFields = fields => ({ followerIndex }) => ({ /** * State transitions: errors update */ -export const updateFormErrors = errors => ({ fieldsErrors }) => ({ +export const updateFormErrors = (errors) => ({ fieldsErrors }) => ({ fieldsErrors: { ...fieldsErrors, ...errors, @@ -147,7 +147,7 @@ export class FollowerIndexForm extends PureComponent { })); }; - onFieldsChange = fields => { + onFieldsChange = (fields) => { this.setState(updateFields(fields)); const newFields = { @@ -162,7 +162,7 @@ export class FollowerIndexForm extends PureComponent { } }; - getFieldsErrors = newFields => { + getFieldsErrors = (newFields) => { return Object.keys(newFields).reduce((errors, field) => { const validator = fieldToValidatorMap[field]; const value = newFields[field]; @@ -201,10 +201,10 @@ export class FollowerIndexForm extends PureComponent { this.validateIndexName(name); }; - validateIndexName = async name => { + validateIndexName = async (name) => { try { const indices = await loadIndices(); - const doesExist = indices.some(index => index.name === name); + const doesExist = indices.some((index) => index.name === name); if (doesExist) { const error = { message: ( @@ -252,7 +252,7 @@ export class FollowerIndexForm extends PureComponent { } }; - onClusterChange = remoteCluster => { + onClusterChange = (remoteCluster) => { this.onFieldsChange({ remoteCluster }); }; @@ -260,7 +260,7 @@ export class FollowerIndexForm extends PureComponent { return this.state.followerIndex; }; - toggleAdvancedSettings = event => { + toggleAdvancedSettings = (event) => { // If the user edits the advanced settings but then hides them, we need to make sure the // edited values don't get sent to the API when the user saves, but we *do* want to restore // these values to the form when the user re-opens the advanced settings. @@ -298,7 +298,7 @@ export class FollowerIndexForm extends PureComponent { isFormValid() { return Object.values(this.state.fieldsErrors).every( - error => error === undefined || error === null + (error) => error === undefined || error === null ); } @@ -440,7 +440,7 @@ export class FollowerIndexForm extends PureComponent { defaultMessage="Replication requires a leader index on a remote cluster." /> ), - remoteClusterNotConnectedNotEditable: name => ({ + remoteClusterNotConnectedNotEditable: (name) => ({ title: ( ), }), - remoteClusterDoesNotExist: name => ( + remoteClusterDoesNotExist: (name) => ( - {advancedSettingsFields.map(advancedSetting => { + {advancedSettingsFields.map((advancedSetting) => { const { field, testSubject, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js index 5965bfd8cc603..9c1e8255d069c 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js @@ -25,18 +25,18 @@ class FollowerIndexPauseProviderUi extends PureComponent { indices: [], }; - onMouseOverModal = event => { + onMouseOverModal = (event) => { // This component can sometimes be used inside of an EuiToolTip, in which case mousing over // the modal can trigger the tooltip. Stopping propagation prevents this. event.stopPropagation(); }; - pauseFollowerIndex = index => { + pauseFollowerIndex = (index) => { this.setState({ isModalOpen: true, indices: arrify(index) }); }; onConfirm = () => { - this.props.pauseFollowerIndex(this.state.indices.map(index => index.name)); + this.props.pauseFollowerIndex(this.state.indices.map((index) => index.name)); this.setState({ isModalOpen: false, indices: [] }); this.props.onConfirm && this.props.onConfirm(); }; @@ -65,7 +65,7 @@ class FollowerIndexPauseProviderUi extends PureComponent { values: { count: indices.length }, } ); - const hasCustomSettings = indices.some(index => !areAllSettingsDefault(index)); + const hasCustomSettings = indices.some((index) => !areAllSettingsDefault(index)); return ( @@ -118,7 +118,7 @@ class FollowerIndexPauseProviderUi extends PureComponent {

    - {indices.map(index => ( + {indices.map((index) => (
  • {index.name}
  • ))}
@@ -142,8 +142,8 @@ class FollowerIndexPauseProviderUi extends PureComponent { } } -const mapDispatchToProps = dispatch => ({ - pauseFollowerIndex: id => dispatch(pauseFollowerIndex(id)), +const mapDispatchToProps = (dispatch) => ({ + pauseFollowerIndex: (id) => dispatch(pauseFollowerIndex(id)), }); export const FollowerIndexPauseProvider = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js index ed9bc015c87e2..101e3df6bf710 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js @@ -10,7 +10,7 @@ import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiLink, EuiOverlayMask } from '@elastic/eui'; - +import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; import { routing } from '../services/routing'; import { resumeFollowerIndex } from '../store/actions'; import { arrify } from '../../../common/services/utils'; @@ -25,13 +25,13 @@ class FollowerIndexResumeProviderUi extends PureComponent { ids: null, }; - onMouseOverModal = event => { + onMouseOverModal = (event) => { // This component can sometimes be used inside of an EuiToolTip, in which case mousing over // the modal can trigger the tooltip. Stopping propagation prevents this. event.stopPropagation(); }; - resumeFollowerIndex = id => { + resumeFollowerIndex = (id) => { this.setState({ isModalOpen: true, ids: arrify(id) }); }; @@ -97,7 +97,13 @@ class FollowerIndexResumeProviderUi extends PureComponent { custom advanced settings, {editLink}." values={{ editLink: ( - +
    - {ids.map(id => ( + {ids.map((id) => (
  • {id}
  • ))}
@@ -148,8 +154,8 @@ class FollowerIndexResumeProviderUi extends PureComponent { } } -const mapDispatchToProps = dispatch => ({ - resumeFollowerIndex: id => dispatch(resumeFollowerIndex(id)), +const mapDispatchToProps = (dispatch) => ({ + resumeFollowerIndex: (id) => dispatch(resumeFollowerIndex(id)), }); export const FollowerIndexResumeProvider = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js index f3b267a69b18c..68b6b970ad90b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js @@ -24,13 +24,13 @@ class FollowerIndexUnfollowProviderUi extends PureComponent { ids: null, }; - onMouseOverModal = event => { + onMouseOverModal = (event) => { // This component can sometimes be used inside of an EuiToolTip, in which case mousing over // the modal can trigger the tooltip. Stopping propagation prevents this. event.stopPropagation(); }; - unfollowLeaderIndex = id => { + unfollowLeaderIndex = (id) => { this.setState({ isModalOpen: true, ids: arrify(id) }); }; @@ -110,7 +110,7 @@ class FollowerIndexUnfollowProviderUi extends PureComponent { />

    - {ids.map(id => ( + {ids.map((id) => (
  • {id}
  • ))}
@@ -134,8 +134,8 @@ class FollowerIndexUnfollowProviderUi extends PureComponent { } } -const mapDispatchToProps = dispatch => ({ - unfollowLeaderIndex: id => dispatch(unfollowLeaderIndex(id)), +const mapDispatchToProps = (dispatch) => ({ + unfollowLeaderIndex: (id) => dispatch(unfollowLeaderIndex(id)), }); export const FollowerIndexUnfollowProvider = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js b/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js index afb0ae8a9c0c4..6c534db7fc7c5 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js @@ -19,7 +19,7 @@ import { /** * State transitions: fields update */ -export const updateFields = newValues => ({ fields }) => ({ +export const updateFields = (newValues) => ({ fields }) => ({ fields: { ...fields, ...newValues, @@ -44,7 +44,7 @@ export class FormEntryRow extends PureComponent { testSubj: PropTypes.string, }; - onFieldChange = value => { + onFieldChange = (value) => { const { field, onValueUpdate, type } = this.props; const isNumber = type === 'number'; @@ -57,7 +57,7 @@ export class FormEntryRow extends PureComponent { onValueUpdate({ [field]: valueParsed }); }; - renderField = isInvalid => { + renderField = (isInvalid) => { const { value, type, disabled, isLoading, testSubj } = this.props; switch (type) { case 'number': @@ -65,7 +65,7 @@ export class FormEntryRow extends PureComponent { this.onFieldChange(e.target.value)} + onChange={(e) => this.onFieldChange(e.target.value)} disabled={disabled === true} isLoading={isLoading} fullWidth @@ -77,7 +77,7 @@ export class FormEntryRow extends PureComponent { this.onFieldChange(e.target.value)} + onChange={(e) => this.onFieldChange(e.target.value)} disabled={disabled === true} isLoading={isLoading} fullWidth diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js b/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js index 00d29ffdca3a6..cc76334afde21 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js @@ -19,7 +19,6 @@ import { } from '@elastic/eui'; import { routing } from '../services/routing'; -import { BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; const errorMessages = { noClusterFound: () => ( @@ -28,7 +27,7 @@ const errorMessages = { defaultMessage="You need at least one remote cluster to create a follower index." /> ), - remoteClusterNotConnectedEditable: name => ({ + remoteClusterNotConnectedEditable: (name) => ({ title: ( c.name === clusterName); + const remoteCluster = remoteClusters.find((c) => c.name === clusterName); return remoteCluster && remoteCluster.isConnected ? { error: null } @@ -76,7 +75,7 @@ export class RemoteClustersFormField extends PureComponent { }; } - onRemoteClusterChange = cluster => { + onRemoteClusterChange = (cluster) => { const { onChange, onError } = this.props; const { error } = this.validateRemoteCluster(cluster); onChange(cluster); @@ -137,7 +136,7 @@ export class RemoteClustersFormField extends PureComponent { fullWidth options={remoteClustersOptions} value={hasClusters ? selected : ''} - onChange={e => { + onChange={(e) => { this.onRemoteClusterChange(e.target.value); }} hasNoInitialSelection={!hasClusters} @@ -152,10 +151,9 @@ export class RemoteClustersFormField extends PureComponent { {' '} {/* Break out of EuiFormRow's flexbox layout */} {this.errorMessages.noClusterFound()}

{description}

{ + renderRemoteClusterDoesNotExist = (name) => { const { currentUrl } = this.props; const title = i18n.translate( 'xpack.crossClusterReplication.remoteClustersFormField.remoteClusterNotFoundTitle', @@ -266,10 +262,9 @@ export class RemoteClustersFormField extends PureComponent {

{this.errorMessages.remoteClusterDoesNotExist(name)}

{ const { selected, remoteClusters, isEditable } = this.props; - const remoteCluster = remoteClusters.find(c => c.name === selected); + const remoteCluster = remoteClusters.find((c) => c.name === selected); const isSelectedRemoteClusterConnected = remoteCluster && remoteCluster.isConnected; let error; @@ -319,7 +314,7 @@ export class RemoteClustersFormField extends PureComponent { render() { const { remoteClusters, selected, isEditable, areErrorsVisible } = this.props; - const remoteCluster = remoteClusters.find(c => c.name === selected); + const remoteCluster = remoteClusters.find((c) => c.name === selected); const hasClusters = Boolean(remoteClusters.length); const isSelectedRemoteClusterConnected = remoteCluster && remoteCluster.isConnected; const isInvalid = areErrorsVisible && (!hasClusters || !isSelectedRemoteClusterConnected); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_provider.js index 5b26444a136b8..4690f02539886 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_provider.js @@ -19,7 +19,7 @@ export class RemoteClustersProvider extends PureComponent { } loadRemoteClusters() { - const sortClusterByName = remoteClusters => + const sortClusterByName = (remoteClusters) => remoteClusters.sort((a, b) => { if (a.name < b.name) { return -1; @@ -31,13 +31,13 @@ export class RemoteClustersProvider extends PureComponent { }); loadRemoteClusters() .then(sortClusterByName) - .then(remoteClusters => { + .then((remoteClusters) => { this.setState({ isLoading: false, remoteClusters, }); }) - .catch(error => { + .catch((error) => { this.setState({ isLoading: false, error, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/index.tsx b/x-pack/plugins/cross_cluster_replication/public/app/index.tsx index 79569b587f97f..8be3eb5c8b32a 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/index.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/index.tsx @@ -6,8 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; -import { HashRouter } from 'react-router-dom'; -import { I18nStart } from 'kibana/public'; +import { I18nStart, ScopedHistory, ApplicationStart } from 'kibana/public'; import { UnmountCallback } from 'src/core/public'; import { init as initBreadcrumbs, SetBreadcrumbs } from './services/breadcrumbs'; @@ -15,13 +14,16 @@ import { init as initDocumentation } from './services/documentation_links'; import { App } from './app'; import { ccrStore } from './store'; -const renderApp = (element: Element, I18nContext: I18nStart['Context']): UnmountCallback => { +const renderApp = ( + element: Element, + I18nContext: I18nStart['Context'], + history: ScopedHistory, + getUrlForApp: ApplicationStart['getUrlForApp'] +): UnmountCallback => { render( - - - + , element @@ -36,17 +38,21 @@ export async function mountApp({ I18nContext, ELASTIC_WEBSITE_URL, DOC_LINK_VERSION, + history, + getUrlForApp, }: { element: Element; setBreadcrumbs: SetBreadcrumbs; I18nContext: I18nStart['Context']; ELASTIC_WEBSITE_URL: string; DOC_LINK_VERSION: string; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; }): Promise { // Import and initialize additional services here instead of in plugin.ts to reduce the size of the // initial bundle as much as possible. initBreadcrumbs(setBreadcrumbs); initDocumentation(`${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`); - return renderApp(element, I18nContext); + return renderApp(element, I18nContext, history, getUrlForApp); } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js index 95ae14ab89f58..b34b831e07ca4 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js @@ -13,12 +13,12 @@ import { AutoFollowPatternAdd as AutoFollowPatternAddView } from './auto_follow_ const scope = SECTIONS.AUTO_FOLLOW_PATTERN; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ apiStatus: getApiStatus(`${scope}-save`)(state), apiError: getApiError(`${scope}-save`)(state), }); -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ saveAutoFollowPattern: (id, autoFollowPattern) => dispatch(saveAutoFollowPattern(id, autoFollowPattern)), clearApiError: () => dispatch(clearApiError(scope)), diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js index 60a6cc79376e5..76fdf6e2fd766 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js @@ -27,7 +27,7 @@ export class AutoFollowPatternAdd extends PureComponent { }; componentDidMount() { - setBreadcrumbs([listBreadcrumb, addBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/auto_follow_patterns'), addBreadcrumb]); } componentWillUnmount() { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js index be470edc07537..4eb1054248737 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js @@ -23,7 +23,7 @@ import { AutoFollowPatternEdit as AutoFollowPatternEditView } from './auto_follo const scope = SECTIONS.AUTO_FOLLOW_PATTERN; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ apiStatus: { get: getApiStatus(`${scope}-get`)(state), save: getApiStatus(`${scope}-save`)(state), @@ -36,9 +36,9 @@ const mapStateToProps = state => ({ autoFollowPattern: getSelectedAutoFollowPattern('edit')(state), }); -const mapDispatchToProps = dispatch => ({ - getAutoFollowPattern: id => dispatch(getAutoFollowPattern(id)), - selectAutoFollowPattern: id => dispatch(selectEditAutoFollowPattern(id)), +const mapDispatchToProps = (dispatch) => ({ + getAutoFollowPattern: (id) => dispatch(getAutoFollowPattern(id)), + selectAutoFollowPattern: (id) => dispatch(selectEditAutoFollowPattern(id)), saveAutoFollowPattern: (id, autoFollowPattern) => { // Strip out errors. const { active, remoteCluster, leaderIndexPatterns, followIndexPattern } = autoFollowPattern; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js index 387d7817a0357..d89e3adb531ab 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPageContent, EuiSpacer } from '@elastic/eui'; import { listBreadcrumb, editBreadcrumb, setBreadcrumbs } from '../../services/breadcrumbs'; -import { routing } from '../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { AutoFollowPatternForm, AutoFollowPatternPageTitle, @@ -54,7 +54,7 @@ export class AutoFollowPatternEdit extends PureComponent { selectAutoFollowPattern(decodedId); - setBreadcrumbs([listBreadcrumb, editBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/auto_follow_patterns'), editBreadcrumb]); } componentDidUpdate(prevProps, prevState) { @@ -108,7 +108,7 @@ export class AutoFollowPatternEdit extends PureComponent { ({ +const mapStateToProps = (state) => ({ apiStatus: getApiStatus(`${scope}-save`)(state), apiError: getApiError(`${scope}-save`)(state), }); -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ saveFollowerIndex: (id, followerIndex) => dispatch(saveFollowerIndex(id, followerIndex)), clearApiError: () => dispatch(clearApiError(`${scope}-save`)), }); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js index 003e27777652b..a4056406d13fd 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js @@ -27,7 +27,7 @@ export class FollowerIndexAdd extends PureComponent { }; componentDidMount() { - setBreadcrumbs([listBreadcrumb, addBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/follower_indices'), addBreadcrumb]); } componentWillUnmount() { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.container.js index 9bfbdda41fcd5..f870031f170db 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.container.js @@ -23,7 +23,7 @@ import { FollowerIndexEdit as FollowerIndexEditView } from './follower_index_edi const scope = SECTIONS.FOLLOWER_INDEX; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ apiStatus: { get: getApiStatus(`${scope}-get`)(state), save: getApiStatus(`${scope}-save`)(state), @@ -36,9 +36,9 @@ const mapStateToProps = state => ({ followerIndex: getSelectedFollowerIndex('edit')(state), }); -const mapDispatchToProps = dispatch => ({ - getFollowerIndex: id => dispatch(getFollowerIndex(id)), - selectFollowerIndex: id => dispatch(selectEditFollowerIndex(id)), +const mapDispatchToProps = (dispatch) => ({ + getFollowerIndex: (id) => dispatch(getFollowerIndex(id)), + selectFollowerIndex: (id) => dispatch(selectEditFollowerIndex(id)), saveFollowerIndex: (id, followerIndex) => dispatch(saveFollowerIndex(id, followerIndex, true)), clearApiError: () => { dispatch(clearApiError(`${scope}-get`)); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js index 22f9a7338384b..0a3eb7fd4a2c9 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { setBreadcrumbs, listBreadcrumb, editBreadcrumb } from '../../services/breadcrumbs'; -import { routing } from '../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { FollowerIndexForm, FollowerIndexPageTitle, @@ -74,7 +74,7 @@ export class FollowerIndexEdit extends PureComponent { selectFollowerIndex(decodedId); - setBreadcrumbs([listBreadcrumb, editBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/follower_indices'), editBreadcrumb]); } componentDidUpdate(prevProps, prevState) { @@ -151,7 +151,7 @@ export class FollowerIndexEdit extends PureComponent { ({ +const mapStateToProps = (state) => ({ autoFollowPatterns: getListAutoFollowPatterns(state), autoFollowPatternId: getSelectedAutoFollowPatternId('detail')(state), apiStatus: getApiStatus(scope)(state), @@ -31,9 +31,9 @@ const mapStateToProps = state => ({ isAuthorized: isApiAuthorized(scope)(state), }); -const mapDispatchToProps = dispatch => ({ - loadAutoFollowPatterns: inBackground => dispatch(loadAutoFollowPatterns(inBackground)), - selectAutoFollowPattern: id => dispatch(selectDetailAutoFollowPattern(id)), +const mapDispatchToProps = (dispatch) => ({ + loadAutoFollowPatterns: (inBackground) => dispatch(loadAutoFollowPatterns(inBackground)), + selectAutoFollowPattern: (id) => dispatch(selectDetailAutoFollowPattern(id)), loadAutoFollowStats: () => dispatch(loadAutoFollowStats()), }); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js index c8cf94842aa68..5ef78b9ba6bb5 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { routing } from '../../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { extractQueryParams } from '../../../services/query_params'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD } from '../../../constants'; @@ -103,7 +103,7 @@ export class AutoFollowPatternList extends PureComponent { } renderHeader() { - const { isAuthorized } = this.props; + const { isAuthorized, history } = this.props; return ( @@ -122,7 +122,7 @@ export class AutoFollowPatternList extends PureComponent { {isAuthorized && ( ({ +const mapStateToProps = (state) => ({ apiStatusDelete: getApiStatus(`${scope}-delete`)(state), }); -const mapDispatchToProps = dispatch => ({ - selectAutoFollowPattern: name => dispatch(selectDetailAutoFollowPattern(name)), - pauseAutoFollowPattern: name => dispatch(pauseAutoFollowPattern(name)), - resumeAutoFollowPattern: name => dispatch(resumeAutoFollowPattern(name)), +const mapDispatchToProps = (dispatch) => ({ + selectAutoFollowPattern: (name) => dispatch(selectDetailAutoFollowPattern(name)), + pauseAutoFollowPattern: (name) => dispatch(pauseAutoFollowPattern(name)), + resumeAutoFollowPattern: (name) => dispatch(resumeAutoFollowPattern(name)), }); export const AutoFollowPatternTable = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index d682fdaadf818..2309dece3f92b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -27,7 +27,7 @@ const getFilteredPatterns = (autoFollowPatterns, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); - return autoFollowPatterns.filter(autoFollowPattern => { + return autoFollowPatterns.filter((autoFollowPattern) => { const { name, remoteCluster, @@ -107,7 +107,7 @@ export class AutoFollowPatternTable extends PureComponent { ), sortable: true, truncateText: false, - render: name => { + render: (name) => { return ( { @@ -130,7 +130,7 @@ export class AutoFollowPatternTable extends PureComponent { defaultMessage: 'Status', } ), - render: active => { + render: (active) => { const statusText = active ? i18n.translate( 'xpack.crossClusterReplication.autoFollowPatternList.table.statusTextActive', @@ -169,7 +169,7 @@ export class AutoFollowPatternTable extends PureComponent { defaultMessage: 'Leader patterns', } ), - render: leaderIndexPatterns => leaderIndexPatterns.join(', '), + render: (leaderIndexPatterns) => leaderIndexPatterns.join(', '), }, { field: 'followIndexPatternPrefix', @@ -217,7 +217,7 @@ export class AutoFollowPatternTable extends PureComponent { return ( { + onClick={(event) => { if (event.stopPropagation) { event.stopPropagation(); } @@ -250,7 +250,7 @@ export class AutoFollowPatternTable extends PureComponent { return ( (window.location.hash = routing.getAutoFollowPatternPath(name))} + onClick={() => routing.navigate(routing.getAutoFollowPatternPath(name))} data-test-subj="contextMenuEditButton" > @@ -270,7 +270,7 @@ export class AutoFollowPatternTable extends PureComponent { return ( - {deleteAutoFollowPattern => ( + {(deleteAutoFollowPattern) => ( deleteAutoFollowPattern(name)} data-test-subj="contextMenuDeleteButton" @@ -318,7 +318,7 @@ export class AutoFollowPatternTable extends PureComponent { }; const selection = { - onSelectionChange: selectedItems => + onSelectionChange: (selectedItems) => this.setState({ selectedItems: selectedItems.map(({ name }) => name) }), }; @@ -326,13 +326,11 @@ export class AutoFollowPatternTable extends PureComponent { toolsLeft: selectedItems.length ? ( - filteredAutoFollowPatterns.find(item => item.name === name) + patterns={this.state.selectedItems.map((name) => + filteredAutoFollowPatterns.find((item) => item.name === name) )} /> - ) : ( - undefined - ), + ) : undefined, onChange: this.onSearch, box: { incremental: true, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js index a5d72a5e7e136..dcf494bccf8ff 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js @@ -16,7 +16,7 @@ import { SECTIONS } from '../../../../../constants'; const scope = SECTIONS.AUTO_FOLLOW_PATTERN; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ autoFollowPatternId: getSelectedAutoFollowPatternId('detail')(state), autoFollowPattern: getSelectedAutoFollowPattern('detail')(state), apiStatus: getApiStatus(scope)(state), diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js index 3f2ed82420ff1..6b2ee01bff8df 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js @@ -31,6 +31,7 @@ import { EuiTitle, } from '@elastic/eui'; +import { routing } from '../../../../../services/routing'; import { AutoFollowPatternIndicesPreview, AutoFollowPatternActionMenu, @@ -296,7 +297,12 @@ export class DetailPanel extends Component { - + { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; @@ -47,15 +47,15 @@ export class ContextMenu extends PureComponent { }); }; - editFollowerIndex = id => { - const uri = routing.getFollowerIndexPath(id, '/edit', false); + editFollowerIndex = (id) => { + const uri = routing.getFollowerIndexPath(id); routing.navigate(uri); }; render() { const { followerIndices } = this.props; const followerIndicesLength = followerIndices.length; - const followerIndexNames = followerIndices.map(index => index.name); + const followerIndexNames = followerIndices.map((index) => index.name); const { iconSide = 'right', iconType = 'arrowDown', @@ -84,7 +84,7 @@ export class ContextMenu extends PureComponent { const pausedFollowerIndexNames = followerIndices .filter(({ isPaused }) => isPaused) - .map(index => index.name); + .map((index) => index.name); const activeFollowerIndices = followerIndices.filter(({ isPaused }) => !isPaused); return ( @@ -107,7 +107,7 @@ export class ContextMenu extends PureComponent { {activeFollowerIndices.length ? ( - {pauseFollowerIndex => ( + {(pauseFollowerIndex) => ( pauseFollowerIndex(activeFollowerIndices)} @@ -124,7 +124,7 @@ export class ContextMenu extends PureComponent { {pausedFollowerIndexNames.length ? ( - {resumeFollowerIndex => ( + {(resumeFollowerIndex) => ( resumeFollowerIndex(pausedFollowerIndexNames)} @@ -156,7 +156,7 @@ export class ContextMenu extends PureComponent { )} - {unfollowLeaderIndex => ( + {(unfollowLeaderIndex) => ( unfollowLeaderIndex(followerIndexNames)} diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js index 46190c27b27e5..640b7a25ce06b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js @@ -16,7 +16,7 @@ import { SECTIONS } from '../../../../../constants'; const scope = SECTIONS.FOLLOWER_INDEX; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ followerIndexId: getSelectedFollowerIndexId('detail')(state), followerIndex: getSelectedFollowerIndex('detail')(state), apiStatus: getApiStatus(scope)(state), diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js index 4436d76643e6c..a133c10b148aa 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js @@ -32,6 +32,7 @@ import { import 'brace/theme/textmate'; import { getIndexListUri } from '../../../../../../../../../plugins/index_management/public'; +import { routing } from '../../../../../services/routing'; import { API_STATUS } from '../../../../../constants'; import { ContextMenu } from '../context_menu'; @@ -452,7 +453,12 @@ export class DetailPanel extends Component { - + ({ +const mapStateToProps = (state) => ({ apiStatusDelete: getApiStatus(`${scope}-delete`)(state), }); -const mapDispatchToProps = dispatch => ({ - selectFollowerIndex: name => dispatch(selectDetailFollowerIndex(name)), +const mapDispatchToProps = (dispatch) => ({ + selectFollowerIndex: (name) => dispatch(selectDetailFollowerIndex(name)), }); export const FollowerIndicesTable = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index e95b3b0356aba..183609355fdfd 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -30,7 +30,7 @@ const getFilteredIndices = (followerIndices, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); - return followerIndices.filter(followerIndex => { + return followerIndices.filter((followerIndex) => { const { name, remoteCluster, leaderIndex } = followerIndex; if (name.toLowerCase().includes(normalizedSearchText)) { @@ -96,8 +96,8 @@ export class FollowerIndicesTable extends PureComponent { }); }; - editFollowerIndex = id => { - const uri = routing.getFollowerIndexPath(id, '/edit', false); + editFollowerIndex = (id) => { + const uri = routing.getFollowerIndexPath(id); routing.navigate(uri); }; @@ -107,7 +107,7 @@ export class FollowerIndicesTable extends PureComponent { const actions = [ /* Pause or resume follower index */ { - render: followerIndex => { + render: (followerIndex) => { const { name, isPaused } = followerIndex; const label = isPaused ? i18n.translate( @@ -125,7 +125,7 @@ export class FollowerIndicesTable extends PureComponent { return isPaused ? ( - {resumeFollowerIndex => ( + {(resumeFollowerIndex) => ( resumeFollowerIndex(name)} data-test-subj="resumeButton"> {label} @@ -134,7 +134,7 @@ export class FollowerIndicesTable extends PureComponent { ) : ( - {pauseFollowerIndex => ( + {(pauseFollowerIndex) => ( pauseFollowerIndex(followerIndex)} data-test-subj="pauseButton" @@ -177,7 +177,7 @@ export class FollowerIndicesTable extends PureComponent { return ( - {unfollowLeaderIndex => ( + {(unfollowLeaderIndex) => ( unfollowLeaderIndex(name)} data-test-subj="unfollowButton"> {label} @@ -200,7 +200,7 @@ export class FollowerIndicesTable extends PureComponent { ), sortable: true, truncateText: false, - render: name => { + render: (name) => { return ( { @@ -224,7 +224,7 @@ export class FollowerIndicesTable extends PureComponent { ), truncateText: true, sortable: true, - render: isPaused => { + render: (isPaused) => { return isPaused ? ( this.setState({ selectedItems: newSelectedItems }), + onSelectionChange: (newSelectedItems) => this.setState({ selectedItems: newSelectedItems }), }; const search = { toolsLeft: selectedItems.length ? ( - ) : ( - undefined - ), + ) : undefined, onChange: this.onSearch, box: { incremental: true, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js index f49db53d9e304..0afb0536726a3 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js @@ -19,7 +19,7 @@ import { FollowerIndicesList as FollowerIndicesListView } from './follower_indic const scope = SECTIONS.FOLLOWER_INDEX; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ followerIndices: getListFollowerIndices(state), followerIndexId: getSelectedFollowerIndexId('detail')(state), apiStatus: getApiStatus(scope)(state), @@ -27,9 +27,9 @@ const mapStateToProps = state => ({ isAuthorized: isApiAuthorized(scope)(state), }); -const mapDispatchToProps = dispatch => ({ - loadFollowerIndices: inBackground => dispatch(loadFollowerIndices(inBackground)), - selectFollowerIndex: id => dispatch(selectDetailFollowerIndex(id)), +const mapDispatchToProps = (dispatch) => ({ + loadFollowerIndices: (inBackground) => dispatch(loadFollowerIndices(inBackground)), + selectFollowerIndex: (id) => dispatch(selectDetailFollowerIndex(id)), }); export const FollowerIndicesList = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index 7b843d08cefd3..4d4cbbf6825ec 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { routing } from '../../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { extractQueryParams } from '../../../services/query_params'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants'; @@ -94,7 +94,7 @@ export class FollowerIndicesList extends PureComponent { } renderHeader() { - const { isAuthorized } = this.props; + const { isAuthorized, history } = this.props; return ( @@ -113,7 +113,7 @@ export class FollowerIndicesList extends PureComponent { {isAuthorized && ( ({ +const mapStateToProps = (state) => ({ autoFollowPatterns: getListAutoFollowPatterns(state), isAutoFollowApiAuthorized: isApiAuthorized(SECTIONS.AUTO_FOLLOW_PATTERN)(state), followerIndices: getListFollowerIndices(state), diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js index bcd9dad114862..26f2ad33736dd 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js @@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; import { setBreadcrumbs, listBreadcrumb } from '../../services/breadcrumbs'; import { routing } from '../../services/routing'; import { AutoFollowPatternList } from './auto_follow_pattern_list'; @@ -45,7 +44,7 @@ export class CrossClusterReplicationHome extends PureComponent { ]; componentDidMount() { - setBreadcrumbs([listBreadcrumb]); + setBreadcrumbs([listBreadcrumb()]); } static getDerivedStateFromProps(props) { @@ -59,7 +58,8 @@ export class CrossClusterReplicationHome extends PureComponent { }; } - onSectionChange = section => { + onSectionChange = (section) => { + setBreadcrumbs([listBreadcrumb(`/${section}`)]); routing.navigate(`/${section}`); }; @@ -79,7 +79,7 @@ export class CrossClusterReplicationHome extends PureComponent { - {this.tabs.map(tab => ( + {this.tabs.map((tab) => ( this.onSectionChange(tab.id)} isSelected={tab.id === this.state.activeSection} @@ -94,12 +94,8 @@ export class CrossClusterReplicationHome extends PureComponent { - - + + diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js index adff40ef29be6..72a4532bfafac 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js @@ -43,17 +43,17 @@ export const getHttpClient = () => { // --- -const createIdString = ids => ids.map(id => encodeURIComponent(id)).join(','); +const createIdString = (ids) => ids.map((id) => encodeURIComponent(id)).join(','); /* Auto Follow Pattern */ export const loadAutoFollowPatterns = () => httpClient.get(`${API_BASE_PATH}/auto_follow_patterns`); -export const getAutoFollowPattern = id => +export const getAutoFollowPattern = (id) => httpClient.get(`${API_BASE_PATH}/auto_follow_patterns/${encodeURIComponent(id)}`); export const loadRemoteClusters = () => httpClient.get(API_REMOTE_CLUSTERS_BASE_PATH); -export const createAutoFollowPattern = autoFollowPattern => { +export const createAutoFollowPattern = (autoFollowPattern) => { const request = httpClient.post(`${API_BASE_PATH}/auto_follow_patterns`, { body: JSON.stringify(autoFollowPattern), }); @@ -68,16 +68,16 @@ export const updateAutoFollowPattern = (id, autoFollowPattern) => { return trackUserRequest(request, UIM_AUTO_FOLLOW_PATTERN_UPDATE); }; -export const deleteAutoFollowPattern = id => { +export const deleteAutoFollowPattern = (id) => { const ids = arrify(id); - const idString = ids.map(_id => encodeURIComponent(_id)).join(','); + const idString = ids.map((_id) => encodeURIComponent(_id)).join(','); const request = httpClient.delete(`${API_BASE_PATH}/auto_follow_patterns/${idString}`); const uiMetric = ids.length > 1 ? UIM_AUTO_FOLLOW_PATTERN_DELETE_MANY : UIM_AUTO_FOLLOW_PATTERN_DELETE; return trackUserRequest(request, uiMetric); }; -export const pauseAutoFollowPattern = id => { +export const pauseAutoFollowPattern = (id) => { const ids = arrify(id); const idString = ids.map(encodeURIComponent).join(','); const request = httpClient.post(`${API_BASE_PATH}/auto_follow_patterns/${idString}/pause`); @@ -87,7 +87,7 @@ export const pauseAutoFollowPattern = id => { return trackUserRequest(request, uiMetric); }; -export const resumeAutoFollowPattern = id => { +export const resumeAutoFollowPattern = (id) => { const ids = arrify(id); const idString = ids.map(encodeURIComponent).join(','); const request = httpClient.post(`${API_BASE_PATH}/auto_follow_patterns/${idString}/resume`); @@ -100,10 +100,10 @@ export const resumeAutoFollowPattern = id => { /* Follower Index */ export const loadFollowerIndices = () => httpClient.get(`${API_BASE_PATH}/follower_indices`); -export const getFollowerIndex = id => +export const getFollowerIndex = (id) => httpClient.get(`${API_BASE_PATH}/follower_indices/${encodeURIComponent(id)}`); -export const createFollowerIndex = followerIndex => { +export const createFollowerIndex = (followerIndex) => { const uiMetrics = [UIM_FOLLOWER_INDEX_CREATE]; const isUsingAdvancedSettings = !areAllSettingsDefault(followerIndex); if (isUsingAdvancedSettings) { @@ -115,7 +115,7 @@ export const createFollowerIndex = followerIndex => { return trackUserRequest(request, uiMetrics); }; -export const pauseFollowerIndex = id => { +export const pauseFollowerIndex = (id) => { const ids = arrify(id); const idString = createIdString(ids); const request = httpClient.put(`${API_BASE_PATH}/follower_indices/${idString}/pause`); @@ -123,7 +123,7 @@ export const pauseFollowerIndex = id => { return trackUserRequest(request, uiMetric); }; -export const resumeFollowerIndex = id => { +export const resumeFollowerIndex = (id) => { const ids = arrify(id); const idString = createIdString(ids); const request = httpClient.put(`${API_BASE_PATH}/follower_indices/${idString}/resume`); @@ -131,7 +131,7 @@ export const resumeFollowerIndex = id => { return trackUserRequest(request, uiMetric); }; -export const unfollowLeaderIndex = id => { +export const unfollowLeaderIndex = (id) => { const ids = arrify(id); const idString = createIdString(ids); const request = httpClient.put(`${API_BASE_PATH}/follower_indices/${idString}/unfollow`); @@ -189,10 +189,12 @@ export const loadIndices = () => { } abortController = new AbortController(); const { signal } = abortController; - return httpClient.get(`${API_INDEX_MANAGEMENT_BASE_PATH}/indices`, { signal }).then(response => { - abortController = null; - return response; - }); + return httpClient + .get(`${API_INDEX_MANAGEMENT_BASE_PATH}/indices`, { signal }) + .then((response) => { + abortController = null; + return response; + }); }; export const loadPermissions = () => httpClient.get(`${API_BASE_PATH}/permissions`); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.js index 70311d5ba1e4d..5433ee73282a0 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const parseAutoFollowError = error => { +export const parseAutoFollowError = (error) => { if (!error.leaderIndex) { return null; } @@ -27,7 +27,7 @@ export const parseAutoFollowError = error => { export const parseAutoFollowErrors = (recentAutoFollowErrors, maxErrorsToShow = 5) => recentAutoFollowErrors .map(parseAutoFollowError) - .filter(error => error !== null) + .filter((error) => error !== null) .reduce((byId, error) => { if (!byId[error.id]) { byId[error.id] = []; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.js index 5d374ab3779e4..e717d130da2b0 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.js @@ -29,24 +29,22 @@ export const getPreviewIndicesFromAutoFollowPattern = ({ limit = 5, wildcardPlaceHolders = [ moment().format('YYYY-MM-DD'), - moment() - .add(1, 'days') - .format('YYYY-MM-DD'), - moment() - .add(2, 'days') - .format('YYYY-MM-DD'), + moment().add(1, 'days').format('YYYY-MM-DD'), + moment().add(2, 'days').format('YYYY-MM-DD'), ], }) => { const indicesPreview = []; let indexPreview; let leaderIndexTemplate; - leaderIndexPatterns.forEach(leaderIndexPattern => { - wildcardPlaceHolders.forEach(placeHolder => { + leaderIndexPatterns.forEach((leaderIndexPattern) => { + wildcardPlaceHolders.forEach((placeHolder) => { leaderIndexTemplate = leaderIndexPattern.replace(/\*/g, placeHolder); indexPreview = getFollowPattern(prefix, suffix, leaderIndexTemplate); - if (!indicesPreview.some(_indexPreview => indexPreview.toString === _indexPreview.toString)) { + if ( + !indicesPreview.some((_indexPreview) => indexPreview.toString === _indexPreview.toString) + ) { indicesPreview.push(indexPreview); } }); @@ -58,7 +56,7 @@ export const getPreviewIndicesFromAutoFollowPattern = ({ }; }; -export const getPrefixSuffixFromFollowPattern = followPattern => { +export const getPrefixSuffixFromFollowPattern = (followPattern) => { let followIndexPatternPrefix; let followIndexPatternSuffix; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.test.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.test.js index 56633549ef013..dc5b19799940d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.test.js @@ -29,14 +29,10 @@ describe('Auto-follo pattern service', () => { }); expect(hasMore).toBe(false); - expect(indicesPreview.map(preview => preview.toString)).toEqual([ + expect(indicesPreview.map((preview) => preview.toString)).toEqual([ `prefix_logstash-${moment().format('YYYY-MM-DD')}_suffix`, - `prefix_logstash-${moment() - .add(1, 'days') - .format('YYYY-MM-DD')}_suffix`, - `prefix_logstash-${moment() - .add(2, 'days') - .format('YYYY-MM-DD')}_suffix`, + `prefix_logstash-${moment().add(1, 'days').format('YYYY-MM-DD')}_suffix`, + `prefix_logstash-${moment().add(2, 'days').format('YYYY-MM-DD')}_suffix`, ]); }); @@ -50,18 +46,12 @@ describe('Auto-follo pattern service', () => { }); expect(hasMore).toBe(true); - expect(indicesPreview.map(preview => preview.toString)).toEqual([ + expect(indicesPreview.map((preview) => preview.toString)).toEqual([ `prefix_logstash-${moment().format('YYYY-MM-DD')}_suffix`, - `prefix_logstash-${moment() - .add(1, 'days') - .format('YYYY-MM-DD')}_suffix`, - `prefix_logstash-${moment() - .add(2, 'days') - .format('YYYY-MM-DD')}_suffix`, + `prefix_logstash-${moment().add(1, 'days').format('YYYY-MM-DD')}_suffix`, + `prefix_logstash-${moment().add(2, 'days').format('YYYY-MM-DD')}_suffix`, `prefix_other-${moment().format('YYYY-MM-DD')}_suffix`, - `prefix_other-${moment() - .add(1, 'days') - .format('YYYY-MM-DD')}_suffix`, + `prefix_other-${moment().add(1, 'days').format('YYYY-MM-DD')}_suffix`, ]); }); @@ -76,7 +66,7 @@ describe('Auto-follo pattern service', () => { wildcardPlaceHolders, }); - expect(indicesPreview.map(preview => preview.toString)).toEqual([ + expect(indicesPreview.map((preview) => preview.toString)).toEqual([ 'prefix_logstash-A_suffix', 'prefix_logstash-B_suffix', ]); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js index cf394d4b3c7d8..39d40389daa17 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js @@ -52,7 +52,7 @@ export const validateName = (name = '') => { return errorMsg; }; -export const validateLeaderIndexPattern = indexPattern => { +export const validateLeaderIndexPattern = (indexPattern) => { if (indexPattern) { const errors = indexPatterns.validate(indexPattern); @@ -100,7 +100,7 @@ export const validateLeaderIndexPattern = indexPattern => { return null; }; -export const validateLeaderIndexPatterns = indexPatterns => { +export const validateLeaderIndexPatterns = (indexPatterns) => { // We only need to check if a value has been provided, because validation for this field // has already been executed as the user has entered input into it. if (!indexPatterns.length) { @@ -117,7 +117,7 @@ export const validateLeaderIndexPatterns = indexPatterns => { return null; }; -export const validatePrefix = prefix => { +export const validatePrefix = (prefix) => { // If it's empty, it is valid if (!prefix || !prefix.trim()) { return null; @@ -161,7 +161,7 @@ export const validatePrefix = prefix => { return null; }; -export const validateSuffix = suffix => { +export const validateSuffix = (suffix) => { // If it's empty, it is valid if (!suffix || !suffix.trim()) { return null; @@ -200,7 +200,7 @@ export const validateAutoFollowPattern = (autoFollowPattern = {}) => { let error = null; let fieldValue; - Object.keys(autoFollowPattern).forEach(fieldName => { + Object.keys(autoFollowPattern).forEach((fieldName) => { fieldValue = autoFollowPattern[fieldName]; error = null; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts index 84ac9356462ad..c3ca893e6182b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts @@ -9,8 +9,6 @@ import { ChromeBreadcrumb } from 'src/core/public'; import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; -import { BASE_PATH } from '../../../common/constants'; - export type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; let setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; @@ -19,11 +17,13 @@ export const init = (_setBreadcrumbs: SetBreadcrumbs): void => { setBreadcrumbs = _setBreadcrumbs; }; -export const listBreadcrumb = { - text: i18n.translate('xpack.crossClusterReplication.homeBreadcrumbTitle', { - defaultMessage: 'Cross-Cluster Replication', - }), - href: `#${BASE_PATH}`, +export const listBreadcrumb = (section?: string) => { + return { + text: i18n.translate('xpack.crossClusterReplication.homeBreadcrumbTitle', { + defaultMessage: 'Cross-Cluster Replication', + }), + href: section || '/', + }; }; export const addBreadcrumb = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/follower_index_default_settings.js b/x-pack/plugins/cross_cluster_replication/public/app/services/follower_index_default_settings.js index 118a54887d404..7699b776620f4 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/follower_index_default_settings.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/follower_index_default_settings.js @@ -6,7 +6,7 @@ import { FOLLOWER_INDEX_ADVANCED_SETTINGS } from '../../../common/constants'; -export const getSettingDefault = name => { +export const getSettingDefault = (name) => { if (!FOLLOWER_INDEX_ADVANCED_SETTINGS[name]) { throw new Error(`Unknown setting ${name}`); } @@ -18,8 +18,8 @@ export const isSettingDefault = (name, value) => { return getSettingDefault(name) === value; }; -export const areAllSettingsDefault = settings => { - return Object.keys(FOLLOWER_INDEX_ADVANCED_SETTINGS).every(name => +export const areAllSettingsDefault = (settings) => { + return Object.keys(FOLLOWER_INDEX_ADVANCED_SETTINGS).every((name) => isSettingDefault(name, settings[name]) ); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/get_remote_cluster_name.js b/x-pack/plugins/cross_cluster_replication/public/app/services/get_remote_cluster_name.js index bf144b7129475..4f47facf70b20 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/get_remote_cluster_name.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/get_remote_cluster_name.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -const getFirstConnectedCluster = clusters => { +const getFirstConnectedCluster = (clusters) => { for (let i = 0; i < clusters.length; i++) { if (clusters[i].isConnected) { return clusters[i]; @@ -16,7 +16,7 @@ const getFirstConnectedCluster = clusters => { }; export const getRemoteClusterName = (remoteClusters, selected) => { - return selected && remoteClusters.some(c => c.name === selected) + return selected && remoteClusters.some((c) => c.name === selected) ? selected : getFirstConnectedCluster(remoteClusters).name; }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/input_validation.js b/x-pack/plugins/cross_cluster_replication/public/app/services/input_validation.js index 7e2b45b625c1f..e702a47e91155 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/input_validation.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/input_validation.js @@ -8,17 +8,17 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { indices } from '../../../../../../src/plugins/es_ui_shared/public'; -const isEmpty = value => { +const isEmpty = (value) => { return !value || !value.trim().length; }; -const hasSpaces = value => (typeof value === 'string' ? value.includes(' ') : false); +const hasSpaces = (value) => (typeof value === 'string' ? value.includes(' ') : false); -const beginsWithPeriod = value => { +const beginsWithPeriod = (value) => { return value[0] === '.'; }; -const findIllegalCharacters = value => { +const findIllegalCharacters = (value) => { return indices.INDEX_ILLEGAL_CHARACTERS_VISIBLE.reduce((chars, char) => { if (value.includes(char)) { chars.push(char); @@ -28,7 +28,7 @@ const findIllegalCharacters = value => { }, []); }; -export const indexNameValidator = value => { +export const indexNameValidator = (value) => { if (isEmpty(value)) { return [ { return undefined; }; -export const leaderIndexValidator = value => { +export const leaderIndexValidator = (value) => { if (isEmpty(value)) { return [ - !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); - -const isLeftClickEvent = event => event.button === 0; +import { BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; const queryParamsFromObject = (params, encodeParams = false) => { if (!params) { @@ -26,67 +20,31 @@ const queryParamsFromObject = (params, encodeParams = false) => { return `?${paramsStr}`; }; -const appToBasePathMap = { - [APPS.CCR_APP]: BASE_PATH, - [APPS.REMOTE_CLUSTER_APP]: BASE_PATH_REMOTE_CLUSTERS, -}; - class Routing { _reactRouter = null; - /** - * The logic for generating hrefs and onClick handlers from the `to` prop is largely borrowed from - * https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js. - * - * @param {*} to URL to navigate to - */ - getRouterLinkProps(to, base = BASE_PATH, params = {}, encodeParams = false) { + getHrefToRemoteClusters(route = '/', params, encodeParams = false) { const search = queryParamsFromObject(params, encodeParams) || ''; - const location = - typeof to === 'string' - ? createLocation(base + to + search, null, null, this._reactRouter.history.location) - : to; - const href = this._reactRouter.history.createHref(location); - - const onClick = event => { - if (event.defaultPrevented) { - return; - } - - // If target prop is set (e.g. to "_blank"), let browser handle link. - if (event.target.getAttribute('target')) { - return; - } - - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - } - - // Prevent regular link behavior, which causes a browser refresh. - event.preventDefault(); - this._reactRouter.history.push(location); - }; - - return { href, onClick }; + return this._reactRouter.getUrlForApp('management', { + path: `${BASE_PATH_REMOTE_CLUSTERS}${route}${search}`, + }); } - navigate(route = '/home', app = APPS.CCR_APP, params, encodeParams = false) { + navigate(route = '/home', params, encodeParams = false) { const search = queryParamsFromObject(params, encodeParams); this._reactRouter.history.push({ - pathname: encodeURI(appToBasePathMap[app] + route), + pathname: encodeURI(route), search, }); } getAutoFollowPatternPath = (name, section = '/edit') => { - return encodeURI(`#${BASE_PATH}/auto_follow_patterns${section}/${encodeURIComponent(name)}`); + return encodeURI(`/auto_follow_patterns${section}/${encodeURIComponent(name)}`); }; - getFollowerIndexPath = (name, section = '/edit', withBase = true) => { - return withBase - ? encodeURI(`#${BASE_PATH}/follower_indices${section}/${encodeURIComponent(name)}`) - : encodeURI(`/follower_indices${section}/${encodeURIComponent(name)}`); + getFollowerIndexPath = (name, section = '/edit') => { + return encodeURI(`/follower_indices${section}/${encodeURIComponent(name)}`); }; get reactRouter() { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.ts index aecc4eb83893f..b4307ed125bf2 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.ts +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.ts @@ -24,7 +24,7 @@ export function init(usageCollection: UsageCollectionSetup): void { */ export function trackUserRequest(request: Promise, actionType: string) { // Only track successful actions. - return request.then(response => { + return request.then((response) => { // It looks like we're using the wrong type here, added via // https://github.com/elastic/kibana/pull/41113/files#diff-e65a0a6696a9d723969afd871cbd60cdR19 // but we'll keep it for now to avoid discontinuity in our telemetry data. diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/utils.js b/x-pack/plugins/cross_cluster_replication/public/app/services/utils.js index 9769228098c12..06ce3962966e9 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/utils.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/utils.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const objectToArray = obj => Object.keys(obj).map(k => ({ ...obj[k], __id__: k })); +export const objectToArray = (obj) => Object.keys(obj).map((k) => ({ ...obj[k], __id__: k })); export const arrayToObject = (array, keyProp = 'id') => array.reduce((acc, item) => { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/api.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/api.js index 4b97699ece2ff..6abb024b5dcce 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/api.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/api.js @@ -22,7 +22,10 @@ export const setApiError = ({ error, scope }) => ({ payload: { error, scope }, }); -export const clearApiError = scope => ({ type: t.API_ERROR_SET, payload: { error: null, scope } }); +export const clearApiError = (scope) => ({ + type: t.API_ERROR_SET, + payload: { error: null, scope }, +}); export const sendApiRequest = ({ label, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js index 52a22cb17d0a9..6503333924951 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js @@ -22,12 +22,12 @@ import { getSelectedAutoFollowPatternId } from '../selectors'; const { AUTO_FOLLOW_PATTERN: scope } = SECTIONS; -export const selectDetailAutoFollowPattern = id => ({ +export const selectDetailAutoFollowPattern = (id) => ({ type: t.AUTO_FOLLOW_PATTERN_SELECT_DETAIL, payload: id, }); -export const selectEditAutoFollowPattern = id => ({ +export const selectEditAutoFollowPattern = (id) => ({ type: t.AUTO_FOLLOW_PATTERN_SELECT_EDIT, payload: id, }); @@ -40,7 +40,7 @@ export const loadAutoFollowPatterns = (isUpdating = false) => handler: async () => await loadAutoFollowPatternsRequest(), }); -export const getAutoFollowPattern = id => +export const getAutoFollowPattern = (id) => sendApiRequest({ label: t.AUTO_FOLLOW_PATTERN_GET, scope: `${scope}-get`, @@ -76,13 +76,13 @@ export const saveAutoFollowPattern = (id, autoFollowPattern, isUpdating = false) ); getToasts().addSuccess(successMessage); - routing.navigate(`/auto_follow_patterns`, undefined, { + routing.navigate(`/auto_follow_patterns`, { pattern: encodeURIComponent(id), }); }, }); -export const deleteAutoFollowPattern = id => +export const deleteAutoFollowPattern = (id) => sendApiRequest({ label: t.AUTO_FOLLOW_PATTERN_DELETE, scope: `${scope}-delete`, @@ -144,13 +144,13 @@ export const deleteAutoFollowPattern = id => }, }); -export const pauseAutoFollowPattern = id => +export const pauseAutoFollowPattern = (id) => sendApiRequest({ label: t.AUTO_FOLLOW_PATTERN_PAUSE, scope: `${scope}-pause`, status: API_STATUS.UPDATING, handler: () => pauseAutoFollowPatternRequest(id), - onSuccess: response => { + onSuccess: (response) => { /** * We can have 1 or more auto-follow pattern pause operations * that can fail or succeed. We will show 1 toast notification for each. @@ -200,13 +200,13 @@ export const pauseAutoFollowPattern = id => }, }); -export const resumeAutoFollowPattern = id => +export const resumeAutoFollowPattern = (id) => sendApiRequest({ label: t.AUTO_FOLLOW_PATTERN_RESUME, scope: `${scope}-resume`, status: API_STATUS.UPDATING, handler: () => resumeAutoFollowPatternRequest(id), - onSuccess: response => { + onSuccess: (response) => { /** * We can have 1 or more auto-follow pattern resume operations * that can fail or succeed. We will show 1 toast notification for each. diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js index d081e0444eb58..1af5a95a29b98 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js @@ -23,12 +23,12 @@ import { getSelectedFollowerIndexId } from '../selectors'; const { FOLLOWER_INDEX: scope } = SECTIONS; -export const selectDetailFollowerIndex = id => ({ +export const selectDetailFollowerIndex = (id) => ({ type: t.FOLLOWER_INDEX_SELECT_DETAIL, payload: id, }); -export const selectEditFollowerIndex = id => ({ +export const selectEditFollowerIndex = (id) => ({ type: t.FOLLOWER_INDEX_SELECT_EDIT, payload: id, }); @@ -41,7 +41,7 @@ export const loadFollowerIndices = (isUpdating = false) => handler: async () => await loadFollowerIndicesRequest(), }); -export const getFollowerIndex = id => +export const getFollowerIndex = (id) => sendApiRequest({ label: t.FOLLOWER_INDEX_GET, scope: `${scope}-get`, @@ -77,13 +77,13 @@ export const saveFollowerIndex = (name, followerIndex, isUpdating = false) => ); getToasts().addSuccess(successMessage); - routing.navigate(`/follower_indices`, undefined, { + routing.navigate(`/follower_indices`, { name: encodeURIComponent(name), }); }, }); -export const pauseFollowerIndex = id => +export const pauseFollowerIndex = (id) => sendApiRequest({ label: t.FOLLOWER_INDEX_PAUSE, status: API_STATUS.SAVING, @@ -142,7 +142,7 @@ export const pauseFollowerIndex = id => }, }); -export const resumeFollowerIndex = id => +export const resumeFollowerIndex = (id) => sendApiRequest({ label: t.FOLLOWER_INDEX_RESUME, status: API_STATUS.SAVING, @@ -202,7 +202,7 @@ export const resumeFollowerIndex = id => }, }); -export const unfollowLeaderIndex = id => +export const unfollowLeaderIndex = (id) => sendApiRequest({ label: t.FOLLOWER_INDEX_UNFOLLOW, status: API_STATUS.DELETING, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/auto_follow_pattern.js index bd082f1b0372b..c09c02698106b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/auto_follow_pattern.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/auto_follow_pattern.js @@ -14,17 +14,17 @@ const initialState = { selectedEditId: null, }; -const success = action => `${action}_SUCCESS`; +const success = (action) => `${action}_SUCCESS`; const setActiveForIds = (ids, byId, active) => { const shallowCopyByIds = { ...byId }; - ids.forEach(id => { + ids.forEach((id) => { shallowCopyByIds[id].active = active; }); return shallowCopyByIds; }; -const parseAutoFollowPattern = autoFollowPattern => { +const parseAutoFollowPattern = (autoFollowPattern) => { // Extract prefix and suffix from follow index pattern const { followIndexPatternPrefix, followIndexPatternSuffix } = getPrefixSuffixFromFollowPattern( autoFollowPattern.followIndexPattern @@ -56,7 +56,7 @@ export const reducer = (state = initialState, action) => { case success(t.AUTO_FOLLOW_PATTERN_DELETE): { const byId = { ...state.byId }; const { itemsDeleted } = action.payload; - itemsDeleted.forEach(id => delete byId[id]); + itemsDeleted.forEach((id) => delete byId[id]); return { ...state, byId }; } case success(t.AUTO_FOLLOW_PATTERN_PAUSE): { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js index fe69a465257ef..3fbc2ca967ec0 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js @@ -13,9 +13,9 @@ const initialState = { selectedEditId: null, }; -const success = action => `${action}_SUCCESS`; +const success = (action) => `${action}_SUCCESS`; -const parseFollowerIndex = followerIndex => { +const parseFollowerIndex = (followerIndex) => { // Extract status into boolean return { ...followerIndex, isPaused: followerIndex.status === 'paused' }; }; @@ -42,7 +42,7 @@ export const reducer = (state = initialState, action) => { case success(t.FOLLOWER_INDEX_UNFOLLOW): { const byId = { ...state.byId }; const { itemsUnfollowed } = action.payload; - itemsUnfollowed.forEach(id => delete byId[id]); + itemsUnfollowed.forEach((id) => delete byId[id]); return { ...state, byId }; } default: diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/stats.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/stats.js index 4cacfe1b759a5..39d3b1c4d8e4f 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/stats.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/stats.js @@ -11,7 +11,7 @@ const initialState = { autoFollow: null, }; -const success = action => `${action}_SUCCESS`; +const success = (action) => `${action}_SUCCESS`; export const reducer = (state = initialState, action) => { switch (action.type) { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js index 7dbc5a327bc66..eb0b88b95bbfc 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js @@ -9,12 +9,13 @@ import { objectToArray } from '../../services/utils'; import { API_STATUS } from '../../constants'; // Api -export const getApiState = state => state.api; -export const getApiStatus = scope => - createSelector(getApiState, apiState => apiState.status[scope] || API_STATUS.IDLE); -export const getApiError = scope => createSelector(getApiState, apiState => apiState.error[scope]); -export const isApiAuthorized = scope => - createSelector(getApiError(scope), error => { +export const getApiState = (state) => state.api; +export const getApiStatus = (scope) => + createSelector(getApiState, (apiState) => apiState.status[scope] || API_STATUS.IDLE); +export const getApiError = (scope) => + createSelector(getApiState, (apiState) => apiState.error[scope]); +export const isApiAuthorized = (scope) => + createSelector(getApiError(scope), (error) => { if (!error) { return true; } @@ -22,20 +23,20 @@ export const isApiAuthorized = scope => }); // Stats -export const getStatsState = state => state.stats; +export const getStatsState = (state) => state.stats; export const getAutoFollowStats = createSelector( getStatsState, - statsState => statsState.autoFollow + (statsState) => statsState.autoFollow ); // Auto-follow pattern -export const getAutoFollowPatternState = state => state.autoFollowPattern; +export const getAutoFollowPatternState = (state) => state.autoFollowPattern; export const getAutoFollowPatterns = createSelector( getAutoFollowPatternState, - autoFollowPatternsState => autoFollowPatternsState.byId + (autoFollowPatternsState) => autoFollowPatternsState.byId ); export const getSelectedAutoFollowPatternId = (view = 'detail') => - createSelector(getAutoFollowPatternState, autoFollowPatternsState => + createSelector(getAutoFollowPatternState, (autoFollowPatternsState) => view === 'detail' ? autoFollowPatternsState.selectedDetailId : autoFollowPatternsState.selectedEditId @@ -59,22 +60,23 @@ export const getSelectedAutoFollowPattern = (view = 'detail') => return autoFollowPattern ? { ...autoFollowPattern, errors } : null; } ); -export const getListAutoFollowPatterns = createSelector(getAutoFollowPatterns, autoFollowPatterns => - objectToArray(autoFollowPatterns) +export const getListAutoFollowPatterns = createSelector( + getAutoFollowPatterns, + (autoFollowPatterns) => objectToArray(autoFollowPatterns) ); // Follower index -export const getFollowerIndexState = state => state.followerIndex; +export const getFollowerIndexState = (state) => state.followerIndex; export const getFollowerIndices = createSelector( getFollowerIndexState, - followerIndexState => followerIndexState.byId + (followerIndexState) => followerIndexState.byId ); export const getSelectedFollowerIndexId = (view = 'detail') => - createSelector(getFollowerIndexState, followerIndexState => + createSelector(getFollowerIndexState, (followerIndexState) => view === 'detail' ? followerIndexState.selectedDetailId : followerIndexState.selectedEditId ); export const getSelectedFollowerIndex = (view = 'detail') => - createSelector(getFollowerIndexState, followerIndexState => { + createSelector(getFollowerIndexState, (followerIndexState) => { const propId = view === 'detail' ? 'selectedDetailId' : 'selectedEditId'; if (!followerIndexState[propId]) { @@ -82,6 +84,6 @@ export const getSelectedFollowerIndex = (view = 'detail') => } return followerIndexState.byId[followerIndexState[propId]]; }); -export const getListFollowerIndices = createSelector(getFollowerIndices, followerIndices => +export const getListFollowerIndices = createSelector(getFollowerIndices, (followerIndices) => objectToArray(followerIndices) ); diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts index 561da838a4202..8bf0d519e685d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -41,13 +41,14 @@ export class CrossClusterReplicationPlugin implements Plugin { id: MANAGEMENT_ID, title: PLUGIN.TITLE, order: 6, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const { mountApp } = await import('./app'); const [coreStart] = await getStartServices(); const { i18n: { Context: I18nContext }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + application: { getUrlForApp }, } = coreStart; return mountApp({ @@ -56,16 +57,18 @@ export class CrossClusterReplicationPlugin implements Plugin { I18nContext, ELASTIC_WEBSITE_URL, DOC_LINK_VERSION, + history, + getUrlForApp, }); }, }); - ccrApp.disable(); - + // NOTE: We enable the plugin by default instead of disabling it by default because this + // creates a race condition that causes functional tests to fail on CI (see #66781). licensing.license$ .pipe(first()) .toPromise() - .then(license => { + .then((license) => { const licenseStatus = license.check(PLUGIN.ID, PLUGIN.minimumLicenseType); const isLicenseOk = licenseStatus.state === 'valid'; const config = this.initializerContext.config.get(); @@ -76,8 +79,6 @@ export class CrossClusterReplicationPlugin implements Plugin { const isCcrUiEnabled = config.ui.enabled && remoteClusters.isUiEnabled; if (isLicenseOk && isCcrUiEnabled) { - ccrApp.enable(); - if (indexManagement) { const propertyPath = 'isFollowerIndex'; @@ -94,6 +95,8 @@ export class CrossClusterReplicationPlugin implements Plugin { indexManagement.extensionsService.addBadge(followerBadgeExtension); } + } else { + ccrApp.disable(); } }); } diff --git a/x-pack/plugins/cross_cluster_replication/server/plugin.ts b/x-pack/plugins/cross_cluster_replication/server/plugin.ts index 7ef085a21ac1a..f30378d874a9a 100644 --- a/x-pack/plugins/cross_cluster_replication/server/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/server/plugin.ts @@ -57,7 +57,7 @@ const ccrDataEnricher = async (indicesList: Index[], callWithRequest: APICaller) 'transport.request', params ); - return indicesList.map(index => { + return indicesList.map((index) => { const isFollowerIndex = !!followerIndices.find( (followerIndex: { follower_index: string }) => { return followerIndex.follower_index === index.name; @@ -92,7 +92,7 @@ export class CrossClusterReplicationServerPlugin implements Plugin { + .then((config) => { // remoteClusters.isUiEnabled is driven by the xpack.remote_clusters.ui.enabled setting. // The CCR UI depends upon the Remote Clusters UI (e.g. by cross-linking to it), so if // the Remote Clusters UI is disabled we can't show the CCR UI. diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts index ed2633a4a469e..085e01a836942 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts @@ -44,7 +44,7 @@ export const registerDeleteRoute = ({ }; await Promise.all( - ids.map(_id => + ids.map((_id) => context .crossClusterReplication!.client.callAsCurrentUser('ccr.deleteAutoFollowPattern', { id: _id, diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts index 325939709e751..ca224b9ae3627 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts @@ -43,13 +43,13 @@ export const registerPauseRoute = ({ }; await Promise.all( - ids.map(_id => + ids.map((_id) => context .crossClusterReplication!.client.callAsCurrentUser('ccr.pauseAutoFollowPattern', { id: _id, }) .then(() => itemsPaused.push(_id)) - .catch(err => { + .catch((err) => { errors.push({ id: _id, error: formatError(err) }); }) ) diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts index f5e917773704c..c5f3e2190260e 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts @@ -49,7 +49,7 @@ export const registerResumeRoute = ({ id: _id, }) .then(() => itemsResumed.push(_id)) - .catch(err => { + .catch((err) => { errors.push({ id: _id, error: formatError(err) }); }) ) diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts index 7432ea7ca5c82..bf6f026e70849 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts @@ -47,7 +47,7 @@ export const registerPauseRoute = ({ id: _id, }) .then(() => itemsPaused.push(_id)) - .catch(err => { + .catch((err) => { errors.push({ id: _id, error: formatError(err) }); }) ) diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts index ca8f3a9f5fe9d..03f21df2e856e 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts @@ -47,7 +47,7 @@ export const registerResumeRoute = ({ id: _id, }) .then(() => itemsResumed.push(_id)) - .catch(err => { + .catch((err) => { errors.push({ id: _id, error: formatError(err) }); }) ) diff --git a/x-pack/plugins/cross_cluster_replication/server/services/license.ts b/x-pack/plugins/cross_cluster_replication/server/services/license.ts index bfd357867c3e2..5424092a01ee5 100644 --- a/x-pack/plugins/cross_cluster_replication/server/services/license.ts +++ b/x-pack/plugins/cross_cluster_replication/server/services/license.ts @@ -37,7 +37,7 @@ export class License { { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } ) { - licensing.license$.subscribe(license => { + licensing.license$.subscribe((license) => { const { state, message } = license.check(pluginId, minimumLicenseType); const hasRequiredLicense = state === 'valid'; diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index f416ca97f7110..37211ea537179 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["data", "advancedUiActions", "drilldowns", "embeddable", "dashboard", "share"], + "requiredPlugins": ["data", "uiActionsEnhanced", "drilldowns", "embeddable", "dashboard", "share"], "configPath": ["xpack", "dashboardEnhanced"] } diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index c258a4148f84a..413f5a7afe356 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -9,19 +9,19 @@ import { SharePluginStart, SharePluginSetup } from '../../../../src/plugins/shar import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { DashboardDrilldownsService } from './services'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; -import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; +import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../ui_actions_enhanced/public'; import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; import { DashboardStart } from '../../../../src/plugins/dashboard/public'; export interface SetupDependencies { - advancedUiActions: AdvancedUiActionsSetup; + uiActionsEnhanced: AdvancedUiActionsSetup; drilldowns: DrilldownsSetup; embeddable: EmbeddableSetup; share: SharePluginSetup; } export interface StartDependencies { - advancedUiActions: AdvancedUiActionsStart; + uiActionsEnhanced: AdvancedUiActionsStart; data: DataPublicPluginStart; drilldowns: DrilldownsStart; embeddable: EmbeddableStart; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx index 555acf1fca5ff..309e6cbf53a3d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx @@ -8,7 +8,7 @@ import { FlyoutEditDrilldownAction, FlyoutEditDrilldownParams } from './flyout_e import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks'; import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; -import { uiActionsEnhancedPluginMock } from '../../../../../../advanced_ui_actions/public/mocks'; +import { uiActionsEnhancedPluginMock } from '../../../../../../ui_actions_enhanced/public/mocks'; import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx index ec3a78e97eae4..9a4ecb2d4bfb0 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render, cleanup, act } from '@testing-library/react/pure'; import { MenuItem } from './menu_item'; import { createStateContainer } from '../../../../../../../../src/plugins/kibana_utils/public'; -import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../advanced_ui_actions/public'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../ui_actions_enhanced/public'; import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import '@testing-library/jest-dom'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts index cccacf701a9ad..e831f87baa11c 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts @@ -10,9 +10,9 @@ import { UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsStart, -} from '../../../../../advanced_ui_actions/public'; +} from '../../../../../ui_actions_enhanced/public'; import { TriggerContextMapping } from '../../../../../../../src/plugins/ui_actions/public'; -import { uiActionsEnhancedPluginMock } from '../../../../../advanced_ui_actions/public/mocks'; +import { uiActionsEnhancedPluginMock } from '../../../../../ui_actions_enhanced/public/mocks'; export class MockEmbeddable extends Embeddable { public rootType = 'dashboard'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index f5926cd6961c2..4325e3309b898 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -41,7 +41,7 @@ export class DashboardDrilldownsService { setupDrilldowns( core: CoreSetup, - { advancedUiActions: uiActions }: SetupDependencies + { uiActionsEnhanced: uiActions }: SetupDependencies ) { const start = createStartServicesGetter(core.getStartServices); const getDashboardUrlGenerator = () => { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx index dc19fccf5c92f..6d6803510a281 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx @@ -85,7 +85,7 @@ export class CollectConfigContainer extends React.Component< keepRange={config.useCurrentDateRange} isLoading={isLoading} error={error} - onDashboardSelect={dashboardId => { + onDashboardSelect={(dashboardId) => { onConfig({ ...config, dashboardId }); if (this.state.error) { this.setState({ error: undefined }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx index f3a966a73509c..37f82e449ff72 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx @@ -27,9 +27,9 @@ const InteractiveDemo: React.FC = () => { dashboards={dashboards} currentFilters={currentFilters} keepRange={keepRange} - onDashboardSelect={id => setActiveDashboardId(id)} - onCurrentFiltersToggle={() => setCurrentFilters(old => !old)} - onKeepRangeToggle={() => setKeepRange(old => !old)} + onDashboardSelect={(id) => setActiveDashboardId(id)} + onCurrentFiltersToggle={() => setCurrentFilters((old) => !old)} + onKeepRangeToggle={() => setKeepRange((old) => !old)} onSearchChange={() => {}} isLoading={false} /> @@ -44,7 +44,7 @@ storiesOf( console.log('onDashboardSelect', e)} + onDashboardSelect={(e) => console.log('onDashboardSelect', e)} onSearchChange={() => {}} isLoading={false} /> @@ -53,7 +53,7 @@ storiesOf( console.log('onDashboardSelect', e)} + onDashboardSelect={(e) => console.log('onDashboardSelect', e)} onCurrentFiltersToggle={() => console.log('onCurrentFiltersToggle')} onKeepRangeToggle={() => console.log('onKeepRangeToggle')} onSearchChange={() => {}} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx index a41a5fb718219..c9ecc74f1391c 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx @@ -37,7 +37,7 @@ export const DashboardDrilldownConfig: React.FC = isLoading, error, }) => { - const selectedTitle = dashboards.find(item => item.value === activeDashboardId)?.label || ''; + const selectedTitle = dashboards.find((item) => item.value === activeDashboardId)?.label || ''; return ( <> diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx index c94d19d28e6da..6ce7dccd3a3ec 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx @@ -101,13 +101,13 @@ describe('.execute() & getHref', () => { }, }, plugins: { - advancedUiActions: {}, + uiActionsEnhanced: {}, data: { actions: dataPluginActions, }, }, self: {}, - })) as unknown) as StartServicesGetter>, + })) as unknown) as StartServicesGetter>, getDashboardUrlGenerator: () => new UrlGeneratorsService().setup(coreMock.createSetup()).registerUrlGenerator( createDashboardUrlGenerator(() => diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index 21afa6e822dc5..26a69132cffb1 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -10,7 +10,7 @@ import { DashboardUrlGenerator } from '../../../../../../../src/plugins/dashboar import { ActionContext, Config } from './types'; import { CollectConfigContainer } from './components'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; -import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../advanced_ui_actions/public'; +import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../ui_actions_enhanced/public'; import { txtGoToDashboard } from './i18n'; import { esFilters } from '../../../../../../../src/plugins/data/public'; import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; @@ -22,7 +22,7 @@ import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_uti import { StartDependencies } from '../../../plugin'; export interface Params { - start: StartServicesGetter>; + start: StartServicesGetter>; getDashboardUrlGenerator: () => DashboardUrlGenerator; } @@ -38,7 +38,7 @@ export class DashboardToDashboardDrilldown public readonly euiIcon = 'dashboardApp'; - private readonly ReactCollectConfig: React.FC = props => ( + private readonly ReactCollectConfig: React.FC = (props) => ( ); @@ -93,7 +93,7 @@ export class DashboardToDashboardDrilldown const existingFilters = (config.useCurrentFilters ? currentFilters - : currentFilters?.filter(f => esFilters.isFilterPinned(f))) ?? []; + : currentFilters?.filter((f) => esFilters.isFilterPinned(f))) ?? []; // if useCurrentDashboardDataRange is enabled, then preserve current time range // if undefined is passed, then destination dashboard will figure out time range itself diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts index 211e0ad9a26c4..47cfb8cc61d00 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts @@ -34,7 +34,7 @@ describe('Kuery conjunction suggestions', () => { const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text })); expect(suggestions.length).toBe(2); - expect(suggestions.map(suggestion => suggestion.text)).toEqual(['and ', 'or ']); + expect(suggestions.map((suggestion) => suggestion.text)).toEqual(['and ', 'or ']); }); test('should suggest to insert the suggestion at the end of the string', async () => { @@ -43,8 +43,8 @@ describe('Kuery conjunction suggestions', () => { const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text, end })); expect(suggestions.length).toBe(2); - expect(suggestions.map(suggestion => suggestion.start)).toEqual([end, end]); - expect(suggestions.map(suggestion => suggestion.end)).toEqual([end, end]); + expect(suggestions.map((suggestion) => suggestion.start)).toEqual([end, end]); + expect(suggestions.map((suggestion) => suggestion.end)).toEqual([end, end]); }); test('should have descriptions', async () => { @@ -54,7 +54,7 @@ describe('Kuery conjunction suggestions', () => { expect(typeof suggestions).toBe('object'); expect(Object.keys(suggestions).length).toBe(2); - suggestions.forEach(suggestion => { + suggestions.forEach((suggestion) => { expect(typeof suggestion).toBe('object'); expect(suggestion).toHaveProperty('description'); }); diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx index fedb43812d3d0..db5378982d646 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx @@ -59,7 +59,7 @@ const conjunctions: Record = { ), }; -export const setupGetConjunctionSuggestions: KqlQuerySuggestionProvider = core => { +export const setupGetConjunctionSuggestions: KqlQuerySuggestionProvider = (core) => { return (querySuggestionsArgs, { text, end }) => { let suggestions: QuerySuggestion[] | [] = []; diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts index 2e12ae672f367..8751f7aee8123 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts @@ -110,7 +110,7 @@ describe('Kuery field suggestions', () => { mockKueryNode({ prefix, suffix }) ); expect(suggestions.length).toBeGreaterThan(0); - suggestions.forEach(suggestion => { + suggestions.forEach((suggestion) => { expect(suggestion).toHaveProperty('description'); }); }); diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx index ca045c929f6a1..a8b5cc98c614f 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx @@ -30,24 +30,26 @@ const getDescription = (field: IFieldType) => { const keywordComparator = (first: IFieldType, second: IFieldType) => { const extensions = ['raw', 'keyword']; - if (extensions.map(ext => `${first.name}.${ext}`).includes(second.name)) { + if (extensions.map((ext) => `${first.name}.${ext}`).includes(second.name)) { return 1; - } else if (extensions.map(ext => `${second.name}.${ext}`).includes(first.name)) { + } else if (extensions.map((ext) => `${second.name}.${ext}`).includes(first.name)) { return -1; } return first.name.localeCompare(second.name); }; -export const setupGetFieldSuggestions: KqlQuerySuggestionProvider = core => { +export const setupGetFieldSuggestions: KqlQuerySuggestionProvider = ( + core +) => { return ({ indexPatterns }, { start, end, prefix, suffix, nestedPath = '' }) => { const allFields = flatten( - indexPatterns.map(indexPattern => { + indexPatterns.map((indexPattern) => { return indexPattern.fields.filter(indexPatternsUtils.isFilterable); }) ); const search = `${prefix}${suffix}`.trim().toLowerCase(); - const matchingFields = allFields.filter(field => { + const matchingFields = allFields.filter((field) => { return ( (!nestedPath || (nestedPath && @@ -60,7 +62,7 @@ export const setupGetFieldSuggestions: KqlQuerySuggestionProvider { + const suggestions: QuerySuggestionField[] = sortedFields.map((field) => { const remainingPath = field.subType && field.subType.nested ? field.subType.nested.path.slice(nestedPath ? nestedPath.length + 1 : 0) diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts index 46fa7c68cee3e..546dc6361826a 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts @@ -51,7 +51,7 @@ export const setupKqlQuerySuggestionProvider = (core: CoreSetup): QuerySuggestio } }; - return querySuggestionsArgs => { + return (querySuggestionsArgs) => { const { query, selectionStart, selectionEnd } = querySuggestionsArgs; const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr( selectionEnd @@ -59,6 +59,6 @@ export const setupKqlQuerySuggestionProvider = (core: CoreSetup): QuerySuggestio return Promise.all( getSuggestionsByType(cursoredQuery, querySuggestionsArgs) - ).then(suggestionsByType => dedup(flatten(suggestionsByType))); + ).then((suggestionsByType) => dedup(flatten(suggestionsByType))); }; }; diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts index a00082f8c7d7c..2ddb3966bb560 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts @@ -28,8 +28,5 @@ function escapeNot(str: string) { // See the Space rule in kuery.peg function escapeWhitespace(str: string) { - return str - .replace(/\t/g, '\\t') - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n'); + return str.replace(/\t/g, '\\t').replace(/\r/g, '\\r').replace(/\n/g, '\\n'); } diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts index f7ffe1c2fec68..183ef2858a2b2 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts @@ -76,7 +76,7 @@ describe('Kuery operator suggestions', () => { expect(suggestions.length).toBeGreaterThan(0); - suggestions.forEach(suggestion => { + suggestions.forEach((suggestion) => { expect(suggestion).toHaveProperty('description'); }); }); diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx index 14c42d73f8d0b..47990db82f1a2 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx @@ -148,21 +148,21 @@ const getDescription = (operator: string) =>

{getOperatorByName(operator).des export const setupGetOperatorSuggestions: KqlQuerySuggestionProvider = () => { return ({ indexPatterns }, { end, fieldName, nestedPath }) => { const allFields = flatten( - indexPatterns.map(indexPattern => { + indexPatterns.map((indexPattern) => { return indexPattern.fields.slice(); }) ); const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; const fields = allFields - .filter(field => field.name === fullFieldName) - .map(field => { - const matchingOperators = Object.keys(operators).filter(operator => { + .filter((field) => field.name === fullFieldName) + .map((field) => { + const matchingOperators = Object.keys(operators).filter((operator) => { const { fieldTypes } = getOperatorByName(operator); return !fieldTypes || fieldTypes.includes(field.type); }); - const suggestions = matchingOperators.map(operator => ({ + const suggestions = matchingOperators.map((operator) => ({ type: QuerySuggestionTypes.Operator, text: operator + ' ', description: getDescription(operator), diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts index 03e1a9099f1ab..a8e2a83c57f75 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts @@ -12,7 +12,7 @@ export function sortPrefixFirst(array: any[], prefix?: string | number, property } const lowerCasePrefix = ('' + prefix).toLowerCase(); - const partitions = partition(array, entry => { + const partitions = partition(array, (entry) => { const value = ('' + (property ? entry[property] : entry)).toLowerCase(); return value.startsWith(lowerCasePrefix); diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts index bfd1e13ad9c39..6f9ba4d00109d 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -15,22 +15,22 @@ import { const wrapAsSuggestions = (start: number, end: number, query: string, values: string[]) => values - .filter(value => value.toLowerCase().includes(query.toLowerCase())) - .map(value => ({ + .filter((value) => value.toLowerCase().includes(query.toLowerCase())) + .map((value) => ({ type: QuerySuggestionTypes.Value, text: `${value} `, start, end, })); -export const setupGetValueSuggestions: KqlQuerySuggestionProvider = core => { +export const setupGetValueSuggestions: KqlQuerySuggestionProvider = (core) => { return async ( { indexPatterns, boolFilter, signal }, { start, end, prefix, suffix, fieldName, nestedPath } ): Promise => { const allFields = flatten( - indexPatterns.map(indexPattern => - indexPattern.fields.map(field => ({ + indexPatterns.map((indexPattern) => + indexPattern.fields.map((field) => ({ ...field, indexPattern, })) @@ -38,20 +38,20 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = core => { ); const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; - const fields = allFields.filter(field => field.name === fullFieldName); + const fields = allFields.filter((field) => field.name === fullFieldName); const query = `${prefix}${suffix}`.trim(); const { getValueSuggestions } = getAutocompleteService(); const data = await Promise.all( - fields.map(field => + fields.map((field) => getValueSuggestions({ indexPattern: field.indexPattern, field, query, boolFilter, signal, - }).then(valueSuggestions => { - const quotedValues = valueSuggestions.map(value => + }).then((valueSuggestions) => { + const quotedValues = valueSuggestions.map((value) => typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` ); diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts index c493e8ce86781..3a511c7b5a176 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts @@ -11,6 +11,7 @@ import { ISearchContext, ISearch, getEsPreference, + UI_SETTINGS, } from '../../../../../src/plugins/data/public'; import { IEnhancedEsSearchRequest, EnhancedSearchParams } from '../../common'; import { ASYNC_SEARCH_STRATEGY } from './async_search_strategy'; @@ -27,7 +28,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { const params: EnhancedSearchParams = { - ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'), + ignoreThrottled: !context.core.uiSettings.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN), preference: getEsPreference(context.core.uiSettings), ...request.params, }; diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index 1e554d3ff2d86..8df3114a8472a 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts @@ -11,7 +11,7 @@ import { CoreStart } from 'kibana/public'; jest.useFakeTimers(); -const flushPromises = () => new Promise(resolve => setImmediate(resolve)); +const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); const mockSearch = jest.fn(); let searchInterceptor: EnhancedSearchInterceptor; let mockCoreStart: MockedKeys; diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index 472d39179b468..a4cf324f9d475 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -34,7 +34,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { */ public runBeyondTimeout = () => { this.hideToast(); - this.timeoutSubscriptions.forEach(subscription => subscription.unsubscribe()); + this.timeoutSubscriptions.forEach((subscription) => subscription.unsubscribe()); this.timeoutSubscriptions.clear(); }; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index bf502889ffa4f..15f2ca10af7f7 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -45,7 +45,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = async id => { + const cancel: ISearchCancel = async (id) => { const method = 'DELETE'; const path = encodeURI(`/_async_search/${id}`); await caller('transport.request', { method, path }); diff --git a/x-pack/plugins/drilldowns/kibana.json b/x-pack/plugins/drilldowns/kibana.json index 678c054aa322c..1614f94b488fd 100644 --- a/x-pack/plugins/drilldowns/kibana.json +++ b/x-pack/plugins/drilldowns/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["uiActions", "embeddable", "advancedUiActions"], + "requiredPlugins": ["uiActions", "embeddable", "uiActionsEnhanced"], "configPath": ["xpack", "drilldowns"] } diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index a186feec33924..5fde4fc79e433 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -12,13 +12,13 @@ import { dashboardFactory, urlFactory, // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; +} from '../../../../ui_actions_enhanced/public/components/action_wizard/test_data'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; import { mockDynamicActionManager } from './test_data'; const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ - advancedUiActions: { + uiActionsEnhanced: { getActionFactories() { return [dashboardFactory, urlFactory]; }, diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index 152eaf18f16c1..32cbec795d092 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -11,7 +11,7 @@ import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldow import { dashboardFactory, urlFactory, -} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; +} from '../../../../ui_actions_enhanced/public/components/action_wizard/test_data'; import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { mockDynamicActionManager } from './test_data'; @@ -24,7 +24,7 @@ import { toastDrilldownsCRUDError } from './i18n'; const storage = new Storage(new StubBrowserStorage()); const notifications = coreMock.createStart().notifications; const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ - advancedUiActions: { + uiActionsEnhanced: { getActionFactories() { return [dashboardFactory, urlFactory]; }, @@ -136,7 +136,7 @@ test('Can delete multiple drilldowns', async () => { const checkboxes = screen.getAllByLabelText(/Select this drilldown/i); expect(checkboxes).toHaveLength(3); - checkboxes.forEach(checkbox => fireEvent.click(checkbox)); + checkboxes.forEach((checkbox) => fireEvent.click(checkbox)); expect(screen.queryByText(/Create/i)).not.toBeInTheDocument(); fireEvent.click(screen.getByText(/Delete \(3\)/i)); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index ba273e7d578ff..45cf7365ebd91 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -7,12 +7,12 @@ import React, { useEffect, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import { - AdvancedUiActionsActionFactory as ActionFactory, + UiActionsEnhancedActionFactory as ActionFactory, AdvancedUiActionsStart, UiActionsEnhancedDynamicActionManager as DynamicActionManager, UiActionsEnhancedSerializedAction, UiActionsEnhancedSerializedEvent, -} from '../../../../advanced_ui_actions/public'; +} from '../../../../ui_actions_enhanced/public'; import { NotificationsStart } from '../../../../../../src/core/public'; import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; @@ -48,17 +48,17 @@ enum Routes { } export function createFlyoutManageDrilldowns({ - advancedUiActions, + uiActionsEnhanced, storage, notifications, }: { - advancedUiActions: AdvancedUiActionsStart; + uiActionsEnhanced: AdvancedUiActionsStart; storage: IStorageWrapper; notifications: NotificationsStart; }) { // fine to assume this is static, // because all action factories should be registered in setup phase - const allActionFactories = advancedUiActions.getActionFactories(); + const allActionFactories = uiActionsEnhanced.getActionFactories(); const allActionFactoriesById = allActionFactories.reduce((acc, next) => { acc[next.id] = next; return acc; @@ -115,7 +115,7 @@ export function createFlyoutManageDrilldowns({ function resolveInitialDrilldownWizardConfig(): DrilldownWizardConfig | undefined { if (route !== Routes.Edit) return undefined; if (!currentEditId) return undefined; - const drilldownToEdit = drilldowns?.find(d => d.eventId === currentEditId); + const drilldownToEdit = drilldowns?.find((d) => d.eventId === currentEditId); if (!drilldownToEdit) return undefined; return { @@ -200,11 +200,11 @@ export function createFlyoutManageDrilldowns({ showWelcomeMessage={shouldShowWelcomeMessage} onWelcomeHideClick={onHideWelcomeMessage} drilldowns={drilldowns.map(mapToDrilldownToDrilldownListItem)} - onDelete={ids => { + onDelete={(ids) => { setCurrentEditId(null); deleteDrilldown(ids); }} - onEdit={id => { + onEdit={(id) => { setCurrentEditId(id); setRoute(Routes.Edit); }} @@ -228,7 +228,7 @@ function useCompatibleActionFactoriesForCurrentContext factory.isCompatible(context)) + actionFactories.map((factory) => factory.isCompatible(context)) ); if (canceled) return; setCompatibleActionFactories(actionFactories.filter((_, i) => compatibility[i])); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts index 47a04222286cb..d585fa0692e8c 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -9,7 +9,7 @@ import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, UiActionsEnhancedDynamicActionManagerState as DynamicActionManagerState, UiActionsEnhancedSerializedAction, -} from '../../../../advanced_ui_actions/public'; +} from '../../../../ui_actions_enhanced/public'; import { TriggerContextMapping } from '../../../../../../src/plugins/ui_actions/public'; import { createStateContainer } from '../../../../../../src/plugins/kibana_utils/common'; @@ -48,8 +48,8 @@ class MockDynamicActionManager implements PublicMethodsOf const state = this.state.get(); let events = state.events; - eventIds.forEach(id => { - events = events.filter(e => e.eventId !== id); + eventIds.forEach((id) => { + events = events.filter((e) => e.eventId !== id); }); this.state.set({ @@ -65,7 +65,7 @@ class MockDynamicActionManager implements PublicMethodsOf ) { const state = this.state.get(); const events = state.events; - const idx = events.findIndex(e => e.eventId === eventId); + const idx = events.findIndex((e) => e.eventId === eventId); const event = { eventId, action, diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx index add8b748afee9..be048bf920602 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx @@ -14,8 +14,8 @@ import { dashboardFactory, urlFactory, // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; -import { AdvancedUiActionsActionFactory as ActionFactory } from '../../../../advanced_ui_actions/public/'; +} from '../../../../ui_actions_enhanced/public/components/action_wizard/test_data'; +import { UiActionsEnhancedActionFactory as ActionFactory } from '../../../../ui_actions_enhanced/public/'; storiesOf('components/FlyoutDrilldownWizard', module) .add('default', () => { diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index 84c1a04a71d15..87f886817517f 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -16,7 +16,7 @@ import { txtEditDrilldownTitle, } from './i18n'; import { DrilldownHelloBar } from '../drilldown_hello_bar'; -import { AdvancedUiActionsActionFactory as ActionFactory } from '../../../../advanced_ui_actions/public'; +import { UiActionsEnhancedActionFactory as ActionFactory } from '../../../../ui_actions_enhanced/public'; export interface DrilldownWizardConfig { name: string; diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx index 3bed81a971921..1813851d728db 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n'; import { - AdvancedUiActionsActionFactory as ActionFactory, + UiActionsEnhancedActionFactory as ActionFactory, ActionWizard, -} from '../../../../advanced_ui_actions/public'; +} from '../../../../ui_actions_enhanced/public'; const noopFn = () => {}; @@ -45,7 +45,7 @@ export const FormDrilldownWizard: React.FC = ({ placeholder={txtUntitledDrilldown} value={name} disabled={onNameChange === noopFn} - onChange={event => onNameChange(event.target.value)} + onChange={(event) => onNameChange(event.target.value)} data-test-subj="drilldownNameInput" /> @@ -60,8 +60,8 @@ export const FormDrilldownWizard: React.FC = ({ actionFactories={actionFactories} currentActionFactory={currentActionFactory} config={actionConfig} - onActionFactoryChange={actionFactory => onActionFactoryChange(actionFactory)} - onConfigChange={config => onActionConfigChange(config)} + onActionFactoryChange={(actionFactory) => onActionFactoryChange(actionFactory)} + onConfigChange={(config) => onActionConfigChange(config)} context={actionFactoryContext} /> diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx index ab51c0a829ed3..cd41a3d6ec23a 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx @@ -92,8 +92,8 @@ export function ListManageDrilldowns({ isSelectable={true} responsive={false} selection={{ - onSelectionChange: selection => { - setSelectedDrilldowns(selection.map(drilldown => drilldown.id)); + onSelectionChange: (selection) => { + setSelectedDrilldowns(selection.map((drilldown) => drilldown.id)); }, selectableMessage: () => txtSelectDrilldown, }} diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index 0108e04df9c99..32176241c102f 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -6,18 +6,18 @@ import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; -import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; +import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../ui_actions_enhanced/public'; import { createFlyoutManageDrilldowns } from './components/connected_flyout_manage_drilldowns'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; export interface SetupDependencies { uiActions: UiActionsSetup; - advancedUiActions: AdvancedUiActionsSetup; + uiActionsEnhanced: AdvancedUiActionsSetup; } export interface StartDependencies { uiActions: UiActionsStart; - advancedUiActions: AdvancedUiActionsStart; + uiActionsEnhanced: AdvancedUiActionsStart; } // eslint-disable-next-line @@ -36,7 +36,7 @@ export class DrilldownsPlugin public start(core: CoreStart, plugins: StartDependencies): StartContract { return { FlyoutManageDrilldowns: createFlyoutManageDrilldowns({ - advancedUiActions: plugins.advancedUiActions, + uiActionsEnhanced: plugins.uiActionsEnhanced, storage: new Storage(localStorage), notifications: core.notifications, }), diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json index 780a1d5d89870..5663671de7bd9 100644 --- a/x-pack/plugins/embeddable_enhanced/kibana.json +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["embeddable", "advancedUiActions"] + "requiredPlugins": ["embeddable", "uiActionsEnhanced"] } diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index f8b3a9dfb92d0..5c5d98d75295d 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -9,7 +9,7 @@ import { EmbeddableActionStorage, EmbeddableWithDynamicActionsInput, } from './embeddable_action_storage'; -import { UiActionsEnhancedSerializedEvent } from '../../../advanced_ui_actions/public'; +import { UiActionsEnhancedSerializedEvent } from '../../../ui_actions_enhanced/public'; import { of } from '../../../../../src/plugins/kibana_utils/public'; class TestEmbeddable extends Embeddable { diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index dcb44323f6d11..fdc42585a80ce 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -7,7 +7,7 @@ import { UiActionsEnhancedAbstractActionStorage as AbstractActionStorage, UiActionsEnhancedSerializedEvent as SerializedEvent, -} from '../../../advanced_ui_actions/public'; +} from '../../../ui_actions_enhanced/public'; import { EmbeddableInput, EmbeddableOutput, @@ -78,7 +78,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async remove(eventId: string) { const input = this.embbeddable.getInput(); const events = input.enhancements?.dynamicActions?.events || []; - const index = events.findIndex(event => eventId === event.eventId); + const index = events.findIndex((event) => eventId === event.eventId); if (index === -1) { throw new Error( @@ -94,7 +94,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async read(eventId: string): Promise { const input = this.embbeddable.getInput(); const events = input.enhancements?.dynamicActions?.events || []; - const event = events.find(ev => eventId === ev.eventId); + const event = events.find((ev) => eventId === ev.eventId); if (!event) { throw new Error( diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index d48c4f9e860cc..e6413ac03aeae 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -27,7 +27,7 @@ import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsSetup, AdvancedUiActionsStart, -} from '../../advanced_ui_actions/public'; +} from '../../ui_actions_enhanced/public'; import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions'; declare module '../../../../src/plugins/ui_actions/public' { @@ -38,12 +38,12 @@ declare module '../../../../src/plugins/ui_actions/public' { export interface SetupDependencies { embeddable: EmbeddableSetup; - advancedUiActions: AdvancedUiActionsSetup; + uiActionsEnhanced: AdvancedUiActionsSetup; } export interface StartDependencies { embeddable: EmbeddableStart; - advancedUiActions: AdvancedUiActionsStart; + uiActionsEnhanced: AdvancedUiActionsStart; } // eslint-disable-next-line @@ -56,20 +56,20 @@ export class EmbeddableEnhancedPlugin implements Plugin { constructor(protected readonly context: PluginInitializerContext) {} - private uiActions?: StartDependencies['advancedUiActions']; + private uiActions?: StartDependencies['uiActionsEnhanced']; public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); const panelNotificationAction = new PanelNotificationsAction(); - plugins.advancedUiActions.registerAction(panelNotificationAction); - plugins.advancedUiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id); + plugins.uiActionsEnhanced.registerAction(panelNotificationAction); + plugins.uiActionsEnhanced.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id); return {}; } public start(core: CoreStart, plugins: StartDependencies): StartContract { - this.uiActions = plugins.advancedUiActions; + this.uiActions = plugins.uiActionsEnhanced; return {}; } @@ -126,7 +126,7 @@ export class EmbeddableEnhancedPlugin uiActions: this.uiActions!, }); - dynamicActions.start().catch(error => { + dynamicActions.start().catch((error) => { /* eslint-disable */ console.log('Failed to start embeddable dynamic actions', embeddable); console.error(error); @@ -134,7 +134,7 @@ export class EmbeddableEnhancedPlugin }); const stop = () => { - dynamicActions.stop().catch(error => { + dynamicActions.stop().catch((error) => { /* eslint-disable */ console.log('Failed to stop embeddable dynamic actions', embeddable); console.error(error); diff --git a/x-pack/plugins/embeddable_enhanced/public/types.ts b/x-pack/plugins/embeddable_enhanced/public/types.ts index 924605be332b2..4f5c316f2fc17 100644 --- a/x-pack/plugins/embeddable_enhanced/public/types.ts +++ b/x-pack/plugins/embeddable_enhanced/public/types.ts @@ -5,7 +5,7 @@ */ import { IEmbeddable } from '../../../../src/plugins/embeddable/public'; -import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../ui_actions_enhanced/public'; export type EnhancedEmbeddable = E & { enhancements: { diff --git a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.test.ts b/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.test.ts index 760c4ef01b31c..1d1396fd520d1 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.test.ts @@ -9,7 +9,7 @@ import { mockAuthenticatedUser } from '../../../security/common/model/authentica it('properly logs audit events', () => { const mockInternalAuditLogger = { log: jest.fn() }; - const audit = new EncryptedSavedObjectsAuditLogger(() => mockInternalAuditLogger); + const audit = new EncryptedSavedObjectsAuditLogger(mockInternalAuditLogger); audit.encryptAttributesSuccess(['one', 'two'], { type: 'known-type', diff --git a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.ts b/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.ts index 1a10dd343d43d..de14a79dd0ddb 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.ts @@ -4,22 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AuditLogger, AuthenticatedUser } from '../../../security/server'; import { SavedObjectDescriptor, descriptorToArray } from '../crypto'; -import { LegacyAPI } from '../plugin'; -import { AuthenticatedUser } from '../../../security/common/model'; /** * Represents all audit events the plugin can log. */ export class EncryptedSavedObjectsAuditLogger { - constructor(private readonly getAuditLogger: () => LegacyAPI['auditLogger']) {} + constructor(private readonly logger: AuditLogger = { log() {} }) {} public encryptAttributeFailure( attributeName: string, descriptor: SavedObjectDescriptor, user?: AuthenticatedUser ) { - this.getAuditLogger().log( + this.logger.log( 'encrypt_failure', `Failed to encrypt attribute "${attributeName}" for saved object "[${descriptorToArray( descriptor @@ -33,7 +32,7 @@ export class EncryptedSavedObjectsAuditLogger { descriptor: SavedObjectDescriptor, user?: AuthenticatedUser ) { - this.getAuditLogger().log( + this.logger.log( 'decrypt_failure', `Failed to decrypt attribute "${attributeName}" for saved object "[${descriptorToArray( descriptor @@ -47,7 +46,7 @@ export class EncryptedSavedObjectsAuditLogger { descriptor: SavedObjectDescriptor, user?: AuthenticatedUser ) { - this.getAuditLogger().log( + this.logger.log( 'encrypt_success', `Successfully encrypted attributes "[${attributesNames}]" for saved object "[${descriptorToArray( descriptor @@ -61,7 +60,7 @@ export class EncryptedSavedObjectsAuditLogger { descriptor: SavedObjectDescriptor, user?: AuthenticatedUser ) { - this.getAuditLogger().log( + this.logger.log( 'decrypt_success', `Successfully decrypted attributes "[${attributesNames}]" for saved object "[${descriptorToArray( descriptor diff --git a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts index 8f74c461a2a9b..db07f0f9ce2c0 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts @@ -54,9 +54,7 @@ describe('createConfig$()', () => { mockRandomBytes.mockReturnValue('ab'.repeat(16)); const contextMock = coreMock.createPluginInitializerContext({}); - const config = await createConfig$(contextMock) - .pipe(first()) - .toPromise(); + const config = await createConfig$(contextMock).pipe(first()).toPromise(); expect(config).toEqual({ config: { encryptionKey: 'ab'.repeat(16) }, usingEphemeralEncryptionKey: true, @@ -75,9 +73,7 @@ describe('createConfig$()', () => { const contextMock = coreMock.createPluginInitializerContext({ encryptionKey: 'supersecret', }); - const config = await createConfig$(contextMock) - .pipe(first()) - .toPromise(); + const config = await createConfig$(contextMock).pipe(first()).toPromise(); expect(config).toEqual({ config: { encryptionKey: 'supersecret' }, usingEphemeralEncryptionKey: false, diff --git a/x-pack/plugins/encrypted_saved_objects/server/config.ts b/x-pack/plugins/encrypted_saved_objects/server/config.ts index 2f01850520724..9c751a9c67f52 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/config.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/config.ts @@ -21,7 +21,7 @@ export const ConfigSchema = schema.object({ export function createConfig$(context: PluginInitializerContext) { return context.config.create>().pipe( - map(config => { + map((config) => { const logger = context.logger.get('config'); let encryptionKey = config.encryptionKey; diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/index.mock.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/index.mock.ts index de0a089ab8c66..11a0cd6f33307 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/index.mock.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/index.mock.ts @@ -21,7 +21,7 @@ export const encryptedSavedObjectsServiceMock = { attrs: T, action: (attrs: T, attrName: string, shouldExpose: boolean) => void ) { - const registration = registrations.find(r => r.type === descriptor.type); + const registration = registrations.find((r) => r.type === descriptor.type); if (!registration) { return attrs; } @@ -40,7 +40,7 @@ export const encryptedSavedObjectsServiceMock = { } mock.isRegistered.mockImplementation( - type => registrations.findIndex(r => r.type === type) >= 0 + (type) => registrations.findIndex((r) => r.type === type) >= 0 ); mock.encryptAttributes.mockImplementation(async (descriptor, attrs) => processAttributes( diff --git a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts index d5550703cf761..38ac8f254315e 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts @@ -18,7 +18,7 @@ function createEncryptedSavedObjectsSetupMock() { function createEncryptedSavedObjectsStartMock() { return { isEncryptionError: jest.fn(), - getClient: jest.fn(opts => createEncryptedSavedObjectsClienttMock(opts)), + getClient: jest.fn((opts) => createEncryptedSavedObjectsClienttMock(opts)), } as jest.Mocked; } diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts index e8568e9964c2f..4afd74488f9fe 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts @@ -16,9 +16,6 @@ describe('EncryptedSavedObjects Plugin', () => { await expect(plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() })) .resolves.toMatchInlineSnapshot(` Object { - "__legacyCompat": Object { - "registerLegacyAPI": [Function], - }, "registerType": [Function], "usingEphemeralEncryptionKey": true, } diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts index 83b412de5db7e..cdbdd18b9d696 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts @@ -22,7 +22,6 @@ export interface PluginsSetup { export interface EncryptedSavedObjectsPluginSetup { registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => void; - __legacyCompat: { registerLegacyAPI: (legacyAPI: LegacyAPI) => void }; usingEphemeralEncryptionKey: boolean; } @@ -31,16 +30,6 @@ export interface EncryptedSavedObjectsPluginStart { getClient: ClientInstanciator; } -/** - * Describes a set of APIs that is available in the legacy platform only and required by this plugin - * to function properly. - */ -export interface LegacyAPI { - auditLogger: { - log: (eventType: string, message: string, data?: Record) => void; - }; -} - /** * Represents EncryptedSavedObjects Plugin instance that will be managed by the Kibana plugin system. */ @@ -48,14 +37,6 @@ export class Plugin { private readonly logger: Logger; private savedObjectsSetup!: ClientInstanciator; - private legacyAPI?: LegacyAPI; - private readonly getLegacyAPI = () => { - if (!this.legacyAPI) { - throw new Error('Legacy API is not registered!'); - } - return this.legacyAPI; - }; - constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); } @@ -72,7 +53,9 @@ export class Plugin { new EncryptedSavedObjectsService( config.encryptionKey, this.logger, - new EncryptedSavedObjectsAuditLogger(() => this.getLegacyAPI().auditLogger) + new EncryptedSavedObjectsAuditLogger( + deps.security?.audit.getLogger('encryptedSavedObjects') + ) ) ); @@ -86,7 +69,6 @@ export class Plugin { return { registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => service.registerType(typeRegistration), - __legacyCompat: { registerLegacyAPI: (legacyAPI: LegacyAPI) => (this.legacyAPI = legacyAPI) }, usingEphemeralEncryptionKey, }; } diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts index 8a4e78288e411..7098f611defa0 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts @@ -450,7 +450,7 @@ describe('#bulkUpdate', () => { ]; const mockedResponse = { - saved_objects: docs.map(doc => ({ + saved_objects: docs.map((doc) => ({ ...doc, attributes: { ...doc.attributes, @@ -465,7 +465,7 @@ describe('#bulkUpdate', () => { await expect( wrapper.bulkUpdate( - docs.map(doc => ({ ...doc })), + docs.map((doc) => ({ ...doc })), {} ) ).resolves.toEqual({ @@ -558,7 +558,7 @@ describe('#bulkUpdate', () => { const options = { namespace }; mockBaseClient.bulkUpdate.mockResolvedValue({ - saved_objects: docs.map(doc => ({ ...doc, references: undefined })), + saved_objects: docs.map((doc) => ({ ...doc, references: undefined })), }); await expect(wrapper.bulkUpdate(docs, options)).resolves.toEqual({ diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts index 7ba8ef3eaad8b..bdc2b6cb2e667 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts @@ -94,7 +94,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon // NodeJS thread pool. If it turns out to be a problem, we can consider switching to the // sequential processing. const encryptedObjects = await Promise.all( - objects.map(async object => { + objects.map(async (object) => { if (!this.options.service.isRegistered(object.type)) { return object; } @@ -137,7 +137,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon // NodeJS thread pool. If it turns out to be a problem, we can consider switching to the // sequential processing. const encryptedObjects = await Promise.all( - objects.map(async object => { + objects.map(async (object) => { const { type, id, attributes } = object; if (!this.options.service.isRegistered(type)) { return object; diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts index 9ab3e85cc8624..af00050183b77 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts @@ -62,7 +62,7 @@ export function setupSavedObjects({ }) ); - return clientOpts => { + return (clientOpts) => { const internalRepositoryAndTypeRegistryPromise = getStartServices().then( ([core]) => [ diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index 66c16d0ddf383..0144e573fc146 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -301,9 +301,7 @@ describe('queryEventsBySavedObject', () => { }, }); - const start = moment() - .subtract(1, 'days') - .toISOString(); + const start = moment().subtract(1, 'days').toISOString(); await clusterClientAdapter.queryEventsBySavedObject( 'index-name', @@ -374,12 +372,8 @@ describe('queryEventsBySavedObject', () => { }, }); - const start = moment() - .subtract(1, 'days') - .toISOString(); - const end = moment() - .add(1, 'days') - .toISOString(); + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().add(1, 'days').toISOString(); await clusterClientAdapter.queryEventsBySavedObject( 'index-name', diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index c0ff87234c09d..7fd239ca49369 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -206,7 +206,7 @@ export class ClusterClientAdapter { page, per_page: perPage, total, - data: hits.map(hit => hit._source) as IEvent[], + data: hits.map((hit) => hit._source) as IEvent[], }; } catch (err) { throw new Error( diff --git a/x-pack/plugins/event_log/server/es/documents.test.ts b/x-pack/plugins/event_log/server/es/documents.test.ts index c08d0ac978bc9..2feb6e3b84f91 100644 --- a/x-pack/plugins/event_log/server/es/documents.test.ts +++ b/x-pack/plugins/event_log/server/es/documents.test.ts @@ -24,7 +24,7 @@ describe('getIndexTemplate()', () => { const indexTemplate = getIndexTemplate(esNames); expect(indexTemplate.index_patterns).toEqual([esNames.indexPatternWithVersion]); expect(indexTemplate.settings.number_of_shards).toBeGreaterThanOrEqual(0); - expect(indexTemplate.settings.number_of_replicas).toBeGreaterThanOrEqual(0); + expect(indexTemplate.settings.auto_expand_replicas).toBe('0-1'); expect(indexTemplate.settings['index.lifecycle.name']).toBe(esNames.ilmPolicy); expect(indexTemplate.settings['index.lifecycle.rollover_alias']).toBe(esNames.alias); expect(indexTemplate.mappings).toMatchObject({}); diff --git a/x-pack/plugins/event_log/server/es/documents.ts b/x-pack/plugins/event_log/server/es/documents.ts index 91b3db554964f..0dc7301568207 100644 --- a/x-pack/plugins/event_log/server/es/documents.ts +++ b/x-pack/plugins/event_log/server/es/documents.ts @@ -13,7 +13,7 @@ export function getIndexTemplate(esNames: EsNames) { index_patterns: [esNames.indexPatternWithVersion], settings: { number_of_shards: 1, - number_of_replicas: 1, + auto_expand_replicas: '0-1', 'index.lifecycle.name': esNames.ilmPolicy, 'index.lifecycle.rollover_alias': esNames.alias, }, diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts index 17c073c4b27f9..16e5fa69d36f6 100644 --- a/x-pack/plugins/event_log/server/event_log_client.test.ts +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -172,12 +172,8 @@ describe('EventLogStart', () => { }; esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result); - const start = moment() - .subtract(1, 'days') - .toISOString(); - const end = moment() - .add(1, 'days') - .toISOString(); + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().add(1, 'days').toISOString(); expect( await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { diff --git a/x-pack/plugins/event_log/server/lib/delay.ts b/x-pack/plugins/event_log/server/lib/delay.ts index a37569baaf692..1c64141c15fb2 100644 --- a/x-pack/plugins/event_log/server/lib/delay.ts +++ b/x-pack/plugins/event_log/server/lib/delay.ts @@ -5,5 +5,5 @@ */ export async function delay(millis: number) { - await new Promise(resolve => setTimeout(resolve, millis)); + await new Promise((resolve) => setTimeout(resolve, millis)); } diff --git a/x-pack/plugins/event_log/server/lib/ready_signal.test.ts b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts index 6f1d92034c06f..c216651ee94b1 100644 --- a/x-pack/plugins/event_log/server/lib/ready_signal.test.ts +++ b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts @@ -13,7 +13,7 @@ describe('ReadySignal', () => { readySignal = createReadySignal(); }); - test('works as expected', async done => { + test('works as expected', async (done) => { let value = 41; timeoutSet(100, async () => { diff --git a/x-pack/plugins/event_log/server/lib/ready_signal.ts b/x-pack/plugins/event_log/server/lib/ready_signal.ts index 2ea8e655089da..58879649b83cb 100644 --- a/x-pack/plugins/event_log/server/lib/ready_signal.ts +++ b/x-pack/plugins/event_log/server/lib/ready_signal.ts @@ -12,7 +12,7 @@ export interface ReadySignal { export function createReadySignal(): ReadySignal { let resolver: (value: T) => void; - const promise = new Promise(resolve => { + const promise = new Promise((resolve) => { resolver = resolve; }); diff --git a/x-pack/plugins/event_log/server/routes/find.ts b/x-pack/plugins/event_log/server/routes/find.ts index f8e1c842ae436..e03aef7c757f6 100644 --- a/x-pack/plugins/event_log/server/routes/find.ts +++ b/x-pack/plugins/event_log/server/routes/find.ts @@ -29,7 +29,7 @@ export const findRoute = (router: IRouter) => { query: findOptionsSchema, }, }, - router.handleLegacyErrors(async function( + router.handleLegacyErrors(async function ( context: RequestHandlerContext, req: KibanaRequest, FindOptionsType, unknown>, res: KibanaResponseFactory diff --git a/x-pack/plugins/features/common/feature.ts b/x-pack/plugins/features/common/feature.ts index ef32a8a80a0bd..1b405094d9eda 100644 --- a/x-pack/plugins/features/common/feature.ts +++ b/x-pack/plugins/features/common/feature.ts @@ -133,7 +133,7 @@ export class Feature { constructor(protected readonly config: RecursiveReadonly) { this.subFeatures = (config.subFeatures ?? []).map( - subFeatureConfig => new SubFeature(subFeatureConfig) + (subFeatureConfig) => new SubFeature(subFeatureConfig) ); } diff --git a/x-pack/plugins/features/public/features_api_client.test.ts b/x-pack/plugins/features/public/features_api_client.test.ts index e3a25ad57425c..95f0c2d527d34 100644 --- a/x-pack/plugins/features/public/features_api_client.test.ts +++ b/x-pack/plugins/features/public/features_api_client.test.ts @@ -32,7 +32,7 @@ describe('Features API Client', () => { const client = new FeaturesAPIClient(coreSetup.http); const result = await client.getFeatures(); - expect(result.map(f => f.id)).toEqual([ + expect(result.map((f) => f.id)).toEqual([ 'feature-a', 'feature-b', 'feature-c', diff --git a/x-pack/plugins/features/public/features_api_client.ts b/x-pack/plugins/features/public/features_api_client.ts index b93c9bf917d79..50cc54a197f56 100644 --- a/x-pack/plugins/features/public/features_api_client.ts +++ b/x-pack/plugins/features/public/features_api_client.ts @@ -12,6 +12,6 @@ export class FeaturesAPIClient { public async getFeatures() { const features = await this.http.get('/api/features'); - return features.map(config => new Feature(config)); + return features.map((config) => new Feature(config)); } } diff --git a/x-pack/plugins/features/server/feature_registry.test.ts b/x-pack/plugins/features/server/feature_registry.test.ts index 2039f8f6acda2..75022922917b3 100644 --- a/x-pack/plugins/features/server/feature_registry.test.ts +++ b/x-pack/plugins/features/server/feature_registry.test.ts @@ -353,7 +353,7 @@ describe('FeatureRegistry', () => { ); }); - ['contains space', 'contains_invalid()_chars', ''].forEach(prohibitedChars => { + ['contains space', 'contains_invalid()_chars', ''].forEach((prohibitedChars) => { it(`prevents features from being registered with a navLinkId of "${prohibitedChars}"`, () => { const featureRegistry = new FeatureRegistry(); expect(() => @@ -396,7 +396,7 @@ describe('FeatureRegistry', () => { }); }); - ['catalogue', 'management', 'navLinks', `doesn't match valid regex`].forEach(prohibitedId => { + ['catalogue', 'management', 'navLinks', `doesn't match valid regex`].forEach((prohibitedId) => { it(`prevents features from being registered with an ID of "${prohibitedId}"`, () => { const featureRegistry = new FeatureRegistry(); expect(() => diff --git a/x-pack/plugins/features/server/feature_registry.ts b/x-pack/plugins/features/server/feature_registry.ts index 6140b7ac87ce0..12aafd226f754 100644 --- a/x-pack/plugins/features/server/feature_registry.ts +++ b/x-pack/plugins/features/server/feature_registry.ts @@ -32,14 +32,14 @@ export class FeatureRegistry { public getAll(): Feature[] { this.locked = true; - return Object.values(this.features).map(featureConfig => new Feature(featureConfig)); + return Object.values(this.features).map((featureConfig) => new Feature(featureConfig)); } } function applyAutomaticPrivilegeGrants(feature: FeatureConfig): FeatureConfig { const allPrivilege = feature.privileges?.all; const readPrivilege = feature.privileges?.read; - const reservedPrivileges = (feature.reserved?.privileges ?? []).map(rp => rp.privilege); + const reservedPrivileges = (feature.reserved?.privileges ?? []).map((rp) => rp.privilege); applyAutomaticAllPrivilegeGrants(allPrivilege, ...reservedPrivileges); applyAutomaticReadPrivilegeGrants(readPrivilege); @@ -50,7 +50,7 @@ function applyAutomaticPrivilegeGrants(feature: FeatureConfig): FeatureConfig { function applyAutomaticAllPrivilegeGrants( ...allPrivileges: Array ) { - allPrivileges.forEach(allPrivilege => { + allPrivileges.forEach((allPrivilege) => { if (allPrivilege) { allPrivilege.savedObject.all = uniq([...allPrivilege.savedObject.all, 'telemetry']); allPrivilege.savedObject.read = uniq([...allPrivilege.savedObject.read, 'config', 'url']); @@ -61,7 +61,7 @@ function applyAutomaticAllPrivilegeGrants( function applyAutomaticReadPrivilegeGrants( ...readPrivileges: Array ) { - readPrivileges.forEach(readPrivilege => { + readPrivileges.forEach((readPrivilege) => { if (readPrivilege) { readPrivilege.savedObject.read = uniq([...readPrivilege.savedObject.read, 'config', 'url']); } diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index 403d9586bf160..7497548cf8904 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -34,53 +34,33 @@ const privilegeSchema = Joi.object({ api: Joi.array().items(Joi.string()), app: Joi.array().items(Joi.string()), savedObject: Joi.object({ - all: Joi.array() - .items(Joi.string()) - .required(), - read: Joi.array() - .items(Joi.string()) - .required(), + all: Joi.array().items(Joi.string()).required(), + read: Joi.array().items(Joi.string()).required(), }).required(), - ui: Joi.array() - .items(Joi.string().regex(uiCapabilitiesRegex)) - .required(), + ui: Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)).required(), }); const subFeaturePrivilegeSchema = Joi.object({ - id: Joi.string() - .regex(subFeaturePrivilegePartRegex) - .required(), + id: Joi.string().regex(subFeaturePrivilegePartRegex).required(), name: Joi.string().required(), - includeIn: Joi.string() - .allow('all', 'read', 'none') - .required(), + includeIn: Joi.string().allow('all', 'read', 'none').required(), management: managementSchema, catalogue: catalogueSchema, api: Joi.array().items(Joi.string()), app: Joi.array().items(Joi.string()), savedObject: Joi.object({ - all: Joi.array() - .items(Joi.string()) - .required(), - read: Joi.array() - .items(Joi.string()) - .required(), + all: Joi.array().items(Joi.string()).required(), + read: Joi.array().items(Joi.string()).required(), }).required(), - ui: Joi.array() - .items(Joi.string().regex(uiCapabilitiesRegex)) - .required(), + ui: Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)).required(), }); const subFeatureSchema = Joi.object({ name: Joi.string().required(), privilegeGroups: Joi.array().items( Joi.object({ - groupType: Joi.string() - .valid('mutually_exclusive', 'independent') - .required(), - privileges: Joi.array() - .items(subFeaturePrivilegeSchema) - .min(1), + groupType: Joi.string().valid('mutually_exclusive', 'independent').required(), + privileges: Joi.array().items(subFeaturePrivilegeSchema).min(1), }) ), }); @@ -99,9 +79,7 @@ const schema = Joi.object({ icon: Joi.string(), description: Joi.string(), navLinkId: Joi.string().regex(uiCapabilitiesRegex), - app: Joi.array() - .items(Joi.string()) - .required(), + app: Joi.array().items(Joi.string()).required(), management: managementSchema, catalogue: catalogueSchema, privileges: Joi.object({ @@ -112,9 +90,7 @@ const schema = Joi.object({ .required(), subFeatures: Joi.when('privileges', { is: null, - then: Joi.array() - .items(subFeatureSchema) - .max(0), + then: Joi.array().items(subFeatureSchema).max(0), otherwise: Joi.array().items(subFeatureSchema), }), privilegesTooltip: Joi.string(), @@ -123,9 +99,7 @@ const schema = Joi.object({ privileges: Joi.array() .items( Joi.object({ - id: Joi.string() - .regex(reservedFeaturePrrivilegePartRegex) - .required(), + id: Joi.string().regex(reservedFeaturePrrivilegePartRegex).required(), privilege: privilegeSchema.required(), }) ) @@ -143,7 +117,7 @@ export function validateFeature(feature: FeatureConfig) { const unseenApps = new Set(app); - const managementSets = Object.entries(management).map(entry => [ + const managementSets = Object.entries(management).map((entry) => [ entry[0], new Set(entry[1]), ]) as Array<[string, Set]>; @@ -153,7 +127,7 @@ export function validateFeature(feature: FeatureConfig) { const unseenCatalogue = new Set(catalogue); function validateAppEntry(privilegeId: string, entry: string[] = []) { - entry.forEach(privilegeApp => unseenApps.delete(privilegeApp)); + entry.forEach((privilegeApp) => unseenApps.delete(privilegeApp)); const unknownAppEntries = difference(entry, app); if (unknownAppEntries.length > 0) { @@ -166,7 +140,7 @@ export function validateFeature(feature: FeatureConfig) { } function validateCatalogueEntry(privilegeId: string, entry: string[] = []) { - entry.forEach(privilegeCatalogue => unseenCatalogue.delete(privilegeCatalogue)); + entry.forEach((privilegeCatalogue) => unseenCatalogue.delete(privilegeCatalogue)); const unknownCatalogueEntries = difference(entry || [], catalogue); if (unknownCatalogueEntries.length > 0) { @@ -184,7 +158,7 @@ export function validateFeature(feature: FeatureConfig) { ) { Object.entries(managementEntry).forEach(([managementSectionId, managementSectionEntry]) => { if (unseenManagement.has(managementSectionId)) { - managementSectionEntry.forEach(entry => { + managementSectionEntry.forEach((entry) => { unseenManagement.get(managementSectionId)!.delete(entry); if (unseenManagement.get(managementSectionId)?.size === 0) { unseenManagement.delete(managementSectionId); @@ -219,7 +193,7 @@ export function validateFeature(feature: FeatureConfig) { privilegeEntries.push(...Object.entries(feature.privileges)); } if (feature.reserved) { - feature.reserved.privileges.forEach(reservedPrivilege => { + feature.reserved.privileges.forEach((reservedPrivilege) => { privilegeEntries.push([reservedPrivilege.id, reservedPrivilege.privilege]); }); } @@ -241,9 +215,9 @@ export function validateFeature(feature: FeatureConfig) { }); const subFeatureEntries = feature.subFeatures ?? []; - subFeatureEntries.forEach(subFeature => { - subFeature.privilegeGroups.forEach(subFeaturePrivilegeGroup => { - subFeaturePrivilegeGroup.privileges.forEach(subFeaturePrivilege => { + subFeatureEntries.forEach((subFeature) => { + subFeature.privilegeGroups.forEach((subFeaturePrivilegeGroup) => { + subFeaturePrivilegeGroup.privileges.forEach((subFeaturePrivilege) => { validateAppEntry(subFeaturePrivilege.id, subFeaturePrivilege.app); validateCatalogueEntry(subFeaturePrivilege.id, subFeaturePrivilege.catalogue); validateManagementEntry(subFeaturePrivilege.id, subFeaturePrivilege.management); @@ -274,7 +248,7 @@ export function validateFeature(feature: FeatureConfig) { if (unseenManagement.size > 0) { const ungrantedManagement = Array.from(unseenManagement.entries()).reduce((acc, entry) => { const values = Array.from(entry[1].values()).map( - managementPage => `${entry[0]}.${managementPage}` + (managementPage) => `${entry[0]}.${managementPage}` ); return [...acc, ...values]; }, [] as string[]); diff --git a/x-pack/plugins/features/server/oss_features.test.ts b/x-pack/plugins/features/server/oss_features.test.ts index 72beff02173d2..c38f2afc88389 100644 --- a/x-pack/plugins/features/server/oss_features.test.ts +++ b/x-pack/plugins/features/server/oss_features.test.ts @@ -11,7 +11,7 @@ import { Feature } from '.'; describe('buildOSSFeatures', () => { it('returns features including timelion', () => { expect( - buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: true }).map(f => f.id) + buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: true }).map((f) => f.id) ).toMatchInlineSnapshot(` Array [ "discover", @@ -28,7 +28,9 @@ Array [ it('returns features excluding timelion', () => { expect( - buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: false }).map(f => f.id) + buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: false }).map( + (f) => f.id + ) ).toMatchInlineSnapshot(` Array [ "discover", @@ -43,7 +45,7 @@ Array [ }); const features = buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: true }); - features.forEach(featureConfig => { + features.forEach((featureConfig) => { it(`returns the ${featureConfig.id} feature augmented with appropriate sub feature privileges`, () => { const privileges = []; for (const featurePrivilege of featurePrivilegeIterator(new Feature(featureConfig), { diff --git a/x-pack/plugins/features/server/plugin.test.ts b/x-pack/plugins/features/server/plugin.test.ts index 3d7cf19e58b0e..79fd012337b00 100644 --- a/x-pack/plugins/features/server/plugin.test.ts +++ b/x-pack/plugins/features/server/plugin.test.ts @@ -39,7 +39,7 @@ describe('Features Plugin', () => { const { getFeatures } = await plugin.start(coreStart); - expect(getFeatures().map(f => f.id)).toMatchInlineSnapshot(` + expect(getFeatures().map((f) => f.id)).toMatchInlineSnapshot(` Array [ "baz", "discover", @@ -67,7 +67,7 @@ describe('Features Plugin', () => { const { getFeatures } = await plugin.start(coreStart); - expect(getFeatures().map(f => f.id)).toMatchInlineSnapshot(` + expect(getFeatures().map((f) => f.id)).toMatchInlineSnapshot(` Array [ "baz", "discover", @@ -88,8 +88,8 @@ describe('Features Plugin', () => { const { getFeatures } = await plugin.start(coreStart); const soTypes = - getFeatures().find(f => f.id === 'savedObjectsManagement')?.privileges?.all.savedObject.all || - []; + getFeatures().find((f) => f.id === 'savedObjectsManagement')?.privileges?.all.savedObject + .all || []; expect(soTypes.includes('foo')).toBe(true); expect(soTypes.includes('bar')).toBe(false); diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts index e3480eda9fe7d..bfae416471c2f 100644 --- a/x-pack/plugins/features/server/plugin.ts +++ b/x-pack/plugins/features/server/plugin.ts @@ -83,8 +83,8 @@ export class Plugin { const registry = savedObjects.getTypeRegistry(); const savedObjectTypes = registry .getAllTypes() - .filter(t => !t.hidden) - .map(t => t.name); + .filter((t) => !t.hidden) + .map((t) => t.name); this.logger.debug( `Registering OSS features with SO types: ${savedObjectTypes.join(', ')}. "includeTimelion": ${ diff --git a/x-pack/plugins/features/server/routes/index.test.ts b/x-pack/plugins/features/server/routes/index.test.ts index 67b28b27f931f..c2e8cd6129d80 100644 --- a/x-pack/plugins/features/server/routes/index.test.ts +++ b/x-pack/plugins/features/server/routes/index.test.ts @@ -74,7 +74,7 @@ describe('GET /api/features', () => { const [call] = mockResponse.ok.mock.calls; const body = call[0]!.body as FeatureConfig[]; - const features = body.map(feature => ({ id: feature.id, order: feature.order })); + const features = body.map((feature) => ({ id: feature.id, order: feature.order })); expect(features).toEqual([ { id: 'feature_3', @@ -103,7 +103,7 @@ describe('GET /api/features', () => { const [call] = mockResponse.ok.mock.calls; const body = call[0]!.body as FeatureConfig[]; - const features = body.map(feature => ({ id: feature.id, order: feature.order })); + const features = body.map((feature) => ({ id: feature.id, order: feature.order })); expect(features).toEqual([ { @@ -133,7 +133,7 @@ describe('GET /api/features', () => { const [call] = mockResponse.ok.mock.calls; const body = call[0]!.body as FeatureConfig[]; - const features = body.map(feature => ({ id: feature.id, order: feature.order })); + const features = body.map((feature) => ({ id: feature.id, order: feature.order })); expect(features).toEqual([ { @@ -163,7 +163,7 @@ describe('GET /api/features', () => { const [call] = mockResponse.ok.mock.calls; const body = call[0]!.body as FeatureConfig[]; - const features = body.map(feature => ({ id: feature.id, order: feature.order })); + const features = body.map((feature) => ({ id: feature.id, order: feature.order })); expect(features).toEqual([ { diff --git a/x-pack/plugins/features/server/routes/index.ts b/x-pack/plugins/features/server/routes/index.ts index d07b488693091..147d34d124fca 100644 --- a/x-pack/plugins/features/server/routes/index.ts +++ b/x-pack/plugins/features/server/routes/index.ts @@ -31,7 +31,7 @@ export function defineRoutes({ router, featureRegistry }: RouteDefinitionParams) return response.ok({ body: allFeatures .filter( - feature => + (feature) => request.query.ignoreValidLicenses || !feature.validLicenses || !feature.validLicenses.length || @@ -42,7 +42,7 @@ export function defineRoutes({ router, featureRegistry }: RouteDefinitionParams) (f1, f2) => (f1.order ?? Number.MAX_SAFE_INTEGER) - (f2.order ?? Number.MAX_SAFE_INTEGER) ) - .map(feature => feature.toRaw()), + .map((feature) => feature.toRaw()), }); } ); diff --git a/x-pack/plugins/features/server/ui_capabilities_for_features.ts b/x-pack/plugins/features/server/ui_capabilities_for_features.ts index e6ff3ad4383d2..e41035e9365ce 100644 --- a/x-pack/plugins/features/server/ui_capabilities_for_features.ts +++ b/x-pack/plugins/features/server/ui_capabilities_for_features.ts @@ -42,14 +42,14 @@ function getCapabilitiesFromFeature(feature: Feature): FeatureCapabilities { const featurePrivileges = Object.values(feature.privileges ?? {}); if (feature.subFeatures) { featurePrivileges.push( - ...feature.subFeatures.map(sf => sf.privilegeGroups.map(pg => pg.privileges)).flat(2) + ...feature.subFeatures.map((sf) => sf.privilegeGroups.map((pg) => pg.privileges)).flat(2) ); } if (feature.reserved?.privileges) { - featurePrivileges.push(...feature.reserved.privileges.map(rp => rp.privilege)); + featurePrivileges.push(...feature.reserved.privileges.map((rp) => rp.privilege)); } - featurePrivileges.forEach(privilege => { + featurePrivileges.forEach((privilege) => { UIFeatureCapabilities[feature.id] = { ...UIFeatureCapabilities[feature.id], ...privilege.ui.reduce( @@ -74,7 +74,7 @@ function buildCapabilities(...allFeatureCapabilities: FeatureCapabilities[]): UI ...acc, }; - ELIGIBLE_FLAT_MERGE_KEYS.forEach(key => { + ELIGIBLE_FLAT_MERGE_KEYS.forEach((key) => { mergedFeatureCapabilities[key] = { ...mergedFeatureCapabilities[key], ...capabilities[key], diff --git a/x-pack/plugins/file_upload/public/components/index_settings.js b/x-pack/plugins/file_upload/public/components/index_settings.js index 8a745f035a8ee..cb1e639e0fede 100644 --- a/x-pack/plugins/file_upload/public/components/index_settings.js +++ b/x-pack/plugins/file_upload/public/components/index_settings.js @@ -58,7 +58,7 @@ export class IndexSettings extends Component { } } - _setIndexName = async name => { + _setIndexName = async (name) => { const errorMessage = await this._isIndexNameAndPatternValid(name); return this.setState({ indexName: name, @@ -72,7 +72,7 @@ export class IndexSettings extends Component { this.props.setIndexName(name); }; - _isIndexNameAndPatternValid = async name => { + _isIndexNameAndPatternValid = async (name) => { const { indexNameList, indexPatternList } = this.state; const nameAlreadyInUse = [...indexNameList, ...indexPatternList].includes(name); if (nameAlreadyInUse) { @@ -113,7 +113,7 @@ export class IndexSettings extends Component { ({ + options={indexTypes.map((indexType) => ({ text: indexType, value: indexType, }))} diff --git a/x-pack/plugins/file_upload/public/components/json_index_file_picker.js b/x-pack/plugins/file_upload/public/components/json_index_file_picker.js index 67086883a9a32..4728efc5b8915 100644 --- a/x-pack/plugins/file_upload/public/components/json_index_file_picker.js +++ b/x-pack/plugins/file_upload/public/components/json_index_file_picker.js @@ -13,8 +13,8 @@ import { MAX_FILE_SIZE } from '../../common/constants/file_import'; import _ from 'lodash'; const ACCEPTABLE_FILETYPES = ['json', 'geojson']; -const acceptedFileTypeString = ACCEPTABLE_FILETYPES.map(type => `.${type}`).join(','); -const acceptedFileTypeStringMessage = ACCEPTABLE_FILETYPES.map(type => `.${type}`).join(', '); +const acceptedFileTypeString = ACCEPTABLE_FILETYPES.map((type) => `.${type}`).join(','); +const acceptedFileTypeStringMessage = ACCEPTABLE_FILETYPES.map((type) => `.${type}`).join(', '); export class JsonIndexFilePicker extends Component { state = { @@ -35,7 +35,7 @@ export class JsonIndexFilePicker extends Component { getFileParseActive = () => this._isMounted && this.state.fileParseActive; - _fileHandler = fileList => { + _fileHandler = (fileList) => { const fileArr = Array.from(fileList); this.props.resetFileAndIndexSettings(); this.setState({ @@ -168,7 +168,7 @@ export class JsonIndexFilePicker extends Component { onFileUpload, setFileProgress: this.setFileProgress, getFileParseActive: this.getFileParseActive, - }).catch(err => { + }).catch((err) => { if (this._isMounted) { this.setState({ fileParseActive: false, diff --git a/x-pack/plugins/file_upload/public/components/json_upload_and_parse.js b/x-pack/plugins/file_upload/public/components/json_upload_and_parse.js index bcfb75e05a17f..453d4f84f7e0e 100644 --- a/x-pack/plugins/file_upload/public/components/json_upload_and_parse.js +++ b/x-pack/plugins/file_upload/public/components/json_upload_and_parse.js @@ -284,9 +284,9 @@ export class JsonUploadAndParse extends Component { {...{ onFileUpload, fileRef, - setIndexName: indexName => this.setState({ indexName }), - setFileRef: fileRef => this.setState({ fileRef }), - setParsedFile: parsedFile => this.setState({ parsedFile }), + setIndexName: (indexName) => this.setState({ indexName }), + setFileRef: (fileRef) => this.setState({ fileRef }), + setParsedFile: (parsedFile) => this.setState({ parsedFile }), transformDetails, resetFileAndIndexSettings: this._resetFileAndIndexSettings, }} @@ -294,10 +294,10 @@ export class JsonUploadAndParse extends Component { this.setState({ indexName })} + setIndexName={(indexName) => this.setState({ indexName })} indexTypes={indexTypes} - setSelectedIndexType={selectedIndexType => this.setState({ selectedIndexType })} - setHasIndexErrors={hasIndexErrors => this.setState({ hasIndexErrors })} + setSelectedIndexType={(selectedIndexType) => this.setState({ selectedIndexType })} + setHasIndexErrors={(hasIndexErrors) => this.setState({ hasIndexErrors })} /> )} diff --git a/x-pack/plugins/file_upload/public/util/file_parser.js b/x-pack/plugins/file_upload/public/util/file_parser.js index 2a0cef25f1678..e396b2b688a07 100644 --- a/x-pack/plugins/file_upload/public/util/file_parser.js +++ b/x-pack/plugins/file_upload/public/util/file_parser.js @@ -46,7 +46,7 @@ export const fileHandler = async ({ // Set up feature tracking let featuresProcessed = 0; - const onFeatureRead = feature => { + const onFeatureRead = (feature) => { // TODO: Add handling and tracking for cleanAndValidate fails featuresProcessed++; return cleanAndValidate(feature); @@ -58,7 +58,7 @@ export const fileHandler = async ({ prevFileReader = fileReader; const filePromise = new Promise((resolve, reject) => { - const onStreamComplete = fileResults => { + const onStreamComplete = (fileResults) => { if (!featuresProcessed) { reject( new Error( diff --git a/x-pack/plugins/file_upload/public/util/file_parser.test.js b/x-pack/plugins/file_upload/public/util/file_parser.test.js index fd467addfba82..5fbac6c7706a4 100644 --- a/x-pack/plugins/file_upload/public/util/file_parser.test.js +++ b/x-pack/plugins/file_upload/public/util/file_parser.test.js @@ -7,8 +7,8 @@ import { fileHandler } from './file_parser'; jest.mock('./pattern_reader', () => ({})); -const cleanAndValidate = jest.fn(a => a); -const setFileProgress = jest.fn(a => a); +const cleanAndValidate = jest.fn((a) => a); +const setFileProgress = jest.fn((a) => a); const getFileReader = () => { const fileReader = { @@ -24,7 +24,7 @@ const getPatternReader = () => { writeDataToPatternStream: jest.fn(), abortStream: jest.fn(), }; - require('./pattern_reader').PatternReader = function() { + require('./pattern_reader').PatternReader = function () { this.writeDataToPatternStream = () => patternReader.writeDataToPatternStream(); this.abortStream = () => patternReader.abortStream(); }; diff --git a/x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.test.js b/x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.test.js index 872df0cddca3c..e86a0fb4284d5 100644 --- a/x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.test.js +++ b/x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.test.js @@ -79,13 +79,13 @@ describe('geo_json_clean_and_validate', () => { // Confirm invalid geometry let geoJson = reader.read(badFeaturesGeoJson); let isSimpleOrValid; - geoJson.features.forEach(feature => { + geoJson.features.forEach((feature) => { isSimpleOrValid = feature.geometry.isSimple() || feature.geometry.isValid(); expect(isSimpleOrValid).toEqual(false); }); // Confirm changes to object - const cleanedFeatures = geoJson.features.map(feature => ({ + const cleanedFeatures = geoJson.features.map((feature) => ({ ...feature, geometry: cleanGeometry(feature), })); @@ -95,7 +95,7 @@ describe('geo_json_clean_and_validate', () => { // Confirm now valid features geometry geoJson = reader.read({ ...badFeaturesGeoJson, features: cleanedFeatures }); - geoJson.features.forEach(feature => { + geoJson.features.forEach((feature) => { isSimpleOrValid = feature.geometry.isSimple() || feature.geometry.isValid(); expect(isSimpleOrValid).toEqual(true); }); diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.js b/x-pack/plugins/file_upload/public/util/indexing_service.js index bfaea00bc6694..eb22b0228b48a 100644 --- a/x-pack/plugins/file_upload/public/util/indexing_service.js +++ b/x-pack/plugins/file_upload/public/util/indexing_service.js @@ -217,7 +217,7 @@ async function getIndexPatternId(name) { const indexPatternSavedObjects = savedObjectSearch.savedObjects; if (indexPatternSavedObjects) { - const ip = indexPatternSavedObjects.find(i => i.attributes.title === name); + const ip = indexPatternSavedObjects.find((i) => i.attributes.title === name); return ip !== undefined ? ip.id : undefined; } else { return undefined; @@ -239,7 +239,7 @@ export const getExistingIndexPatternNames = async () => { fields: ['id', 'title', 'type', 'fields'], perPage: 10000, }) - .then(({ savedObjects }) => savedObjects.map(savedObject => savedObject.get('title'))); + .then(({ savedObjects }) => savedObjects.map((savedObject) => savedObject.get('title'))); return indexPatterns ? indexPatterns.map(({ name }) => name) : []; }; diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.test.js b/x-pack/plugins/file_upload/public/util/indexing_service.test.js index f993ed0e1fd64..072079d3bbdd0 100644 --- a/x-pack/plugins/file_upload/public/util/indexing_service.test.js +++ b/x-pack/plugins/file_upload/public/util/indexing_service.test.js @@ -20,7 +20,7 @@ describe('indexing_service', () => { 'is.not.just.one.period', // name can't be . 'x'.repeat(255), // Cannot be longer than 255 bytes ]; - validNames.forEach(validName => { + validNames.forEach((validName) => { it(`Should validate index pattern: "${validName}"`, () => { const isValid = checkIndexPatternValid(validName); expect(isValid).toEqual(true); @@ -48,7 +48,7 @@ describe('indexing_service', () => { 'x'.repeat(256), // Cannot be longer than 255 bytes 'ü'.repeat(128), // Cannot be longer than 255 bytes (using 2 byte char) ]; - inValidNames.forEach(inValidName => { + inValidNames.forEach((inValidName) => { it(`Should invalidate index pattern: "${inValidName}"`, () => { const isValid = checkIndexPatternValid(inValidName); expect(isValid).toEqual(false); diff --git a/x-pack/plugins/file_upload/public/util/pattern_reader.js b/x-pack/plugins/file_upload/public/util/pattern_reader.js index 152e0f7e54580..bb7a0cab49884 100644 --- a/x-pack/plugins/file_upload/public/util/pattern_reader.js +++ b/x-pack/plugins/file_upload/public/util/pattern_reader.js @@ -21,7 +21,7 @@ export class PatternReader { _registerFeaturePatternHandler(featurePatternCallback) { this._oboeStream.node({ - 'features.*': feature => { + 'features.*': (feature) => { if (!feature.geometry || !feature.geometry.type) { // Only add this error once // TODO: Give feedback on which features failed @@ -48,7 +48,7 @@ export class PatternReader { } _registerStreamCompleteHandler(streamCompleteCallback) { - this._oboeStream.done(parsedGeojson => { + this._oboeStream.done((parsedGeojson) => { streamCompleteCallback({ parsedGeojson, errors: this.getErrors() }); }); } diff --git a/x-pack/plugins/file_upload/public/util/size_limited_chunking.test.js b/x-pack/plugins/file_upload/public/util/size_limited_chunking.test.js index 40f81d30725da..2fac441e8139d 100644 --- a/x-pack/plugins/file_upload/public/util/size_limited_chunking.test.js +++ b/x-pack/plugins/file_upload/public/util/size_limited_chunking.test.js @@ -14,7 +14,7 @@ describe('size_limited_chunking', () => { // Confirm valid geometry const chunkLimit = 100; const chunkedArr = sizeLimitedChunking(testArr, chunkLimit); - chunkedArr.forEach(sizeLimitedArr => { + chunkedArr.forEach((sizeLimitedArr) => { const arrByteSize = new Blob(sizeLimitedArr, { type: 'application/json' }).size; // Chunk size should be less than chunk limit diff --git a/x-pack/plugins/file_upload/server/client/call_with_request_factory.js b/x-pack/plugins/file_upload/server/client/call_with_request_factory.js deleted file mode 100644 index bef6c369fd9ac..0000000000000 --- a/x-pack/plugins/file_upload/server/client/call_with_request_factory.js +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { getDataClient } from '../kibana_server_services'; - -const callWithRequest = once(() => getDataClient()); - -export const callWithRequestFactory = request => { - return (...args) => { - return ( - callWithRequest() - .asScoped(request) - // @ts-ignore - .callAsCurrentUser(...args) - ); - }; -}; diff --git a/x-pack/plugins/file_upload/server/kibana_server_services.js b/x-pack/plugins/file_upload/server/kibana_server_services.js index 104e49015ba80..703b55543fe07 100644 --- a/x-pack/plugins/file_upload/server/kibana_server_services.js +++ b/x-pack/plugins/file_upload/server/kibana_server_services.js @@ -4,15 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -let dataClient; - -export const setElasticsearchClientServices = elasticsearch => { - ({ dataClient } = elasticsearch); -}; -export const getDataClient = () => dataClient; - let internalRepository; -export const setInternalRepository = createInternalRepository => { +export const setInternalRepository = (createInternalRepository) => { internalRepository = createInternalRepository(); }; export const getInternalRepository = () => internalRepository; diff --git a/x-pack/plugins/file_upload/server/plugin.js b/x-pack/plugins/file_upload/server/plugin.js index a11516d03f068..08dd8762ab49d 100644 --- a/x-pack/plugins/file_upload/server/plugin.js +++ b/x-pack/plugins/file_upload/server/plugin.js @@ -5,7 +5,7 @@ */ import { initRoutes } from './routes/file_upload'; -import { setElasticsearchClientServices, setInternalRepository } from './kibana_server_services'; +import { setInternalRepository } from './kibana_server_services'; import { registerFileUploadUsageCollector, fileUploadTelemetryMappingsType } from './telemetry'; export class FileUploadPlugin { @@ -15,7 +15,6 @@ export class FileUploadPlugin { setup(core, plugins) { core.savedObjects.registerType(fileUploadTelemetryMappingsType); - setElasticsearchClientServices(core.elasticsearch); this.router = core.http.createRouter(); registerFileUploadUsageCollector(plugins.usageCollection); } diff --git a/x-pack/plugins/file_upload/server/routes/file_upload.js b/x-pack/plugins/file_upload/server/routes/file_upload.js index d75f03132b404..3935d4ca5fe8e 100644 --- a/x-pack/plugins/file_upload/server/routes/file_upload.js +++ b/x-pack/plugins/file_upload/server/routes/file_upload.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { callWithRequestFactory } from '../client/call_with_request_factory'; import { importDataProvider } from '../models/import_data'; import { updateTelemetry } from '../telemetry/telemetry'; import { MAX_BYTES } from '../../common/constants/file_import'; @@ -86,7 +85,7 @@ const finishValidationAndProcessReq = () => { let resp; try { const validIdReqData = idConditionalValidation(body, boolHasId); - const callWithRequest = callWithRequestFactory(req); + const callWithRequest = con.core.elasticsearch.legacy.client.callAsCurrentUser; const { importData: importDataFunc } = importDataProvider(callWithRequest); const { index, settings, mappings, ingestPipeline, data } = validIdReqData; @@ -115,7 +114,7 @@ const finishValidationAndProcessReq = () => { }; }; -export const initRoutes = router => { +export const initRoutes = (router) => { router.post( { path: `${IMPORT_ROUTE}{id?}`, diff --git a/x-pack/plugins/global_search/README.md b/x-pack/plugins/global_search/README.md new file mode 100644 index 0000000000000..d47e0bd696fd8 --- /dev/null +++ b/x-pack/plugins/global_search/README.md @@ -0,0 +1,49 @@ +# Kibana GlobalSearch plugin + +The GlobalSearch plugin provides an easy way to search for various objects, such as applications +or dashboards from the Kibana instance, from both server and client-side plugins + +## Consuming the globalSearch API + +```ts +startDeps.globalSearch.find('some term').subscribe({ + next: ({ results }) => { + addNewResultsToList(results); + }, + error: () => {}, + complete: () => { + showAsyncSearchIndicator(false); + } +}); +``` + +## Registering custom result providers + +The GlobalSearch API allows to extend provided results by registering your own provider. + +```ts +setupDeps.globalSearch.registerResultProvider({ + id: 'my_provider', + find: (term, options, context) => { + const resultPromise = myService.search(term, context.core.savedObjects.client); + return from(resultPromise).pipe(takeUntil(options.aborted$); + }, +}); +``` + +## Known limitations + +### Client-side registered providers + +Results from providers registered from the client-side `registerResultProvider` API will +not be available when performing a search from the server-side. For this reason, prefer +registering providers using the server-side API when possible. + +Refer to the [RFC](rfcs/text/0011_global_search.md#result_provider_registration) for more details + +### Search completion cause + +There is currently no way to identify `globalSearch.find` observable completion cause: +searches completing because all providers returned all their results and searches +completing because the consumer aborted the search using the `aborted$` option or because +the internal timout period has been reaches will both complete the same way. diff --git a/x-pack/plugins/global_search/common/errors.test.ts b/x-pack/plugins/global_search/common/errors.test.ts new file mode 100644 index 0000000000000..949795abd701a --- /dev/null +++ b/x-pack/plugins/global_search/common/errors.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GlobalSearchFindError } from './errors'; + +describe('GlobalSearchFindError', () => { + describe('#invalidLicense', () => { + it('create an error with the correct `type`', () => { + const error = GlobalSearchFindError.invalidLicense('foobar'); + expect(error.message).toBe('foobar'); + expect(error.type).toBe('invalid-license'); + }); + + it('can be identified via instanceof', () => { + const error = GlobalSearchFindError.invalidLicense('foo'); + expect(error instanceof GlobalSearchFindError).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/global_search/common/errors.ts b/x-pack/plugins/global_search/common/errors.ts new file mode 100644 index 0000000000000..15bc0958cb8aa --- /dev/null +++ b/x-pack/plugins/global_search/common/errors.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// only one type for now, but already present for future-proof reasons +export type GlobalSearchFindErrorType = 'invalid-license'; + +/** + * Error thrown from the {@link GlobalSearchPluginStart.find | GlobalSearch find API}'s result observable + * + * @public + */ +export class GlobalSearchFindError extends Error { + public static invalidLicense(message: string) { + return new GlobalSearchFindError('invalid-license', message); + } + + private constructor(public readonly type: GlobalSearchFindErrorType, message: string) { + super(message); + + // Set the prototype explicitly, see: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, GlobalSearchFindError.prototype); + } +} diff --git a/x-pack/plugins/global_search/common/license_checker.mock.ts b/x-pack/plugins/global_search/common/license_checker.mock.ts new file mode 100644 index 0000000000000..e19a2562e53d8 --- /dev/null +++ b/x-pack/plugins/global_search/common/license_checker.mock.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ILicenseChecker } from './license_checker'; + +const createLicenseCheckerMock = (): jest.Mocked => { + const mock = { + getState: jest.fn(), + getLicense: jest.fn(), + clean: jest.fn(), + }; + + mock.getLicense.mockReturnValue(undefined); + mock.getState.mockReturnValue({ valid: true }); + + return mock; +}; + +export const licenseCheckerMock = { + create: createLicenseCheckerMock, +}; diff --git a/x-pack/plugins/global_search/common/license_checker.test.ts b/x-pack/plugins/global_search/common/license_checker.test.ts new file mode 100644 index 0000000000000..47a0d41016d71 --- /dev/null +++ b/x-pack/plugins/global_search/common/license_checker.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable, of, BehaviorSubject } from 'rxjs'; +import { licenseMock } from '../../licensing/common/licensing.mock'; +import { ILicense, LicenseCheck } from '../../licensing/common/types'; +import { LicenseChecker } from './license_checker'; + +describe('LicenseChecker', () => { + const createLicense = (check: LicenseCheck): ILicense => { + const license = licenseMock.createLicenseMock(); + license.check.mockReturnValue(check); + return license; + }; + + const createLicense$ = (check: LicenseCheck): Observable => of(createLicense(check)); + + it('returns the correct state of the license', () => { + let checker = new LicenseChecker(createLicense$({ state: 'valid' })); + expect(checker.getState()).toEqual({ valid: true }); + + checker = new LicenseChecker(createLicense$({ state: 'expired' })); + expect(checker.getState()).toEqual({ valid: false, message: 'expired' }); + + checker = new LicenseChecker(createLicense$({ state: 'invalid' })); + expect(checker.getState()).toEqual({ valid: false, message: 'invalid' }); + + checker = new LicenseChecker(createLicense$({ state: 'unavailable' })); + expect(checker.getState()).toEqual({ valid: false, message: 'unavailable' }); + }); + + it('updates the state when the license changes', () => { + const license$ = new BehaviorSubject(createLicense({ state: 'valid' })); + + const checker = new LicenseChecker(license$); + expect(checker.getState()).toEqual({ valid: true }); + + license$.next(createLicense({ state: 'expired' })); + expect(checker.getState()).toEqual({ valid: false, message: 'expired' }); + + license$.next(createLicense({ state: 'valid' })); + expect(checker.getState()).toEqual({ valid: true }); + }); + + it('removes the subscription when calling `clean`', () => { + const mockUnsubscribe = jest.fn(); + const mockObs = { + subscribe: jest.fn().mockReturnValue({ unsubscribe: mockUnsubscribe }), + }; + + const checker = new LicenseChecker(mockObs as any); + + expect(mockObs.subscribe).toHaveBeenCalledTimes(1); + expect(mockUnsubscribe).not.toHaveBeenCalled(); + + checker.clean(); + + expect(mockUnsubscribe).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/global_search/common/license_checker.ts b/x-pack/plugins/global_search/common/license_checker.ts new file mode 100644 index 0000000000000..d201b31802b32 --- /dev/null +++ b/x-pack/plugins/global_search/common/license_checker.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable, Subscription } from 'rxjs'; +import { ILicense } from '../../licensing/common/types'; + +export type LicenseState = { valid: false; message: string } | { valid: true }; + +export type CheckLicense = (license: ILicense) => LicenseState; + +const checkLicense: CheckLicense = (license) => { + const check = license.check('globalSearch', 'basic'); + switch (check.state) { + case 'expired': + return { valid: false, message: 'expired' }; + case 'invalid': + return { valid: false, message: 'invalid' }; + case 'unavailable': + return { valid: false, message: 'unavailable' }; + case 'valid': + return { valid: true }; + default: + throw new Error(`Invalid license state: ${check.state}`); + } +}; + +export type ILicenseChecker = PublicMethodsOf; + +export class LicenseChecker { + private subscription: Subscription; + private state: LicenseState = { valid: false, message: 'unknown' }; + + constructor(license$: Observable) { + this.subscription = license$.subscribe((license) => { + this.state = checkLicense(license); + }); + } + + public getState() { + return this.state; + } + + public clean() { + this.subscription.unsubscribe(); + } +} diff --git a/x-pack/plugins/global_search/common/operators/index.ts b/x-pack/plugins/global_search/common/operators/index.ts new file mode 100644 index 0000000000000..2a0cf066a04aa --- /dev/null +++ b/x-pack/plugins/global_search/common/operators/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { takeInArray } from './take_in_array'; diff --git a/x-pack/plugins/global_search/common/operators/take_in_array.test.ts b/x-pack/plugins/global_search/common/operators/take_in_array.test.ts new file mode 100644 index 0000000000000..b73ee20c9889a --- /dev/null +++ b/x-pack/plugins/global_search/common/operators/take_in_array.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TestScheduler } from 'rxjs/testing'; +import { takeInArray } from './take_in_array'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + +describe('takeInArray', () => { + it('only emits a given `count` of items from an array observable', () => { + getTestScheduler().run(({ expectObservable, hot }) => { + const source = hot('a-b-c', { a: [1], b: [2], c: [3] }); + const expected = 'a-(b|)'; + + expectObservable(source.pipe(takeInArray(2))).toBe(expected, { + a: [1], + b: [2], + }); + }); + }); + + it('completes if the source completes before reaching the given `count`', () => { + getTestScheduler().run(({ expectObservable, hot }) => { + const source = hot('a-b-c-|', { a: [1, 2], b: [3, 4], c: [5] }); + const expected = 'a-b-c-|'; + + expectObservable(source.pipe(takeInArray(10))).toBe(expected, { + a: [1, 2], + b: [3, 4], + c: [5], + }); + }); + }); + + it('split the emission if `count` is reached in a given emission', () => { + getTestScheduler().run(({ expectObservable, hot }) => { + const source = hot('a-b-c', { a: [1, 2, 3], b: [4, 5, 6], c: [7, 8] }); + const expected = 'a-(b|)'; + + expectObservable(source.pipe(takeInArray(5))).toBe(expected, { + a: [1, 2, 3], + b: [4, 5], + }); + }); + }); + + it('throws when trying to take a negative number of items', () => { + getTestScheduler().run(({ expectObservable, hot }) => { + const source = hot('a-b-c', { a: [1, 2, 3], b: [4, 5, 6], c: [7, 8] }); + + expect(() => { + source.pipe(takeInArray(-4)).subscribe(() => undefined); + }).toThrowErrorMatchingInlineSnapshot(`"Cannot take a negative number of items"`); + }); + }); +}); diff --git a/x-pack/plugins/global_search/common/operators/take_in_array.ts b/x-pack/plugins/global_search/common/operators/take_in_array.ts new file mode 100644 index 0000000000000..7d041d3c2bab0 --- /dev/null +++ b/x-pack/plugins/global_search/common/operators/take_in_array.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// eslint-disable-next-line max-classes-per-file +import { + EMPTY, + MonoTypeOperatorFunction, + Observable, + Operator, + Subscriber, + TeardownLogic, +} from 'rxjs'; + +/** + * Emits only the first `count` items from the arrays emitted by the source Observable. The limit + * is global to all emitted values, and not per emission. + * + * @example + * ```ts + * const source = of([1, 2], [3, 4], [5, 6]); + * const takeThreeInArray = source.pipe(takeInArray(3)); + * takeThreeInArray.subscribe(x => console.log(x)); + * + * // Logs: + * // [1,2] + * // [3] + * ``` + * + * @param count The total maximum number of value to keep from the emitted arrays + */ +export function takeInArray(count: number): MonoTypeOperatorFunction { + return function takeLastOperatorFunction(source: Observable): Observable { + if (count === 0) { + return EMPTY; + } else { + return source.lift(new TakeInArray(count)); + } + }; +} + +class TakeInArray implements Operator { + constructor(private total: number) { + if (this.total < 0) { + throw new Error('Cannot take a negative number of items'); + } + } + + call(subscriber: Subscriber, source: any): TeardownLogic { + return source.subscribe(new TakeInArraySubscriber(subscriber, this.total)); + } +} + +class TakeInArraySubscriber extends Subscriber { + private current: number = 0; + + constructor(destination: Subscriber, private total: number) { + super(destination); + } + + protected _next(value: T[]): void { + const remaining = this.total - this.current; + if (remaining > value.length) { + this.destination.next!(value); + this.current += value.length; + } else { + this.destination.next!(value.slice(0, remaining)); + this.destination.complete!(); + this.unsubscribe(); + } + } +} diff --git a/x-pack/plugins/global_search/common/process_result.test.mocks.ts b/x-pack/plugins/global_search/common/process_result.test.mocks.ts new file mode 100644 index 0000000000000..718ac7a1a6a52 --- /dev/null +++ b/x-pack/plugins/global_search/common/process_result.test.mocks.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const convertResultUrlMock = jest.fn().mockReturnValue('converted-url'); +jest.doMock('./utils', () => ({ + convertResultUrl: convertResultUrlMock, +})); diff --git a/x-pack/plugins/global_search/common/process_result.test.ts b/x-pack/plugins/global_search/common/process_result.test.ts new file mode 100644 index 0000000000000..723f21a24f552 --- /dev/null +++ b/x-pack/plugins/global_search/common/process_result.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { convertResultUrlMock } from './process_result.test.mocks'; + +import { IBasePath } from './utils'; +import { GlobalSearchProviderResult } from './types'; +import { processProviderResult } from './process_result'; + +const createResult = (parts: Partial): GlobalSearchProviderResult => ({ + id: 'id', + title: 'title', + type: 'type', + icon: 'icon', + url: '/foo/bar', + score: 42, + meta: { foo: 'bar' }, + ...parts, +}); + +describe('processProviderResult', () => { + let basePath: jest.Mocked; + + beforeEach(() => { + basePath = { + prepend: jest.fn(), + }; + + convertResultUrlMock.mockClear(); + }); + + it('returns all properties unchanged except `url`', () => { + const r1 = createResult({ + id: '1', + type: 'test', + url: '/url-1', + title: 'title 1', + icon: 'foo', + score: 69, + meta: { hello: 'dolly' }, + }); + + expect(processProviderResult(r1, basePath)).toEqual({ + ...r1, + url: expect.any(String), + }); + }); + + it('converts the url using `convertResultUrl`', () => { + const r1 = createResult({ id: '1', url: '/url-1' }); + const r2 = createResult({ id: '2', url: '/url-2' }); + + convertResultUrlMock.mockReturnValueOnce('/url-A'); + convertResultUrlMock.mockReturnValueOnce('/url-B'); + + expect(convertResultUrlMock).not.toHaveBeenCalled(); + + const g1 = processProviderResult(r1, basePath); + + expect(g1.url).toEqual('/url-A'); + expect(convertResultUrlMock).toHaveBeenCalledTimes(1); + expect(convertResultUrlMock).toHaveBeenCalledWith(r1.url, basePath); + + const g2 = processProviderResult(r2, basePath); + + expect(g2.url).toEqual('/url-B'); + expect(convertResultUrlMock).toHaveBeenCalledTimes(2); + expect(convertResultUrlMock).toHaveBeenCalledWith(r2.url, basePath); + }); +}); diff --git a/x-pack/plugins/global_search/common/process_result.ts b/x-pack/plugins/global_search/common/process_result.ts new file mode 100644 index 0000000000000..fed6dc14f066b --- /dev/null +++ b/x-pack/plugins/global_search/common/process_result.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GlobalSearchProviderResult, GlobalSearchResult } from './types'; +import { convertResultUrl, IBasePath } from './utils'; + +/** + * Convert a {@link GlobalSearchProviderResult | provider result} + * to a {@link GlobalSearchResult | service result} + */ +export const processProviderResult = ( + result: GlobalSearchProviderResult, + basePath: IBasePath +): GlobalSearchResult => { + return { + ...result, + url: convertResultUrl(result.url, basePath), + }; +}; diff --git a/x-pack/plugins/global_search/common/types.ts b/x-pack/plugins/global_search/common/types.ts new file mode 100644 index 0000000000000..26940806a4ecd --- /dev/null +++ b/x-pack/plugins/global_search/common/types.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { Serializable } from 'src/core/types'; + +/** + * Options provided to {@link GlobalSearchResultProvider | a result provider}'s `find` method. + */ +export interface GlobalSearchProviderFindOptions { + /** + * A custom preference token associated with a search 'session' that should be used to get consistent scoring + * when performing calls to ES. Can also be used as a 'session' token for providers returning data from elsewhere + * than an elasticsearch cluster. + */ + preference: string; + /** + * Observable that emits once if and when the `find` call has been aborted, either manually by the consumer, + * or when the internal timeout period as been reached. + * + * When a `find` request is effectively aborted, the service will stop emitting any new result to the consumer anyway, but + * this can (and should) be used to cancel any pending asynchronous task and complete the result observable from within the provider. + */ + aborted$: Observable; + /** + * The total maximum number of results (including all batches, not per emission) that should be returned by the provider for a given `find` request. + * Any result emitted exceeding this quota will be ignored by the service and not emitted to the consumer. + */ + maxResults: number; +} + +/** + * Structured type for the {@link GlobalSearchProviderResult.url | provider result's url property} + */ +export type GlobalSearchProviderResultUrl = string | { path: string; prependBasePath: boolean }; + +/** + * Representation of a result returned by a {@link GlobalSearchResultProvider | result provider} + */ +export interface GlobalSearchProviderResult { + /** an id that should be unique for an individual provider's results */ + id: string; + /** the title/label of the result */ + title: string; + /** the type of result */ + type: string; + /** an optional EUI icon name to associate with the search result */ + icon?: string; + /** + * The url associated with this result. + * This can be either an absolute url, a path relative to the basePath, or a structure specifying if the basePath should be prepended. + * + * @example + * `result.url = 'https://kibana-instance:8080/base-path/app/my-app/my-result-type/id';` + * `result.url = '/app/my-app/my-result-type/id';` + * `result.url = { path: '/base-path/app/my-app/my-result-type/id', prependBasePath: false };` + */ + url: GlobalSearchProviderResultUrl; + /** the score of the result, from 1 (lowest) to 100 (highest) */ + score: number; + /** an optional record of metadata for this result */ + meta?: Record; +} + +/** + * Representation of a result returned by the {@link GlobalSearchPluginStart.find | `find` API} + */ +export type GlobalSearchResult = Omit & { + /** + * The url associated with this result. + * This can be either an absolute url, or a relative path including the basePath + */ + url: string; +}; + +/** + * Response returned from the {@link GlobalSearchPluginStart | global search service}'s `find` API + * + * @public + */ +export interface GlobalSearchBatchedResults { + /** + * Results for this batch + */ + results: GlobalSearchResult[]; +} diff --git a/x-pack/plugins/global_search/common/utils.test.ts b/x-pack/plugins/global_search/common/utils.test.ts new file mode 100644 index 0000000000000..27f1ce99a58cc --- /dev/null +++ b/x-pack/plugins/global_search/common/utils.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { convertResultUrl } from './utils'; + +const createBasePath = () => ({ + prepend: jest.fn(), +}); + +describe('convertResultUrl', () => { + let basePath: ReturnType; + + beforeEach(() => { + basePath = createBasePath(); + basePath.prepend.mockImplementation((path) => `/base-path${path}`); + }); + + describe('when the url is a string', () => { + it('does not convert absolute urls', () => { + expect(convertResultUrl('http://kibana:8080/foo/bar', basePath)).toEqual( + 'http://kibana:8080/foo/bar' + ); + expect(convertResultUrl('https://localhost/path/to/thing', basePath)).toEqual( + 'https://localhost/path/to/thing' + ); + expect(basePath.prepend).toHaveBeenCalledTimes(0); + }); + + it('prepends the base path to relative urls', () => { + expect(convertResultUrl('/app/my-app/foo', basePath)).toEqual('/base-path/app/my-app/foo'); + expect(basePath.prepend).toHaveBeenCalledTimes(1); + expect(basePath.prepend).toHaveBeenCalledWith('/app/my-app/foo'); + + expect(convertResultUrl('/some-path', basePath)).toEqual('/base-path/some-path'); + expect(basePath.prepend).toHaveBeenCalledTimes(2); + expect(basePath.prepend).toHaveBeenCalledWith('/some-path'); + }); + }); + + describe('when the url is an object', () => { + it('converts the path if `prependBasePath` is true', () => { + expect(convertResultUrl({ path: '/app/my-app', prependBasePath: true }, basePath)).toEqual( + '/base-path/app/my-app' + ); + expect(basePath.prepend).toHaveBeenCalledTimes(1); + expect(basePath.prepend).toHaveBeenCalledWith('/app/my-app'); + + expect(convertResultUrl({ path: '/some-path', prependBasePath: true }, basePath)).toEqual( + '/base-path/some-path' + ); + expect(basePath.prepend).toHaveBeenCalledTimes(2); + expect(basePath.prepend).toHaveBeenCalledWith('/some-path'); + }); + it('does not convert the path if `prependBasePath` is false', () => { + expect(convertResultUrl({ path: '/app/my-app', prependBasePath: false }, basePath)).toEqual( + '/app/my-app' + ); + expect(convertResultUrl({ path: '/some-path', prependBasePath: false }, basePath)).toEqual( + '/some-path' + ); + expect(basePath.prepend).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/x-pack/plugins/global_search/common/utils.ts b/x-pack/plugins/global_search/common/utils.ts new file mode 100644 index 0000000000000..46648319458de --- /dev/null +++ b/x-pack/plugins/global_search/common/utils.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GlobalSearchProviderResultUrl } from './types'; + +// interface matching both the server and client-side implementation of IBasePath for our needs +// used to avoid duplicating `convertResultUrl` in server and client code due to different signatures. +export interface IBasePath { + prepend(path: string): string; +} + +/** + * Convert a {@link GlobalSearchProviderResultUrl | provider result's url} to an absolute or relative url + * usable in {@link GlobalSearchResult | service results} + */ +export const convertResultUrl = ( + url: GlobalSearchProviderResultUrl, + basePath: IBasePath +): string => { + if (typeof url === 'string') { + // relative path + if (url.startsWith('/')) { + return basePath.prepend(url); + } + // absolute url + return url; + } + if (url.prependBasePath) { + return basePath.prepend(url.path); + } + return url.path; +}; diff --git a/x-pack/plugins/global_search/kibana.json b/x-pack/plugins/global_search/kibana.json new file mode 100644 index 0000000000000..c94e080a8c589 --- /dev/null +++ b/x-pack/plugins/global_search/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "globalSearch", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["licensing"], + "optionalPlugins": [], + "configPath": ["xpack", "global_search"] +} diff --git a/x-pack/plugins/global_search/public/config.ts b/x-pack/plugins/global_search/public/config.ts new file mode 100644 index 0000000000000..a3969bef287b2 --- /dev/null +++ b/x-pack/plugins/global_search/public/config.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface GlobalSearchClientConfigType { + // is a string because the server-side counterpart is a duration + // which is serialized to string when sent to the client + // should be parsed using moment.duration(config.search_timeout) + search_timeout: string; +} diff --git a/x-pack/plugins/global_search/public/index.ts b/x-pack/plugins/global_search/public/index.ts new file mode 100644 index 0000000000000..18483cea72540 --- /dev/null +++ b/x-pack/plugins/global_search/public/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializer } from 'src/core/public'; +import { + GlobalSearchPlugin, + GlobalSearchPluginSetupDeps, + GlobalSearchPluginStartDeps, +} from './plugin'; +import { GlobalSearchPluginSetup, GlobalSearchPluginStart } from './types'; + +export const plugin: PluginInitializer< + GlobalSearchPluginSetup, + GlobalSearchPluginStart, + GlobalSearchPluginSetupDeps, + GlobalSearchPluginStartDeps +> = (context) => new GlobalSearchPlugin(context); + +export { + GlobalSearchBatchedResults, + GlobalSearchProviderFindOptions, + GlobalSearchProviderResult, + GlobalSearchProviderResultUrl, + GlobalSearchResult, +} from '../common/types'; +export { + GlobalSearchPluginSetup, + GlobalSearchPluginStart, + GlobalSearchResultProvider, +} from './types'; +export { GlobalSearchFindOptions } from './services/types'; diff --git a/x-pack/plugins/global_search/public/mocks.ts b/x-pack/plugins/global_search/public/mocks.ts new file mode 100644 index 0000000000000..97dc01e92dbfe --- /dev/null +++ b/x-pack/plugins/global_search/public/mocks.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GlobalSearchPluginSetup, GlobalSearchPluginStart } from './types'; +import { searchServiceMock } from './services/search_service.mock'; + +const createSetupMock = (): jest.Mocked => { + const searchMock = searchServiceMock.createSetupContract(); + + return { + registerResultProvider: searchMock.registerResultProvider, + }; +}; + +const createStartMock = (): jest.Mocked => { + const searchMock = searchServiceMock.createStartContract(); + + return { + find: searchMock.find, + }; +}; + +export const globalSearchPluginMock = { + createSetupContract: createSetupMock, + createStartContract: createStartMock, +}; diff --git a/x-pack/plugins/global_search/public/plugin.ts b/x-pack/plugins/global_search/public/plugin.ts new file mode 100644 index 0000000000000..6af8ec32a581d --- /dev/null +++ b/x-pack/plugins/global_search/public/plugin.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; +import { LicensingPluginStart } from '../../licensing/public'; +import { LicenseChecker, ILicenseChecker } from '../common/license_checker'; +import { GlobalSearchPluginSetup, GlobalSearchPluginStart } from './types'; +import { GlobalSearchClientConfigType } from './config'; +import { SearchService } from './services'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface GlobalSearchPluginSetupDeps {} +export interface GlobalSearchPluginStartDeps { + licensing: LicensingPluginStart; +} + +export class GlobalSearchPlugin + implements + Plugin< + GlobalSearchPluginSetup, + GlobalSearchPluginStart, + GlobalSearchPluginSetupDeps, + GlobalSearchPluginStartDeps + > { + private readonly config: GlobalSearchClientConfigType; + private licenseChecker?: ILicenseChecker; + private readonly searchService = new SearchService(); + + constructor(context: PluginInitializerContext) { + this.config = context.config.get(); + } + + setup(core: CoreSetup<{}, GlobalSearchPluginStart>) { + const { registerResultProvider } = this.searchService.setup({ + config: this.config, + }); + + return { + registerResultProvider, + }; + } + + start({ http }: CoreStart, { licensing }: GlobalSearchPluginStartDeps) { + this.licenseChecker = new LicenseChecker(licensing.license$); + const { find } = this.searchService.start({ + http, + licenseChecker: this.licenseChecker, + }); + + return { + find, + }; + } + + public stop() { + if (this.licenseChecker) { + this.licenseChecker.clean(); + } + } +} diff --git a/x-pack/plugins/global_search/public/services/fetch_server_results.test.ts b/x-pack/plugins/global_search/public/services/fetch_server_results.test.ts new file mode 100644 index 0000000000000..f62acd08633ff --- /dev/null +++ b/x-pack/plugins/global_search/public/services/fetch_server_results.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TestScheduler } from 'rxjs/testing'; +import { httpServiceMock } from '../../../../../src/core/public/mocks'; +import { GlobalSearchResult } from '../../common/types'; +import { fetchServerResults } from './fetch_server_results'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + +const createResult = (id: string, parts: Partial = {}): GlobalSearchResult => ({ + id, + title: id, + type: 'type', + url: `/path/to/${id}`, + score: 100, + ...parts, +}); + +describe('fetchServerResults', () => { + let http: ReturnType; + + beforeEach(() => { + http = httpServiceMock.createStartContract(); + }); + + it('perform a POST request to the endpoint with valid options', () => { + http.post.mockResolvedValue({ results: [] }); + + fetchServerResults(http, 'some term', { preference: 'pref' }); + + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith('/internal/global_search/find', { + body: JSON.stringify({ term: 'some term', options: { preference: 'pref' } }), + }); + }); + + it('returns the results from the server', async () => { + const resultA = createResult('A'); + const resultB = createResult('B'); + + http.post.mockResolvedValue({ results: [resultA, resultB] }); + + const results = await fetchServerResults(http, 'some term', { preference: 'pref' }).toPromise(); + + expect(http.post).toHaveBeenCalledTimes(1); + expect(results).toHaveLength(2); + expect(results[0]).toEqual(resultA); + expect(results[1]).toEqual(resultB); + }); + + describe('returns an observable that', () => { + // NOTE: test scheduler do not properly work with promises because of their asynchronous nature. + // we are cheating here by having `http.post` return an observable instead of a promise. + // this still allows more finely grained testing about timing, and asserting that the method + // works properly when `post` returns a real promise is handled in other tests of this suite + + it('emits when the response is received', () => { + getTestScheduler().run(({ expectObservable, hot }) => { + http.post.mockReturnValue(hot('---(a|)', { a: { results: [] } }) as any); + + const results = fetchServerResults(http, 'term', {}); + + expectObservable(results).toBe('---(a|)', { + a: [], + }); + }); + }); + + it('completes without returning results if aborted$ emits before the response', () => { + getTestScheduler().run(({ expectObservable, hot }) => { + http.post.mockReturnValue(hot('---(a|)', { a: { results: [] } }) as any); + const aborted$ = hot('-(a|)', { a: undefined }); + const results = fetchServerResults(http, 'term', { aborted$ }); + + expectObservable(results).toBe('-|', { + a: [], + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/global_search/public/services/fetch_server_results.ts b/x-pack/plugins/global_search/public/services/fetch_server_results.ts new file mode 100644 index 0000000000000..3c06dfab9f50e --- /dev/null +++ b/x-pack/plugins/global_search/public/services/fetch_server_results.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable, from, EMPTY } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; +import { HttpStart } from 'src/core/public'; +import { GlobalSearchResult } from '../../common/types'; +import { GlobalSearchFindOptions } from './types'; + +interface ServerFetchResponse { + results: GlobalSearchResult[]; +} + +/** + * Fetch the server-side results from the GS internal HTTP API. + * + * @remarks + * Though this function returns an Observable, the current implementation is not streaming + * results from the server. All results will be returned in a single batch when + * all server-side providers are completed. + */ +export const fetchServerResults = ( + http: HttpStart, + term: string, + { preference, aborted$ }: GlobalSearchFindOptions +): Observable => { + let controller: AbortController | undefined; + if (aborted$) { + controller = new AbortController(); + aborted$.subscribe(() => { + controller!.abort(); + }); + } + return from( + http.post('/internal/global_search/find', { + body: JSON.stringify({ term, options: { preference } }), + signal: controller?.signal, + }) + ).pipe( + takeUntil(aborted$ ?? EMPTY), + map((response) => response.results) + ); +}; diff --git a/x-pack/plugins/global_search/public/services/index.ts b/x-pack/plugins/global_search/public/services/index.ts new file mode 100644 index 0000000000000..8d3cb86043432 --- /dev/null +++ b/x-pack/plugins/global_search/public/services/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SearchService, SearchServiceSetup, SearchServiceStart } from './search_service'; +export { GlobalSearchFindOptions } from './types'; diff --git a/x-pack/plugins/global_search/public/services/search_service.mock.ts b/x-pack/plugins/global_search/public/services/search_service.mock.ts new file mode 100644 index 0000000000000..eca69148288b9 --- /dev/null +++ b/x-pack/plugins/global_search/public/services/search_service.mock.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchServiceSetup, SearchServiceStart } from './search_service'; +import { of } from 'rxjs'; + +const createSetupMock = (): jest.Mocked => { + return { + registerResultProvider: jest.fn(), + }; +}; + +const createStartMock = (): jest.Mocked => { + const mock = { + find: jest.fn(), + }; + mock.find.mockReturnValue(of({ results: [] })); + + return mock; +}; + +export const searchServiceMock = { + createSetupContract: createSetupMock, + createStartContract: createStartMock, +}; diff --git a/x-pack/plugins/global_search/public/services/search_service.test.mocks.ts b/x-pack/plugins/global_search/public/services/search_service.test.mocks.ts new file mode 100644 index 0000000000000..ce406e27c4a72 --- /dev/null +++ b/x-pack/plugins/global_search/public/services/search_service.test.mocks.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const fetchServerResultsMock = jest.fn(); +jest.doMock('./fetch_server_results', () => ({ + fetchServerResults: fetchServerResultsMock, +})); + +export const getDefaultPreferenceMock = jest.fn(); +jest.doMock('./utils', () => ({ + ...jest.requireActual('./utils'), + getDefaultPreference: getDefaultPreferenceMock, +})); diff --git a/x-pack/plugins/global_search/public/services/search_service.test.ts b/x-pack/plugins/global_search/public/services/search_service.test.ts new file mode 100644 index 0000000000000..350547a928fe4 --- /dev/null +++ b/x-pack/plugins/global_search/public/services/search_service.test.ts @@ -0,0 +1,436 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fetchServerResultsMock, getDefaultPreferenceMock } from './search_service.test.mocks'; + +import { Observable, of } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { TestScheduler } from 'rxjs/testing'; +import { duration } from 'moment'; +import { httpServiceMock } from '../../../../../src/core/public/mocks'; +import { licenseCheckerMock } from '../../common/license_checker.mock'; +import { GlobalSearchProviderResult, GlobalSearchResult } from '../../common/types'; +import { GlobalSearchFindError } from '../../common/errors'; +import { GlobalSearchClientConfigType } from '../config'; +import { GlobalSearchResultProvider } from '../types'; +import { SearchService } from './search_service'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + +describe('SearchService', () => { + let service: SearchService; + let httpStart: ReturnType; + let licenseChecker: ReturnType; + + const createConfig = (timeoutMs: number = 30000): GlobalSearchClientConfigType => { + return { + search_timeout: duration(timeoutMs).toString(), + }; + }; + + const startDeps = () => ({ + http: httpStart, + licenseChecker, + }); + + const createProvider = ( + id: string, + source: Observable = of([]) + ): jest.Mocked => ({ + id, + find: jest.fn().mockImplementation((term, options, context) => source), + }); + + const expectedResult = (id: string) => expect.objectContaining({ id }); + + const expectedBatch = (...ids: string[]) => ({ + results: ids.map((id) => expectedResult(id)), + }); + + const providerResult = ( + id: string, + parts: Partial = {} + ): GlobalSearchProviderResult => ({ + title: id, + type: 'test', + url: '/foo/bar', + score: 100, + ...parts, + id, + }); + + const serverResult = ( + id: string, + parts: Partial = {} + ): GlobalSearchResult => ({ + title: id, + type: 'test', + url: '/foo/bar', + score: 100, + ...parts, + id, + }); + + beforeEach(() => { + service = new SearchService(); + httpStart = httpServiceMock.createStartContract({ basePath: '/base-path' }); + licenseChecker = licenseCheckerMock.create(); + + fetchServerResultsMock.mockClear(); + fetchServerResultsMock.mockReturnValue(of()); + + getDefaultPreferenceMock.mockClear(); + getDefaultPreferenceMock.mockReturnValue('default_pref'); + }); + + describe('#setup()', () => { + describe('#registerResultProvider()', () => { + it('throws when trying to register the same provider twice', () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + const provider = createProvider('A'); + registerResultProvider(provider); + expect(() => { + registerResultProvider(provider); + }).toThrowErrorMatchingInlineSnapshot(`"trying to register duplicate provider: A"`); + }); + }); + }); + + describe('#start()', () => { + describe('#find()', () => { + it('calls the provider with the correct parameters', () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + const provider = createProvider('A'); + registerResultProvider(provider); + + const { find } = service.start(startDeps()); + find('foobar', { preference: 'pref' }); + + expect(provider.find).toHaveBeenCalledTimes(1); + expect(provider.find).toHaveBeenCalledWith( + 'foobar', + expect.objectContaining({ preference: 'pref' }) + ); + }); + + it('calls `fetchServerResults` with the correct parameters', () => { + service.setup({ config: createConfig() }); + + const { find } = service.start(startDeps()); + find('foobar', { preference: 'pref' }); + + expect(fetchServerResultsMock).toHaveBeenCalledTimes(1); + expect(fetchServerResultsMock).toHaveBeenCalledWith( + httpStart, + 'foobar', + expect.objectContaining({ preference: 'pref', aborted$: expect.any(Object) }) + ); + }); + + it('calls `getDefaultPreference` when `preference` is not specified', () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + const provider = createProvider('A'); + registerResultProvider(provider); + + const { find } = service.start(startDeps()); + find('foobar', { preference: 'pref' }); + + expect(getDefaultPreferenceMock).not.toHaveBeenCalled(); + + expect(provider.find).toHaveBeenNthCalledWith( + 1, + 'foobar', + expect.objectContaining({ + preference: 'pref', + }) + ); + + find('foobar', {}); + + expect(getDefaultPreferenceMock).toHaveBeenCalledTimes(1); + + expect(provider.find).toHaveBeenNthCalledWith( + 2, + 'foobar', + expect.objectContaining({ + preference: 'default_pref', + }) + ); + }); + + it('return the results from the provider', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('a-b-|', { + a: [providerResult('1')], + b: [providerResult('2')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const { find } = service.start(startDeps()); + const results = find('foo', {}); + + expectObservable(results).toBe('a-b-|', { + a: expectedBatch('1'), + b: expectedBatch('2'), + }); + }); + }); + + it('return the results from the server', async () => { + service.setup({ config: createConfig() }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const serverResults = hot('a-b-|', { + a: [serverResult('1')], + b: [serverResult('2')], + }); + + fetchServerResultsMock.mockReturnValue(serverResults); + + const { find } = service.start(startDeps()); + const results = find('foo', {}); + + expectObservable(results).toBe('a-b-|', { + a: expectedBatch('1'), + b: expectedBatch('2'), + }); + }); + }); + + it('handles multiple providers', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + registerResultProvider( + createProvider( + 'A', + hot('a---d-|', { + a: [providerResult('A1'), providerResult('A2')], + d: [providerResult('A3')], + }) + ) + ); + registerResultProvider( + createProvider( + 'B', + hot('-b-c| ', { + b: [providerResult('B1')], + c: [providerResult('B2'), providerResult('B3')], + }) + ) + ); + + const { find } = service.start(startDeps()); + const results = find('foo', {}); + + expectObservable(results).toBe('ab-cd-|', { + a: expectedBatch('A1', 'A2'), + b: expectedBatch('B1'), + c: expectedBatch('B2', 'B3'), + d: expectedBatch('A3'), + }); + }); + }); + + it('return mixed server/client providers results', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + fetchServerResultsMock.mockReturnValue( + hot('-----(c|)', { + c: [serverResult('S1'), serverResult('S2')], + }) + ); + + registerResultProvider( + createProvider( + 'A', + hot('a-b-|', { + a: [providerResult('P1')], + b: [providerResult('P2')], + }) + ) + ); + + const { find } = service.start(startDeps()); + const results = find('foo', {}); + + expectObservable(results).toBe('a-b--(c|)', { + a: expectedBatch('P1'), + b: expectedBatch('P2'), + c: expectedBatch('S1', 'S2'), + }); + }); + }); + + it('handles the `aborted$` option', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('--a---(b|)', { + a: [providerResult('1')], + b: [providerResult('2')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const aborted$ = hot('----a--|', { a: undefined }); + + const { find } = service.start(startDeps()); + const results = find('foo', { aborted$ }); + + expectObservable(results).toBe('--a-|', { + a: expectedBatch('1'), + }); + }); + }); + + it('respects the timeout duration', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(100), + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('a 24ms b 100ms (c|)', { + a: [providerResult('1')], + b: [providerResult('2')], + c: [providerResult('3')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const { find } = service.start(startDeps()); + const results = find('foo', {}); + + expectObservable(results).toBe('a 24ms b 74ms |', { + a: expectedBatch('1'), + b: expectedBatch('2'), + }); + }); + }); + + it('only returns a given maximum number of results per provider', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(100), + maxProviderResults: 2, + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + registerResultProvider( + createProvider( + 'A', + hot('a---d-|', { + a: [providerResult('A1'), providerResult('A2')], + d: [providerResult('A3')], + }) + ) + ); + registerResultProvider( + createProvider( + 'B', + hot('-b-c| ', { + b: [providerResult('B1')], + c: [providerResult('B2'), providerResult('B3')], + }) + ) + ); + + const { find } = service.start(startDeps()); + const results = find('foo', {}); + + expectObservable(results).toBe('ab-(c|)', { + a: expectedBatch('A1', 'A2'), + b: expectedBatch('B1'), + c: expectedBatch('B2'), + }); + }); + }); + + it('process the results before returning them', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + const resultA = providerResult('A', { + type: 'application', + icon: 'appIcon', + score: 42, + title: 'foo', + url: '/foo/bar', + }); + const resultB = providerResult('B', { + type: 'dashboard', + score: 69, + title: 'bar', + url: { path: '/foo', prependBasePath: false }, + }); + + const provider = createProvider('A', of([resultA, resultB])); + registerResultProvider(provider); + + const { find } = service.start(startDeps()); + const batch = await find('foo', {}).pipe(take(1)).toPromise(); + + expect(batch.results).toHaveLength(2); + expect(batch.results[0]).toEqual({ + ...resultA, + url: '/base-path/foo/bar', + }); + expect(batch.results[1]).toEqual({ + ...resultB, + url: '/foo', + }); + }); + + it('emits an error when the license is invalid', async () => { + licenseChecker.getState.mockReturnValue({ valid: false, message: 'expired' }); + + const { registerResultProvider } = service.setup({ + config: createConfig(), + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('a-b-|', { + a: [providerResult('1')], + b: [providerResult('2')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const { find } = service.start(startDeps()); + const results = find('foo', {}); + + expectObservable(results).toBe( + '#', + {}, + GlobalSearchFindError.invalidLicense( + 'GlobalSearch API is disabled because of invalid license state: expired' + ) + ); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/global_search/public/services/search_service.ts b/x-pack/plugins/global_search/public/services/search_service.ts new file mode 100644 index 0000000000000..68970b75ad975 --- /dev/null +++ b/x-pack/plugins/global_search/public/services/search_service.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { merge, Observable, timer, throwError } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; +import { duration } from 'moment'; +import { i18n } from '@kbn/i18n'; +import { HttpStart } from 'src/core/public'; +import { GlobalSearchProviderResult, GlobalSearchBatchedResults } from '../../common/types'; +import { GlobalSearchFindError } from '../../common/errors'; +import { takeInArray } from '../../common/operators'; +import { processProviderResult } from '../../common/process_result'; +import { ILicenseChecker } from '../../common/license_checker'; +import { GlobalSearchResultProvider } from '../types'; +import { GlobalSearchClientConfigType } from '../config'; +import { GlobalSearchFindOptions } from './types'; +import { getDefaultPreference } from './utils'; +import { fetchServerResults } from './fetch_server_results'; + +/** @public */ +export interface SearchServiceSetup { + /** + * Register a result provider to be used by the search service. + * + * @example + * ```ts + * setupDeps.globalSearch.registerResultProvider({ + * id: 'my_provider', + * find: (term, options) => { + * const resultPromise = myService.search(term, options); + * return from(resultPromise).pipe(takeUntil(options.aborted$); + * }, + * }); + * ``` + * + * @remarks + * As results from providers registered from the client-side API will not be available from the server's `find` API, + * registering result providers from the client should only be done when returning results that would not be retrievable + * from the server-side. In any other situation, prefer registering your provider from the server-side instead. + */ + registerResultProvider(provider: GlobalSearchResultProvider): void; +} + +/** @public */ +export interface SearchServiceStart { + /** + * Perform a search for given `term` and {@link GlobalSearchFindOptions | options}. + * + * @example + * ```ts + * startDeps.globalSearch.find('some term').subscribe({ + * next: ({ results }) => { + * addNewResultsToList(results); + * }, + * error: () => {}, + * complete: () => { + * showAsyncSearchIndicator(false); + * } + * }); + * ``` + * + * @remarks + * Emissions from the resulting observable will only contains **new** results. It is the consumer's + * responsibility to aggregate the emission and sort the results if required. + */ + find(term: string, options: GlobalSearchFindOptions): Observable; +} + +interface SetupDeps { + config: GlobalSearchClientConfigType; + maxProviderResults?: number; +} + +interface StartDeps { + http: HttpStart; + licenseChecker: ILicenseChecker; +} + +const defaultMaxProviderResults = 20; +const mapToUndefined = () => undefined; + +/** @internal */ +export class SearchService { + private readonly providers = new Map(); + private config?: GlobalSearchClientConfigType; + private http?: HttpStart; + private maxProviderResults = defaultMaxProviderResults; + private licenseChecker?: ILicenseChecker; + + setup({ config, maxProviderResults = defaultMaxProviderResults }: SetupDeps): SearchServiceSetup { + this.config = config; + + this.maxProviderResults = maxProviderResults; + + return { + registerResultProvider: (provider) => { + if (this.providers.has(provider.id)) { + throw new Error(`trying to register duplicate provider: ${provider.id}`); + } + this.providers.set(provider.id, provider); + }, + }; + } + + start({ http, licenseChecker }: StartDeps): SearchServiceStart { + this.http = http; + this.licenseChecker = licenseChecker; + + return { + find: (term, options) => this.performFind(term, options), + }; + } + + private performFind(term: string, options: GlobalSearchFindOptions) { + const licenseState = this.licenseChecker!.getState(); + if (!licenseState.valid) { + return throwError( + GlobalSearchFindError.invalidLicense( + i18n.translate('xpack.globalSearch.find.invalidLicenseError', { + defaultMessage: `GlobalSearch API is disabled because of invalid license state: {errorMessage}`, + values: { errorMessage: licenseState.message }, + }) + ) + ); + } + + const timeout = duration(this.config!.search_timeout).asMilliseconds(); + const timeout$ = timer(timeout).pipe(map(mapToUndefined)); + const aborted$ = options.aborted$ ? merge(options.aborted$, timeout$) : timeout$; + const preference = options.preference ?? getDefaultPreference(); + + const providerOptions = { + ...options, + preference, + maxResults: this.maxProviderResults, + aborted$, + }; + + const processResult = (result: GlobalSearchProviderResult) => + processProviderResult(result, this.http!.basePath); + + const serverResults$ = fetchServerResults(this.http!, term, { + preference, + aborted$, + }); + + const providersResults$ = [...this.providers.values()].map((provider) => + provider.find(term, providerOptions).pipe( + takeInArray(this.maxProviderResults), + takeUntil(aborted$), + map((results) => results.map((r) => processResult(r))) + ) + ); + + return merge(...providersResults$, serverResults$).pipe( + map((results) => ({ + results, + })) + ); + } +} diff --git a/x-pack/plugins/global_search/public/services/types.ts b/x-pack/plugins/global_search/public/services/types.ts new file mode 100644 index 0000000000000..fcaa8f0545a6e --- /dev/null +++ b/x-pack/plugins/global_search/public/services/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; + +/** + * Options for the server-side {@link GlobalSearchPluginStart.find | find API} + */ +export interface GlobalSearchFindOptions { + /** + * A custom preference token associated with a search 'session' that should be used to get consistent scoring + * when performing calls to ES. Can also be used as a 'session' token for providers returning data from elsewhere + * than an elasticsearch cluster. + * + * If not specified, a random token will be generated and used. The token is stored in the sessionStorage and is guaranteed + * to be consistent during a given http 'session' + */ + preference?: string; + /** + * Optional observable to notify that the associated `find` call should be canceled. + * If/when provided and emitting, the result observable will be completed and no further result emission will be performed. + */ + aborted$?: Observable; +} diff --git a/x-pack/plugins/global_search/public/services/utils.test.ts b/x-pack/plugins/global_search/public/services/utils.test.ts new file mode 100644 index 0000000000000..f69fb1d2fd825 --- /dev/null +++ b/x-pack/plugins/global_search/public/services/utils.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { StubBrowserStorage } from '../../../../../src/test_utils/public/stub_browser_storage'; +import { getDefaultPreference } from './utils'; + +describe('getDefaultPreference', () => { + let storage: Storage; + let getItemSpy: jest.SpyInstance; + let setItemSpy: jest.SpyInstance; + + beforeEach(() => { + storage = new StubBrowserStorage(); + getItemSpy = jest.spyOn(storage, 'getItem'); + setItemSpy = jest.spyOn(storage, 'setItem'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('returns the value in storage when available', () => { + getItemSpy.mockReturnValue('foo_pref'); + + const pref = getDefaultPreference(storage); + + expect(pref).toEqual('foo_pref'); + expect(getItemSpy).toHaveBeenCalledTimes(1); + expect(setItemSpy).not.toHaveBeenCalled(); + }); + + it('sets the value to the storage and return it when not already present', () => { + getItemSpy.mockReturnValue(null); + + const returnedPref = getDefaultPreference(storage); + + expect(getItemSpy).toHaveBeenCalledTimes(1); + expect(setItemSpy).toHaveBeenCalledTimes(1); + + const storedPref = setItemSpy.mock.calls[0][1]; + + expect(storage.length).toBe(1); + expect(storage.key(0)).toBe('globalSearch:defaultPref'); + expect(storedPref).toEqual(returnedPref); + }); +}); diff --git a/x-pack/plugins/global_search/public/services/utils.ts b/x-pack/plugins/global_search/public/services/utils.ts new file mode 100644 index 0000000000000..45d2ba7d7c210 --- /dev/null +++ b/x-pack/plugins/global_search/public/services/utils.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uuid from 'uuid'; + +const defaultPrefStorageKey = 'globalSearch:defaultPref'; + +/** + * Returns the default {@link GlobalSearchFindOptions.preference | preference} value. + * + * The implementation is based on the sessionStorage, which ensure the default value for a session/tab will remain the same. + */ +export const getDefaultPreference = (storage: Storage = window.sessionStorage): string => { + let pref = storage.getItem(defaultPrefStorageKey); + if (pref) { + return pref; + } + pref = uuid.v4(); + storage.setItem(defaultPrefStorageKey, pref); + return pref; +}; diff --git a/x-pack/plugins/global_search/public/types.ts b/x-pack/plugins/global_search/public/types.ts new file mode 100644 index 0000000000000..42ef234504d12 --- /dev/null +++ b/x-pack/plugins/global_search/public/types.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { GlobalSearchProviderFindOptions, GlobalSearchProviderResult } from '../common/types'; +import { SearchServiceSetup, SearchServiceStart } from './services'; + +export type GlobalSearchPluginSetup = Pick; +export type GlobalSearchPluginStart = Pick; + +/** + * GlobalSearch result provider, to be registered using the {@link GlobalSearchPluginSetup | global search API} + */ +export interface GlobalSearchResultProvider { + /** + * id of the provider + */ + id: string; + /** + * Method that should return an observable used to emit new results from the provider. + * + * See {@GlobalSearchProviderResult | the result type} for the expected result structure. + * + * @example + * ```ts + * // returning all results in a single batch + * setupDeps.globalSearch.registerResultProvider({ + * id: 'my_provider', + * find: (term, { aborted$, preference, maxResults }, context) => { + * const resultPromise = myService.search(term, { preference, maxResults }, context.core.savedObjects.client); + * return from(resultPromise).pipe(takeUntil(aborted$)); + * }, + * }); + * ``` + */ + find( + term: string, + options: GlobalSearchProviderFindOptions + ): Observable; +} diff --git a/x-pack/plugins/global_search/server/config.ts b/x-pack/plugins/global_search/server/config.ts new file mode 100644 index 0000000000000..33ff45595b912 --- /dev/null +++ b/x-pack/plugins/global_search/server/config.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from 'kibana/server'; + +const configSchema = schema.object({ + search_timeout: schema.duration({ defaultValue: '30s' }), +}); + +export type GlobalSearchConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + exposeToBrowser: { + search_timeout: true, + }, +}; diff --git a/x-pack/plugins/global_search/server/index.ts b/x-pack/plugins/global_search/server/index.ts new file mode 100644 index 0000000000000..82f7c80dca552 --- /dev/null +++ b/x-pack/plugins/global_search/server/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializer } from 'src/core/server'; +import { + GlobalSearchPlugin, + GlobalSearchPluginSetupDeps, + GlobalSearchPluginStartDeps, +} from './plugin'; +import { GlobalSearchPluginSetup, GlobalSearchPluginStart } from './types'; + +export const plugin: PluginInitializer< + GlobalSearchPluginSetup, + GlobalSearchPluginStart, + GlobalSearchPluginSetupDeps, + GlobalSearchPluginStartDeps +> = (context) => new GlobalSearchPlugin(context); + +export { config } from './config'; + +export { + GlobalSearchBatchedResults, + GlobalSearchProviderFindOptions, + GlobalSearchProviderResult, + GlobalSearchProviderResultUrl, + GlobalSearchResult, +} from '../common/types'; +export { + GlobalSearchFindOptions, + GlobalSearchProviderContext, + GlobalSearchPluginStart, + GlobalSearchPluginSetup, + GlobalSearchResultProvider, + RouteHandlerGlobalSearchContext, +} from './types'; diff --git a/x-pack/plugins/global_search/server/mocks.ts b/x-pack/plugins/global_search/server/mocks.ts new file mode 100644 index 0000000000000..8a189a5701708 --- /dev/null +++ b/x-pack/plugins/global_search/server/mocks.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { of } from 'rxjs'; +import { + GlobalSearchPluginSetup, + GlobalSearchPluginStart, + RouteHandlerGlobalSearchContext, +} from './types'; +import { searchServiceMock } from './services/search_service.mock'; + +const createSetupMock = (): jest.Mocked => { + const searchMock = searchServiceMock.createSetupContract(); + + return { + registerResultProvider: searchMock.registerResultProvider, + }; +}; + +const createStartMock = (): jest.Mocked => { + const searchMock = searchServiceMock.createStartContract(); + + return { + find: searchMock.find, + }; +}; + +const createRouteHandlerContextMock = (): jest.Mocked => { + const contextMock = { + find: jest.fn(), + }; + + contextMock.find.mockReturnValue(of([])); + + return contextMock; +}; + +export const globalSearchPluginMock = { + createSetupContract: createSetupMock, + createStartContract: createStartMock, + createRouteHandlerContext: createRouteHandlerContextMock, +}; diff --git a/x-pack/plugins/global_search/server/plugin.test.mocks.ts b/x-pack/plugins/global_search/server/plugin.test.mocks.ts new file mode 100644 index 0000000000000..1223b1ec20389 --- /dev/null +++ b/x-pack/plugins/global_search/server/plugin.test.mocks.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const registerRoutesMock = jest.fn(); +jest.doMock('./routes', () => ({ + registerRoutes: registerRoutesMock, +})); diff --git a/x-pack/plugins/global_search/server/plugin.test.ts b/x-pack/plugins/global_search/server/plugin.test.ts new file mode 100644 index 0000000000000..e654dbfdc158a --- /dev/null +++ b/x-pack/plugins/global_search/server/plugin.test.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { registerRoutesMock } from './plugin.test.mocks'; + +import { coreMock } from '../../../../src/core/server/mocks'; +import { GlobalSearchPlugin } from './plugin'; + +describe('GlobalSearchPlugin', () => { + let plugin: GlobalSearchPlugin; + + beforeEach(() => { + plugin = new GlobalSearchPlugin(coreMock.createPluginInitializerContext()); + }); + + it('registers routes during `setup`', async () => { + await plugin.setup(coreMock.createSetup()); + expect(registerRoutesMock).toHaveBeenCalledTimes(1); + }); + + it('registers the globalSearch route handler context', async () => { + const coreSetup = coreMock.createSetup(); + await plugin.setup(coreSetup); + expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1); + expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledWith( + 'globalSearch', + expect.any(Function) + ); + }); +}); diff --git a/x-pack/plugins/global_search/server/plugin.ts b/x-pack/plugins/global_search/server/plugin.ts new file mode 100644 index 0000000000000..87e7f96b34c0c --- /dev/null +++ b/x-pack/plugins/global_search/server/plugin.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server'; +import { LicensingPluginStart } from '../../licensing/server'; +import { LicenseChecker, ILicenseChecker } from '../common/license_checker'; +import { SearchService, SearchServiceStart } from './services'; +import { registerRoutes } from './routes'; +import { + GlobalSearchPluginSetup, + GlobalSearchPluginStart, + RouteHandlerGlobalSearchContext, +} from './types'; +import { GlobalSearchConfigType } from './config'; + +declare module 'src/core/server' { + interface RequestHandlerContext { + globalSearch?: RouteHandlerGlobalSearchContext; + } +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface GlobalSearchPluginSetupDeps {} +export interface GlobalSearchPluginStartDeps { + licensing: LicensingPluginStart; +} + +export class GlobalSearchPlugin + implements + Plugin< + GlobalSearchPluginSetup, + GlobalSearchPluginStart, + GlobalSearchPluginSetupDeps, + GlobalSearchPluginStartDeps + > { + private readonly config$: Observable; + private readonly searchService = new SearchService(); + private searchServiceStart?: SearchServiceStart; + private licenseChecker?: ILicenseChecker; + + constructor(context: PluginInitializerContext) { + this.config$ = context.config.create(); + } + + public async setup(core: CoreSetup<{}, GlobalSearchPluginStart>) { + const config = await this.config$.pipe(take(1)).toPromise(); + const { registerResultProvider } = this.searchService.setup({ + basePath: core.http.basePath, + config, + }); + + registerRoutes(core.http.createRouter()); + + core.http.registerRouteHandlerContext('globalSearch', (_, req) => { + return { + find: (term, options) => this.searchServiceStart!.find(term, options, req), + }; + }); + + return { + registerResultProvider, + }; + } + + public start(core: CoreStart, { licensing }: GlobalSearchPluginStartDeps) { + this.licenseChecker = new LicenseChecker(licensing.license$); + this.searchServiceStart = this.searchService.start({ + core, + licenseChecker: this.licenseChecker, + }); + return { + find: this.searchServiceStart.find, + }; + } + + public stop() { + if (this.licenseChecker) { + this.licenseChecker.clean(); + } + } +} diff --git a/x-pack/plugins/global_search/server/routes/find.ts b/x-pack/plugins/global_search/server/routes/find.ts new file mode 100644 index 0000000000000..a9063abda0e3e --- /dev/null +++ b/x-pack/plugins/global_search/server/routes/find.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { reduce, map } from 'rxjs/operators'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; +import { GlobalSearchFindError } from '../../common/errors'; + +export const registerInternalFindRoute = (router: IRouter) => { + router.post( + { + path: '/internal/global_search/find', + validate: { + body: schema.object({ + term: schema.string(), + options: schema.maybe( + schema.object({ + preference: schema.maybe(schema.string()), + }) + ), + }), + }, + }, + async (ctx, req, res) => { + const { term, options } = req.body; + try { + const allResults = await ctx + .globalSearch!.find(term, { ...options, aborted$: req.events.aborted$ }) + .pipe( + map((batch) => batch.results), + reduce((acc, results) => [...acc, ...results]) + ) + .toPromise(); + return res.ok({ + body: { + results: allResults, + }, + }); + } catch (e) { + if (e instanceof GlobalSearchFindError && e.type === 'invalid-license') { + return res.forbidden({ body: e.message }); + } + throw e; + } + } + ); +}; diff --git a/x-pack/plugins/global_search/server/routes/index.test.ts b/x-pack/plugins/global_search/server/routes/index.test.ts new file mode 100644 index 0000000000000..64675bc13cb1c --- /dev/null +++ b/x-pack/plugins/global_search/server/routes/index.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { httpServiceMock } from '../../../../../src/core/server/mocks'; +import { registerRoutes } from './index'; + +describe('registerRoutes', () => { + it('foo', () => { + const router = httpServiceMock.createRouter(); + + registerRoutes(router); + + expect(router.post).toHaveBeenCalledTimes(1); + + expect(router.post).toHaveBeenCalledWith( + expect.objectContaining({ + path: '/internal/global_search/find', + }), + expect.any(Function) + ); + + expect(router.get).toHaveBeenCalledTimes(0); + expect(router.delete).toHaveBeenCalledTimes(0); + expect(router.put).toHaveBeenCalledTimes(0); + }); +}); diff --git a/x-pack/plugins/global_search/server/routes/index.ts b/x-pack/plugins/global_search/server/routes/index.ts new file mode 100644 index 0000000000000..7840b95614993 --- /dev/null +++ b/x-pack/plugins/global_search/server/routes/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'src/core/server'; +import { registerInternalFindRoute } from './find'; + +export const registerRoutes = (router: IRouter) => { + registerInternalFindRoute(router); +}; diff --git a/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts b/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts new file mode 100644 index 0000000000000..878e4ac896b96 --- /dev/null +++ b/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { of, throwError } from 'rxjs'; +import supertest from 'supertest'; +import { UnwrapPromise } from '@kbn/utility-types'; +import { setupServer } from '../../../../../../src/core/server/test_utils'; +import { GlobalSearchResult, GlobalSearchBatchedResults } from '../../../common/types'; +import { GlobalSearchFindError } from '../../../common/errors'; +import { globalSearchPluginMock } from '../../mocks'; +import { registerInternalFindRoute } from '../find'; + +type setupServerReturn = UnwrapPromise>; +const pluginId = Symbol('globalSearch'); + +const createResult = (id: string): GlobalSearchResult => ({ + id, + title: id, + type: 'test', + url: `/app/test/${id}`, + score: 42, +}); + +const createBatch = (...ids: string[]): GlobalSearchBatchedResults => ({ + results: ids.map(createResult), +}); + +const expectedResults = (...ids: string[]) => ids.map((id) => expect.objectContaining({ id })); + +describe('POST /internal/global_search/find', () => { + let server: setupServerReturn['server']; + let httpSetup: setupServerReturn['httpSetup']; + let globalSearchHandlerContext: ReturnType; + + beforeEach(async () => { + ({ server, httpSetup } = await setupServer(pluginId)); + + globalSearchHandlerContext = globalSearchPluginMock.createRouteHandlerContext(); + httpSetup.registerRouteHandlerContext( + pluginId, + 'globalSearch', + () => globalSearchHandlerContext + ); + + const router = httpSetup.createRouter('/'); + + registerInternalFindRoute(router); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('calls the handler context with correct parameters', async () => { + await supertest(httpSetup.server.listener) + .post('/internal/global_search/find') + .send({ + term: 'search', + options: { + preference: 'custom-pref', + }, + }) + .expect(200); + + expect(globalSearchHandlerContext.find).toHaveBeenCalledTimes(1); + expect(globalSearchHandlerContext.find).toHaveBeenCalledWith('search', { + preference: 'custom-pref', + aborted$: expect.any(Object), + }); + }); + + it('returns all the results returned from the service', async () => { + globalSearchHandlerContext.find.mockReturnValue( + of(createBatch('1', '2'), createBatch('3', '4')) + ); + + const response = await supertest(httpSetup.server.listener) + .post('/internal/global_search/find') + .send({ + term: 'search', + }) + .expect(200); + + expect(response.body).toEqual({ + results: expectedResults('1', '2', '3', '4'), + }); + }); + + it('returns a 403 when the observable throws an invalid-license error', async () => { + globalSearchHandlerContext.find.mockReturnValue( + throwError(GlobalSearchFindError.invalidLicense('invalid-license-message')) + ); + + const response = await supertest(httpSetup.server.listener) + .post('/internal/global_search/find') + .send({ + term: 'search', + }) + .expect(403); + + expect(response.body).toEqual( + expect.objectContaining({ + message: 'invalid-license-message', + statusCode: 403, + }) + ); + }); + + it('returns the default error when the observable throws any other error', async () => { + globalSearchHandlerContext.find.mockReturnValue(throwError('any-error')); + + const response = await supertest(httpSetup.server.listener) + .post('/internal/global_search/find') + .send({ + term: 'search', + }) + .expect(500); + + expect(response.body).toEqual( + expect.objectContaining({ + message: 'An internal server error occurred.', + statusCode: 500, + }) + ); + }); +}); diff --git a/x-pack/plugins/global_search/server/services/context.test.ts b/x-pack/plugins/global_search/server/services/context.test.ts new file mode 100644 index 0000000000000..397a1ea170349 --- /dev/null +++ b/x-pack/plugins/global_search/server/services/context.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { httpServerMock, coreMock } from '../../../../../src/core/server/mocks'; +import { getContextFactory } from './context'; + +describe('getContextFactory', () => { + it('returns a GlobalSearchProviderContext bound to the request', () => { + const coreStart = coreMock.createStart(); + const request = httpServerMock.createKibanaRequest(); + + const factory = getContextFactory(coreStart); + const context = factory(request); + + expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledTimes(1); + expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledWith(request); + + expect(coreStart.savedObjects.getTypeRegistry).toHaveBeenCalledTimes(1); + + expect(coreStart.elasticsearch.legacy.client.asScoped).toHaveBeenCalledTimes(1); + expect(coreStart.elasticsearch.legacy.client.asScoped).toHaveBeenCalledWith(request); + + const soClient = coreStart.savedObjects.getScopedClient.mock.results[0].value; + expect(coreStart.uiSettings.asScopedToClient).toHaveBeenCalledTimes(1); + expect(coreStart.uiSettings.asScopedToClient).toHaveBeenCalledWith(soClient); + + expect(context).toEqual({ + core: { + savedObjects: expect.any(Object), + elasticsearch: expect.any(Object), + uiSettings: expect.any(Object), + }, + }); + }); +}); diff --git a/x-pack/plugins/global_search/server/services/context.ts b/x-pack/plugins/global_search/server/services/context.ts new file mode 100644 index 0000000000000..b15deccaae018 --- /dev/null +++ b/x-pack/plugins/global_search/server/services/context.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, KibanaRequest } from 'src/core/server'; +import { GlobalSearchProviderContext } from '../types'; + +export type GlobalSearchContextFactory = (request: KibanaRequest) => GlobalSearchProviderContext; + +/** + * {@link GlobalSearchProviderContext | context} factory + */ +export const getContextFactory = (coreStart: CoreStart) => ( + request: KibanaRequest +): GlobalSearchProviderContext => { + const soClient = coreStart.savedObjects.getScopedClient(request); + return { + core: { + savedObjects: { + client: soClient, + typeRegistry: coreStart.savedObjects.getTypeRegistry(), + }, + elasticsearch: { + legacy: { + client: coreStart.elasticsearch.legacy.client.asScoped(request), + }, + }, + uiSettings: { + client: coreStart.uiSettings.asScopedToClient(soClient), + }, + }, + }; +}; diff --git a/x-pack/plugins/global_search/server/services/index.ts b/x-pack/plugins/global_search/server/services/index.ts new file mode 100644 index 0000000000000..cee5b24d2f588 --- /dev/null +++ b/x-pack/plugins/global_search/server/services/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SearchService, SearchServiceSetup, SearchServiceStart } from './search_service'; diff --git a/x-pack/plugins/global_search/server/services/search_service.mock.ts b/x-pack/plugins/global_search/server/services/search_service.mock.ts new file mode 100644 index 0000000000000..eca69148288b9 --- /dev/null +++ b/x-pack/plugins/global_search/server/services/search_service.mock.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchServiceSetup, SearchServiceStart } from './search_service'; +import { of } from 'rxjs'; + +const createSetupMock = (): jest.Mocked => { + return { + registerResultProvider: jest.fn(), + }; +}; + +const createStartMock = (): jest.Mocked => { + const mock = { + find: jest.fn(), + }; + mock.find.mockReturnValue(of({ results: [] })); + + return mock; +}; + +export const searchServiceMock = { + createSetupContract: createSetupMock, + createStartContract: createStartMock, +}; diff --git a/x-pack/plugins/global_search/server/services/search_service.test.ts b/x-pack/plugins/global_search/server/services/search_service.test.ts new file mode 100644 index 0000000000000..fd705b4286680 --- /dev/null +++ b/x-pack/plugins/global_search/server/services/search_service.test.ts @@ -0,0 +1,323 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable, of } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { TestScheduler } from 'rxjs/testing'; +import { duration } from 'moment'; +import { httpServiceMock, httpServerMock, coreMock } from '../../../../../src/core/server/mocks'; +import { licenseCheckerMock } from '../../common/license_checker.mock'; +import { GlobalSearchProviderResult } from '../../common/types'; +import { GlobalSearchFindError } from '../../common/errors'; +import { GlobalSearchConfigType } from '../config'; +import { GlobalSearchResultProvider } from '../types'; +import { SearchService } from './search_service'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + +describe('SearchService', () => { + let service: SearchService; + let basePath: ReturnType; + let coreStart: ReturnType; + let licenseChecker: ReturnType; + let request: ReturnType; + + const createConfig = (timeoutMs: number = 30000): GlobalSearchConfigType => { + return { + search_timeout: duration(timeoutMs), + }; + }; + + const createProvider = ( + id: string, + source: Observable = of([]) + ): jest.Mocked => ({ + id, + find: jest.fn().mockImplementation((term, options, context) => source), + }); + + const expectedResult = (id: string) => expect.objectContaining({ id }); + + const expectedBatch = (...ids: string[]) => ({ + results: ids.map((id) => expectedResult(id)), + }); + + const result = ( + id: string, + parts: Partial = {} + ): GlobalSearchProviderResult => ({ + title: id, + type: 'test', + url: '/foo/bar', + score: 100, + ...parts, + id, + }); + + beforeEach(() => { + service = new SearchService(); + basePath = httpServiceMock.createBasePath(); + basePath.prepend.mockImplementation((path) => `/base-path${path}`); + coreStart = coreMock.createStart(); + licenseChecker = licenseCheckerMock.create(); + }); + + describe('#setup()', () => { + describe('#registerResultProvider()', () => { + it('throws when trying to register the same provider twice', () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + basePath, + }); + + const provider = createProvider('A'); + registerResultProvider(provider); + expect(() => { + registerResultProvider(provider); + }).toThrowErrorMatchingInlineSnapshot(`"trying to register duplicate provider: A"`); + }); + }); + }); + + describe('#start()', () => { + describe('#find()', () => { + it('calls the provider with the correct parameters', () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + basePath, + }); + + const provider = createProvider('A'); + registerResultProvider(provider); + + const { find } = service.start({ core: coreStart, licenseChecker }); + find('foobar', { preference: 'pref' }, request); + + expect(provider.find).toHaveBeenCalledTimes(1); + expect(provider.find).toHaveBeenCalledWith( + 'foobar', + expect.objectContaining({ preference: 'pref' }), + expect.objectContaining({ core: expect.any(Object) }) + ); + }); + + it('return the results from the provider', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + basePath, + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('a-b-|', { + a: [result('1')], + b: [result('2')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const { find } = service.start({ core: coreStart, licenseChecker }); + const results = find('foo', {}, request); + + expectObservable(results).toBe('a-b-|', { + a: expectedBatch('1'), + b: expectedBatch('2'), + }); + }); + }); + + it('handles multiple providers', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + basePath, + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + registerResultProvider( + createProvider( + 'A', + hot('a---d-|', { + a: [result('A1'), result('A2')], + d: [result('A3')], + }) + ) + ); + registerResultProvider( + createProvider( + 'B', + hot('-b-c| ', { + b: [result('B1')], + c: [result('B2'), result('B3')], + }) + ) + ); + + const { find } = service.start({ core: coreStart, licenseChecker }); + const results = find('foo', {}, request); + + expectObservable(results).toBe('ab-cd-|', { + a: expectedBatch('A1', 'A2'), + b: expectedBatch('B1'), + c: expectedBatch('B2', 'B3'), + d: expectedBatch('A3'), + }); + }); + }); + + it('handles the `aborted$` option', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + basePath, + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('--a---(b|)', { + a: [result('1')], + b: [result('2')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const aborted$ = hot('----a--|', { a: undefined }); + + const { find } = service.start({ core: coreStart, licenseChecker }); + const results = find('foo', { aborted$ }, request); + + expectObservable(results).toBe('--a-|', { + a: expectedBatch('1'), + }); + }); + }); + + it('respects the timeout duration', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(100), + basePath, + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('a 24ms b 100ms (c|)', { + a: [result('1')], + b: [result('2')], + c: [result('3')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const { find } = service.start({ core: coreStart, licenseChecker }); + const results = find('foo', {}, request); + + expectObservable(results).toBe('a 24ms b 74ms |', { + a: expectedBatch('1'), + b: expectedBatch('2'), + }); + }); + }); + + it('only returns a given maximum number of results per provider', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(100), + basePath, + maxProviderResults: 2, + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + registerResultProvider( + createProvider( + 'A', + hot('a---d-|', { + a: [result('A1'), result('A2')], + d: [result('A3')], + }) + ) + ); + registerResultProvider( + createProvider( + 'B', + hot('-b-c| ', { + b: [result('B1')], + c: [result('B2'), result('B3')], + }) + ) + ); + + const { find } = service.start({ core: coreStart, licenseChecker }); + const results = find('foo', {}, request); + + expectObservable(results).toBe('ab-(c|)', { + a: expectedBatch('A1', 'A2'), + b: expectedBatch('B1'), + c: expectedBatch('B2'), + }); + }); + }); + + it('process the results before returning them', async () => { + const { registerResultProvider } = service.setup({ + config: createConfig(), + basePath, + }); + + const resultA = result('A', { + type: 'application', + icon: 'appIcon', + score: 42, + title: 'foo', + url: '/foo/bar', + }); + const resultB = result('B', { + type: 'dashboard', + score: 69, + title: 'bar', + url: { path: '/foo', prependBasePath: false }, + }); + + const provider = createProvider('A', of([resultA, resultB])); + registerResultProvider(provider); + + const { find } = service.start({ core: coreStart, licenseChecker }); + const batch = await find('foo', {}, request).pipe(take(1)).toPromise(); + + expect(batch.results).toHaveLength(2); + expect(batch.results[0]).toEqual({ + ...resultA, + url: '/base-path/foo/bar', + }); + expect(batch.results[1]).toEqual({ + ...resultB, + url: '/foo', + }); + }); + + it('emits an error when the license is invalid', async () => { + licenseChecker.getState.mockReturnValue({ valid: false, message: 'expired' }); + + const { registerResultProvider } = service.setup({ + config: createConfig(), + basePath, + }); + + getTestScheduler().run(({ expectObservable, hot }) => { + const providerResults = hot('a-b-|', { + a: [result('1')], + b: [result('2')], + }); + registerResultProvider(createProvider('A', providerResults)); + + const { find } = service.start({ core: coreStart, licenseChecker }); + const results = find('foo', {}, request); + + expectObservable(results).toBe( + '#', + {}, + GlobalSearchFindError.invalidLicense( + 'GlobalSearch API is disabled because of invalid license state: expired' + ) + ); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/global_search/server/services/search_service.ts b/x-pack/plugins/global_search/server/services/search_service.ts new file mode 100644 index 0000000000000..12eada2a1385e --- /dev/null +++ b/x-pack/plugins/global_search/server/services/search_service.ts @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable, timer, merge, throwError } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; +import { KibanaRequest, CoreStart, IBasePath } from 'src/core/server'; +import { GlobalSearchProviderResult, GlobalSearchBatchedResults } from '../../common/types'; +import { GlobalSearchFindError } from '../../common/errors'; +import { takeInArray } from '../../common/operators'; +import { ILicenseChecker } from '../../common/license_checker'; + +import { processProviderResult } from '../../common/process_result'; +import { GlobalSearchConfigType } from '../config'; +import { getContextFactory, GlobalSearchContextFactory } from './context'; +import { GlobalSearchResultProvider, GlobalSearchFindOptions } from '../types'; + +/** @public */ +export interface SearchServiceSetup { + /** + * Register a result provider to be used by the search service. + * + * @example + * ```ts + * setupDeps.globalSearch.registerResultProvider({ + * id: 'my_provider', + * find: (term, options, context) => { + * const resultPromise = myService.search(term, options, context.core.savedObjects.client); + * return from(resultPromise).pipe(takeUntil(options.aborted$); + * }, + * }); + * ``` + */ + registerResultProvider(provider: GlobalSearchResultProvider): void; +} + +/** @public */ +export interface SearchServiceStart { + /** + * Perform a search for given `term` and {@link GlobalSearchFindOptions | options}. + * + * @example + * ```ts + * startDeps.globalSearch.find('some term').subscribe({ + * next: ({ results }) => { + * addNewResultsToList(results); + * }, + * error: () => {}, + * complete: () => { + * showAsyncSearchIndicator(false); + * } + * }); + * ``` + * + * @remarks + * - Emissions from the resulting observable will only contains **new** results. It is the consumer + * responsibility to aggregate the emission and sort the results if required. + * - Results from the client-side registered providers will not available when performing a search + * from the server-side `find` API. + */ + find( + term: string, + options: GlobalSearchFindOptions, + request: KibanaRequest + ): Observable; +} + +interface SetupDeps { + basePath: IBasePath; + config: GlobalSearchConfigType; + maxProviderResults?: number; +} + +interface StartDeps { + core: CoreStart; + licenseChecker: ILicenseChecker; +} + +const defaultMaxProviderResults = 20; +const mapToUndefined = () => undefined; + +/** @internal */ +export class SearchService { + private readonly providers = new Map(); + private basePath?: IBasePath; + private config?: GlobalSearchConfigType; + private contextFactory?: GlobalSearchContextFactory; + private licenseChecker?: ILicenseChecker; + private maxProviderResults = defaultMaxProviderResults; + + setup({ + basePath, + config, + maxProviderResults = defaultMaxProviderResults, + }: SetupDeps): SearchServiceSetup { + this.basePath = basePath; + this.config = config; + this.maxProviderResults = maxProviderResults; + + return { + registerResultProvider: (provider) => { + if (this.providers.has(provider.id)) { + throw new Error(`trying to register duplicate provider: ${provider.id}`); + } + this.providers.set(provider.id, provider); + }, + }; + } + + start({ core, licenseChecker }: StartDeps): SearchServiceStart { + this.licenseChecker = licenseChecker; + this.contextFactory = getContextFactory(core); + return { + find: (term, options, request) => this.performFind(term, options, request), + }; + } + + private performFind(term: string, options: GlobalSearchFindOptions, request: KibanaRequest) { + const licenseState = this.licenseChecker!.getState(); + if (!licenseState.valid) { + return throwError( + GlobalSearchFindError.invalidLicense( + i18n.translate('xpack.globalSearch.find.invalidLicenseError', { + defaultMessage: `GlobalSearch API is disabled because of invalid license state: {errorMessage}`, + values: { errorMessage: licenseState.message }, + }) + ) + ); + } + + const context = this.contextFactory!(request); + + const timeout$ = timer(this.config!.search_timeout.asMilliseconds()).pipe(map(mapToUndefined)); + const aborted$ = options.aborted$ ? merge(options.aborted$, timeout$) : timeout$; + const providerOptions = { + ...options, + preference: options.preference ?? 'default', + maxResults: this.maxProviderResults, + aborted$, + }; + + const processResult = (result: GlobalSearchProviderResult) => + processProviderResult(result, this.basePath!); + + const providersResults$ = [...this.providers.values()].map((provider) => + provider.find(term, providerOptions, context).pipe( + takeInArray(this.maxProviderResults), + takeUntil(aborted$), + map((results) => results.map((r) => processResult(r))) + ) + ); + + return merge(...providersResults$).pipe( + map((results) => ({ + results, + })) + ); + } +} diff --git a/x-pack/plugins/global_search/server/types.ts b/x-pack/plugins/global_search/server/types.ts new file mode 100644 index 0000000000000..eca4aff366883 --- /dev/null +++ b/x-pack/plugins/global_search/server/types.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { + ISavedObjectTypeRegistry, + IScopedClusterClient, + IUiSettingsClient, + SavedObjectsClientContract, +} from 'src/core/server'; +import { + GlobalSearchBatchedResults, + GlobalSearchProviderFindOptions, + GlobalSearchProviderResult, +} from '../common/types'; +import { SearchServiceSetup, SearchServiceStart } from './services'; + +export type GlobalSearchPluginSetup = Pick; +export type GlobalSearchPluginStart = Pick; + +/** + * globalSearch route handler context. + * + * @public + */ +export interface RouteHandlerGlobalSearchContext { + /** + * See {@link SearchServiceStart.find | the find API} + */ + find(term: string, options: GlobalSearchFindOptions): Observable; +} + +/** + * Context passed to server-side {@GlobalSearchResultProvider | result provider}'s `find` method. + * + * @public + */ +export interface GlobalSearchProviderContext { + core: { + savedObjects: { + client: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; + }; + elasticsearch: { + legacy: { + client: IScopedClusterClient; + }; + }; + uiSettings: { + client: IUiSettingsClient; + }; + }; +} + +/** + * Options for the server-side {@link GlobalSearchPluginStart.find | find API} + * + * @public + */ +export interface GlobalSearchFindOptions { + /** + * A custom preference token associated with a search 'session' that should be used to get consistent scoring + * when performing calls to ES. Can also be used as a 'session' token for providers returning data from elsewhere + * than an elasticsearch cluster. + * If not specified, a random token will be generated and used. + */ + preference?: string; + /** + * Optional observable to notify that the associated `find` call should be canceled. + * If/when provided and emitting, no further result emission will be performed and the result observable will be completed. + */ + aborted$?: Observable; +} + +/** + * GlobalSearch result provider, to be registered using the {@link GlobalSearchPluginSetup | global search API} + * + * @public + */ +export interface GlobalSearchResultProvider { + /** + * id of the provider + */ + id: string; + /** + * Method that should return an observable used to emit new results from the provider. + * + * See {@GlobalSearchProviderResult | the result type} for the expected result structure. + * + * @example + * ```ts + * // returning all results in a single batch + * setupDeps.globalSearch.registerResultProvider({ + * id: 'my_provider', + * find: (term, { aborted$, preference, maxResults }, context) => { + * const resultPromise = myService.search(term, { preference, maxResults }, context.core.savedObjects.client); + * return from(resultPromise).pipe(takeUntil(aborted$)); + * }, + * }); + * ``` + */ + find( + term: string, + options: GlobalSearchProviderFindOptions, + context: GlobalSearchProviderContext + ): Observable; +} diff --git a/x-pack/plugins/graph/public/angular/graph_client_workspace.js b/x-pack/plugins/graph/public/angular/graph_client_workspace.js index bcd31716b6d64..cfa125fcc49ee 100644 --- a/x-pack/plugins/graph/public/angular/graph_client_workspace.js +++ b/x-pack/plugins/graph/public/angular/graph_client_workspace.js @@ -11,7 +11,7 @@ import d3 from 'd3'; // Pluggable function to handle the comms with a server. Default impl here is // for use outside of Kibana server with direct access to elasticsearch -let graphExplorer = function(indexName, typeName, request, responseHandler) { +let graphExplorer = function (indexName, typeName, request, responseHandler) { const dataForServer = JSON.stringify(request); $.ajax({ type: 'POST', @@ -20,12 +20,12 @@ let graphExplorer = function(indexName, typeName, request, responseHandler) { contentType: 'application/json;charset=utf-8', async: true, data: dataForServer, - success: function(data) { + success: function (data) { responseHandler(data); }, }); }; -let searcher = function(indexName, request, responseHandler) { +let searcher = function (indexName, request, responseHandler) { const dataForServer = JSON.stringify(request); $.ajax({ type: 'POST', @@ -34,7 +34,7 @@ let searcher = function(indexName, request, responseHandler) { contentType: 'application/json;charset=utf-8', //Not sure why this was necessary - worked without elsewhere async: true, data: dataForServer, - success: function(data) { + success: function (data) { responseHandler(data); }, }); @@ -46,14 +46,14 @@ function AddNodeOperation(node, owner) { const self = this; const vm = owner; self.node = node; - self.undo = function() { + self.undo = function () { vm.arrRemove(vm.nodes, self.node); vm.arrRemove(vm.selectedNodes, self.node); self.node.isSelected = false; delete vm.nodesMap[self.node.id]; }; - self.redo = function() { + self.redo = function () { vm.nodes.push(self.node); vm.nodesMap[self.node.id] = self.node; }; @@ -63,11 +63,11 @@ function AddEdgeOperation(edge, owner) { const self = this; const vm = owner; self.edge = edge; - self.undo = function() { + self.undo = function () { vm.arrRemove(vm.edges, self.edge); delete vm.edgesMap[self.edge.id]; }; - self.redo = function() { + self.redo = function () { vm.edges.push(self.edge); vm.edgesMap[self.edge.id] = self.edge; }; @@ -84,10 +84,10 @@ function GroupOperation(receiver, orphan) { const self = this; self.receiver = receiver; self.orphan = orphan; - self.undo = function() { + self.undo = function () { self.orphan.parent = undefined; }; - self.redo = function() { + self.redo = function () { self.orphan.parent = self.receiver; }; } @@ -96,10 +96,10 @@ function UnGroupOperation(parent, child) { const self = this; self.parent = parent; self.child = child; - self.undo = function() { + self.undo = function () { self.child.parent = self.parent; }; - self.redo = function() { + self.redo = function () { self.child.parent = undefined; }; } @@ -135,7 +135,7 @@ function GraphWorkspace(options) { searcher = options.searchProxy; } - this.addUndoLogEntry = function(undoOperations) { + this.addUndoLogEntry = function (undoOperations) { self.undoLog.push(undoOperations); if (self.undoLog.length > 50) { //Remove the oldest @@ -144,29 +144,29 @@ function GraphWorkspace(options) { self.redoLog = []; }; - this.undo = function() { + this.undo = function () { const lastOps = this.undoLog.pop(); if (lastOps) { this.stopLayout(); this.redoLog.push(lastOps); - lastOps.forEach(ops => ops.undo()); + lastOps.forEach((ops) => ops.undo()); this.runLayout(); } }; - this.redo = function() { + this.redo = function () { const lastOps = this.redoLog.pop(); if (lastOps) { this.stopLayout(); this.undoLog.push(lastOps); - lastOps.forEach(ops => ops.redo()); + lastOps.forEach((ops) => ops.redo()); this.runLayout(); } }; //Determines if 2 nodes are connected via an edge - this.areLinked = function(a, b) { + this.areLinked = function (a, b) { if (a === b) return true; - this.edges.forEach(e => { + this.edges.forEach((e) => { if (e.source === a && e.target === b) { return true; } @@ -179,9 +179,9 @@ function GraphWorkspace(options) { //======== Selection functions ======== - this.selectAll = function() { + this.selectAll = function () { self.selectedNodes = []; - self.nodes.forEach(node => { + self.nodes.forEach((node) => { if (node.parent === undefined) { node.isSelected = true; self.selectedNodes.push(node); @@ -191,16 +191,16 @@ function GraphWorkspace(options) { }); }; - this.selectNone = function() { + this.selectNone = function () { self.selectedNodes = []; - self.nodes.forEach(node => { + self.nodes.forEach((node) => { node.isSelected = false; }); }; - this.selectInvert = function() { + this.selectInvert = function () { self.selectedNodes = []; - self.nodes.forEach(node => { + self.nodes.forEach((node) => { if (node.parent !== undefined) { return; } @@ -211,8 +211,8 @@ function GraphWorkspace(options) { }); }; - this.selectNodes = function(nodes) { - nodes.forEach(node => { + this.selectNodes = function (nodes) { + nodes.forEach((node) => { node.isSelected = true; if (self.selectedNodes.indexOf(node) < 0) { self.selectedNodes.push(node); @@ -220,14 +220,14 @@ function GraphWorkspace(options) { }); }; - this.selectNode = function(node) { + this.selectNode = function (node) { node.isSelected = true; if (self.selectedNodes.indexOf(node) < 0) { self.selectedNodes.push(node); } }; - this.deleteSelection = function() { + this.deleteSelection = function () { let allAndGrouped = self.returnUnpackedGroupeds(self.selectedNodes); // Nothing selected so process all nodes @@ -236,7 +236,7 @@ function GraphWorkspace(options) { } const undoOperations = []; - allAndGrouped.forEach(node => { + allAndGrouped.forEach((node) => { //We set selected to false because despite being deleted, node objects sit in an undo log node.isSelected = false; delete self.nodesMap[node.id]; @@ -245,10 +245,10 @@ function GraphWorkspace(options) { self.arrRemoveAll(self.nodes, allAndGrouped); self.arrRemoveAll(self.selectedNodes, allAndGrouped); - const danglingEdges = self.edges.filter(function(edge) { + const danglingEdges = self.edges.filter(function (edge) { return self.nodes.indexOf(edge.source) < 0 || self.nodes.indexOf(edge.target) < 0; }); - danglingEdges.forEach(edge => { + danglingEdges.forEach((edge) => { delete self.edgesMap[edge.id]; undoOperations.push(new ReverseOperation(new AddEdgeOperation(edge, self))); }); @@ -257,9 +257,9 @@ function GraphWorkspace(options) { self.runLayout(); }; - this.selectNeighbours = function() { + this.selectNeighbours = function () { const newSelections = []; - self.edges.forEach(edge => { + self.edges.forEach((edge) => { if (!edge.topSrc.isSelected) { if (self.selectedNodes.indexOf(edge.topTarget) >= 0) { if (newSelections.indexOf(edge.topSrc) < 0) { @@ -275,37 +275,37 @@ function GraphWorkspace(options) { } } }); - newSelections.forEach(newlySelectedNode => { + newSelections.forEach((newlySelectedNode) => { self.selectedNodes.push(newlySelectedNode); newlySelectedNode.isSelected = true; }); }; - this.selectNone = function() { - self.selectedNodes.forEach(node => { + this.selectNone = function () { + self.selectedNodes.forEach((node) => { node.isSelected = false; }); self.selectedNodes = []; }; - this.deselectNode = function(node) { + this.deselectNode = function (node) { node.isSelected = false; self.arrRemove(self.selectedNodes, node); }; - this.getAllSelectedNodes = function() { + this.getAllSelectedNodes = function () { return this.returnUnpackedGroupeds(self.selectedNodes); }; - this.colorSelected = function(colorNum) { - self.getAllSelectedNodes().forEach(node => { + this.colorSelected = function (colorNum) { + self.getAllSelectedNodes().forEach((node) => { node.color = colorNum; }); }; - this.getSelectionsThatAreGrouped = function() { + this.getSelectionsThatAreGrouped = function () { const result = []; - self.selectedNodes.forEach(node => { + self.selectedNodes.forEach((node) => { if (node.numChildren > 0) { result.push(node); } @@ -313,13 +313,13 @@ function GraphWorkspace(options) { return result; }; - this.ungroupSelection = function() { - self.getSelectionsThatAreGrouped().forEach(node => { + this.ungroupSelection = function () { + self.getSelectionsThatAreGrouped().forEach((node) => { self.ungroup(node); }); }; - this.toggleNodeSelection = function(node) { + this.toggleNodeSelection = function (node) { if (node.isSelected) { self.deselectNode(node); } else { @@ -329,7 +329,7 @@ function GraphWorkspace(options) { return node.isSelected; }; - this.returnUnpackedGroupeds = function(topLevelNodeArray) { + this.returnUnpackedGroupeds = function (topLevelNodeArray) { //Gather any grouped nodes that are part of this top-level selection const result = topLevelNodeArray.slice(); @@ -371,7 +371,7 @@ function GraphWorkspace(options) { // ======= Miscellaneous functions - this.clearGraph = function() { + this.clearGraph = function () { this.stopLayout(); this.nodes = []; this.edges = []; @@ -398,9 +398,9 @@ function GraphWorkspace(options) { } }; - this.getNeighbours = function(node) { + this.getNeighbours = function (node) { const neighbourNodes = []; - self.edges.forEach(edge => { + self.edges.forEach((edge) => { if (edge.topSrc === edge.topTarget) { return; } @@ -419,7 +419,7 @@ function GraphWorkspace(options) { }; //Creates a query that represents a node - either simple term query or boolean if grouped - this.buildNodeQuery = function(topLevelNode) { + this.buildNodeQuery = function (topLevelNode) { let containedNodes = [topLevelNode]; containedNodes = self.returnUnpackedGroupeds(containedNodes); if (containedNodes.length === 1) { @@ -431,7 +431,7 @@ function GraphWorkspace(options) { }; } const termsByField = {}; - containedNodes.forEach(node => { + containedNodes.forEach((node) => { let termsList = termsByField[node.data.field]; if (!termsList) { termsList = []; @@ -465,20 +465,20 @@ function GraphWorkspace(options) { //====== Layout functions ======== - this.stopLayout = function() { + this.stopLayout = function () { if (this.force) { this.force.stop(); } this.force = null; }; - this.runLayout = function() { + this.runLayout = function () { this.stopLayout(); // The set of nodes and edges we present to the d3 layout algorithms // is potentially a reduced set of nodes if the client has used any // grouping of nodes into parent nodes. const effectiveEdges = []; - self.edges.forEach(edge => { + self.edges.forEach((edge) => { let topSrc = edge.source; let topTarget = edge.target; while (topSrc.parent !== undefined) { @@ -497,12 +497,12 @@ function GraphWorkspace(options) { }); } }); - const visibleNodes = self.nodes.filter(function(n) { + const visibleNodes = self.nodes.filter(function (n) { return n.parent === undefined; }); //reset then roll-up all the counts const allNodes = self.nodes; - allNodes.forEach(node => { + allNodes.forEach((node) => { node.numChildren = 0; }); @@ -527,11 +527,11 @@ function GraphWorkspace(options) { .theta(0.99) .alpha(0.5) .size([800, 600]) - .on('tick', function() { + .on('tick', function () { const nodeArray = self.nodes; let hasRollups = false; //Update the position of all "top level nodes" - nodeArray.forEach(n => { + nodeArray.forEach((n) => { //Code to support roll-ups if (n.parent === undefined) { n.kx = n.x; @@ -541,7 +541,7 @@ function GraphWorkspace(options) { } }); if (hasRollups) { - nodeArray.forEach(n => { + nodeArray.forEach((n) => { //Code to support roll-ups if (n.parent !== undefined) { // Is a grouped node - inherit parent's position so edges point into parent @@ -568,9 +568,9 @@ function GraphWorkspace(options) { //========Grouping functions========== //Merges all selected nodes into node - this.groupSelections = function(node) { + this.groupSelections = function (node) { const ops = []; - self.nodes.forEach(function(otherNode) { + self.nodes.forEach(function (otherNode) { if (otherNode !== node && otherNode.isSelected && otherNode.parent === undefined) { otherNode.parent = node; otherNode.isSelected = false; @@ -584,10 +584,10 @@ function GraphWorkspace(options) { self.runLayout(); }; - this.mergeNeighbours = function(node) { + this.mergeNeighbours = function (node) { const neighbours = self.getNeighbours(node); const ops = []; - neighbours.forEach(function(otherNode) { + neighbours.forEach(function (otherNode) { if (otherNode !== node && otherNode.parent === undefined) { otherNode.parent = node; otherNode.isSelected = false; @@ -599,14 +599,14 @@ function GraphWorkspace(options) { self.runLayout(); }; - this.mergeSelections = function(targetNode) { + this.mergeSelections = function (targetNode) { if (!targetNode) { console.log('Error - merge called on undefined target'); return; } const selClone = self.selectedNodes.slice(); const ops = []; - selClone.forEach(function(otherNode) { + selClone.forEach(function (otherNode) { if (otherNode !== targetNode && otherNode.parent === undefined) { otherNode.parent = targetNode; otherNode.isSelected = false; @@ -618,9 +618,9 @@ function GraphWorkspace(options) { self.runLayout(); }; - this.ungroup = function(node) { + this.ungroup = function (node) { const ops = []; - self.nodes.forEach(function(other) { + self.nodes.forEach(function (other) { if (other.parent === node) { other.parent = undefined; ops.push(new UnGroupOperation(node, other)); @@ -630,20 +630,20 @@ function GraphWorkspace(options) { self.runLayout(); }; - this.unblacklist = function(node) { + this.unblacklist = function (node) { self.arrRemove(self.blacklistedNodes, node); }; - this.blacklistSelection = function() { + this.blacklistSelection = function () { const selection = self.getAllSelectedNodes(); const danglingEdges = []; - self.edges.forEach(function(edge) { + self.edges.forEach(function (edge) { if (selection.indexOf(edge.source) >= 0 || selection.indexOf(edge.target) >= 0) { delete self.edgesMap[edge.id]; danglingEdges.push(edge); } }); - selection.forEach(node => { + selection.forEach((node) => { delete self.nodesMap[node.id]; self.blacklistedNodes.push(node); node.isSelected = false; @@ -656,7 +656,7 @@ function GraphWorkspace(options) { // A "simple search" operation that requires no parameters from the client. // Performs numHops hops pulling in field-specific number of terms each time - this.simpleSearch = function(searchTerm, fieldsChoice, numHops) { + this.simpleSearch = function (searchTerm, fieldsChoice, numHops) { const qs = { query_string: { query: searchTerm, @@ -665,7 +665,7 @@ function GraphWorkspace(options) { return this.search(qs, fieldsChoice, numHops); }; - this.search = function(query, fieldsChoice, numHops) { + this.search = function (query, fieldsChoice, numHops) { if (!fieldsChoice) { fieldsChoice = self.options.vertex_fields; } @@ -734,7 +734,7 @@ function GraphWorkspace(options) { self.callElasticsearch(request); }; - this.buildControls = function() { + this.buildControls = function () { //This is an object managed by the client that may be subject to change const guiSettingsObj = self.options.exploreControls; @@ -753,11 +753,11 @@ function GraphWorkspace(options) { return controls; }; - this.makeNodeId = function(field, term) { + this.makeNodeId = function (field, term) { return field + '..' + term; }; - this.makeEdgeId = function(srcId, targetId) { + this.makeEdgeId = function (srcId, targetId) { let id = srcId + '->' + targetId; if (srcId > targetId) { id = targetId + '->' + srcId; @@ -766,7 +766,7 @@ function GraphWorkspace(options) { }; //======= Adds new nodes retrieved from an elasticsearch search ======== - this.mergeGraph = function(newData) { + this.mergeGraph = function (newData) { this.stopLayout(); if (!newData.nodes) { @@ -785,7 +785,7 @@ function GraphWorkspace(options) { //Remove nodes we already have const dedupedNodes = []; - newData.nodes.forEach(node => { + newData.nodes.forEach((node) => { //Assign an ID node.id = self.makeNodeId(node.field, node.term); if (!this.nodesMap[node.id]) { @@ -801,7 +801,7 @@ function GraphWorkspace(options) { this.options.nodeLabeller(dedupedNodes); } - dedupedNodes.forEach(dedupedNode => { + dedupedNodes.forEach((dedupedNode) => { let label = dedupedNode.term; if (dedupedNode.label) { label = dedupedNode.label; @@ -827,7 +827,7 @@ function GraphWorkspace(options) { this.nodesMap[node.id] = node; }); - newData.edges.forEach(edge => { + newData.edges.forEach((edge) => { const src = newData.nodes[edge.source]; const target = newData.nodes[edge.target]; edge.id = this.makeEdgeId(src.id, target.id); @@ -867,7 +867,7 @@ function GraphWorkspace(options) { this.runLayout(); }; - this.mergeIds = function(parentId, childId) { + this.mergeIds = function (parentId, childId) { const parent = self.getNode(parentId); const child = self.getNode(childId); if (child.isSelected) { @@ -879,16 +879,16 @@ function GraphWorkspace(options) { self.runLayout(); }; - this.getNode = function(nodeId) { + this.getNode = function (nodeId) { return this.nodesMap[nodeId]; }; - this.getEdge = function(edgeId) { + this.getEdge = function (edgeId) { return this.edgesMap[edgeId]; }; //======= Expand functions to request new additions to the graph - this.expandSelecteds = function(targetOptions = {}) { + this.expandSelecteds = function (targetOptions = {}) { let startNodes = self.getAllSelectedNodes(); if (startNodes.length === 0) { startNodes = self.nodes; @@ -897,19 +897,19 @@ function GraphWorkspace(options) { self.expand(clone, targetOptions); }; - this.expandGraph = function() { + this.expandGraph = function () { self.expandSelecteds(); }; //Find new nodes to link to existing selected nodes - this.expandNode = function(node) { + this.expandNode = function (node) { self.expand(self.returnUnpackedGroupeds([node]), {}); }; // A manual expand function where the client provides the list // of existing nodes that are the start points and some options // about what targets are of interest. - this.expand = function(startNodes, targetOptions) { + this.expand = function (startNodes, targetOptions) { //============================= const nodesByField = {}; const excludeNodesByField = {}; @@ -983,7 +983,7 @@ function GraphWorkspace(options) { } //Identify target fields - targetFields.forEach(targetField => { + targetFields.forEach((targetField) => { const fieldName = targetField.name; // Sometimes the target field is disabled from loading new hops so we need to use the last valid figure const hopSize = targetField.hopSize > 0 ? targetField.hopSize : targetField.lastValidHopSize; @@ -1005,13 +1005,13 @@ function GraphWorkspace(options) { }, }; self.lastRequest = JSON.stringify(request, null, '\t'); - graphExplorer(self.options.indexName, request, function(data) { + graphExplorer(self.options.indexName, request, function (data) { self.lastResponse = JSON.stringify(data, null, '\t'); const edges = []; //Label fields with a field number for CSS styling - data.vertices.forEach(node => { - targetFields.some(fieldDef => { + data.vertices.forEach((node) => { + targetFields.some((fieldDef) => { if (node.field === fieldDef.name) { node.color = fieldDef.color; node.icon = fieldDef.icon; @@ -1026,7 +1026,7 @@ function GraphWorkspace(options) { const minLineSize = 2; const maxLineSize = 10; let maxEdgeWeight = 0.00000001; - data.connections.forEach(edge => { + data.connections.forEach((edge) => { maxEdgeWeight = Math.max(maxEdgeWeight, edge.weight); edges.push({ source: edge.source, @@ -1046,11 +1046,11 @@ function GraphWorkspace(options) { //===== End expand graph ======================== }; - this.trimExcessNewEdges = function(newNodes, newEdges) { + this.trimExcessNewEdges = function (newNodes, newEdges) { let trimmedEdges = []; const maxNumEdgesToReturn = 5; //Trim here to just the new edges that are most interesting. - newEdges.forEach(edge => { + newEdges.forEach((edge) => { const src = newNodes[edge.source]; const target = newNodes[edge.target]; const srcId = src.field + '..' + src.term; @@ -1080,7 +1080,7 @@ function GraphWorkspace(options) { }); if (trimmedEdges.length > maxNumEdgesToReturn) { //trim to only the most interesting ones - trimmedEdges.sort(function(a, b) { + trimmedEdges.sort(function (a, b) { return b.weight - a.weight; }); trimmedEdges = trimmedEdges.splice(0, maxNumEdgesToReturn); @@ -1088,13 +1088,13 @@ function GraphWorkspace(options) { return trimmedEdges; }; - this.getQuery = function(startNodes, loose) { + this.getQuery = function (startNodes, loose) { const shoulds = []; let nodes = startNodes; if (!startNodes) { nodes = self.nodes; } - nodes.forEach(node => { + nodes.forEach((node) => { if (node.parent === undefined) { shoulds.push(self.buildNodeQuery(node)); } @@ -1107,7 +1107,7 @@ function GraphWorkspace(options) { }; }; - this.getSelectedOrAllNodes = function() { + this.getSelectedOrAllNodes = function () { let startNodes = self.getAllSelectedNodes(); if (startNodes.length === 0) { startNodes = self.nodes; @@ -1115,8 +1115,8 @@ function GraphWorkspace(options) { return startNodes; }; - this.getSelectedOrAllTopNodes = function() { - return self.getSelectedOrAllNodes().filter(function(node) { + this.getSelectedOrAllTopNodes = function () { + return self.getSelectedOrAllNodes().filter(function (node) { return node.parent === undefined; }); }; @@ -1135,7 +1135,7 @@ function GraphWorkspace(options) { * @param maxNewEdges Max number of new edges added. Avoid adding too many new edges * at once into the graph otherwise disorientating */ - this.fillInGraph = function(maxNewEdges = 10) { + this.fillInGraph = function (maxNewEdges = 10) { let nodesForLinking = self.getSelectedOrAllTopNodes(); const maxNumVerticesSearchable = 100; @@ -1161,7 +1161,7 @@ function GraphWorkspace(options) { // the first 2 nodes in the array will therefore be labelled "0|1" const shoulds = []; const filterMap = {}; - nodesForLinking.forEach(function(node, nodeNum) { + nodesForLinking.forEach(function (node, nodeNum) { const nodeQuery = self.buildNodeQuery(node); shoulds.push(nodeQuery); filterMap[nodeNum] = nodeQuery; @@ -1186,10 +1186,10 @@ function GraphWorkspace(options) { }; // Search for connections between the selected nodes. - searcher(self.options.indexName, searchReq, function(data) { + searcher(self.options.indexName, searchReq, function (data) { const numDocsMatched = data.hits.total; const buckets = data.aggregations.matrix.buckets; - const vertices = nodesForLinking.map(function(existingNode) { + const vertices = nodesForLinking.map(function (existingNode) { return { field: existingNode.data.field, term: existingNode.data.term, @@ -1202,11 +1202,11 @@ function GraphWorkspace(options) { let maxEdgeWeight = 0; // Turn matrix array of results into a map const keyedBuckets = {}; - buckets.forEach(function(bucket) { + buckets.forEach(function (bucket) { keyedBuckets[bucket.key] = bucket; }); - buckets.forEach(function(bucket) { + buckets.forEach(function (bucket) { // We calibrate line thickness based on % of max weight of // all edges (including the edges we may already have in the workspace) const ids = bucket.key.split('|'); @@ -1232,7 +1232,7 @@ function GraphWorkspace(options) { }); const backFilledMinLineSize = 2; const backFilledMaxLineSize = 5; - buckets.forEach(function(bucket) { + buckets.forEach(function (bucket) { if (bucket.doc_count < parseInt(self.options.exploreControls.minDocCount)) { return; } @@ -1263,7 +1263,7 @@ function GraphWorkspace(options) { }); // Trim the array of connections so that we don't add too many at once - disorientating for users otherwise if (connections.length > maxNewEdges) { - connections = connections.sort(function(a, b) { + connections = connections.sort(function (a, b) { return b.weight - a.weight; }); connections = connections.slice(0, maxNewEdges); @@ -1287,11 +1287,11 @@ function GraphWorkspace(options) { // We use a free-text search on the index's configured default field (typically '_all') // to drill-down into docs that should be linked but aren't via the exact terms // we have in the workspace - this.getLikeThisButNotThisQuery = function(startNodes) { + this.getLikeThisButNotThisQuery = function (startNodes) { const likeQueries = []; const txtsByFieldType = {}; - startNodes.forEach(node => { + startNodes.forEach((node) => { let txt = txtsByFieldType[node.data.field]; if (txt) { txt = txt + ' ' + node.label; @@ -1317,11 +1317,11 @@ function GraphWorkspace(options) { const excludeNodesByField = {}; const allExistingNodes = self.nodes; - allExistingNodes.forEach(existingNode => { + allExistingNodes.forEach((existingNode) => { addTermToFieldList(excludeNodesByField, existingNode.data.field, existingNode.data.term); }); const blacklistedNodes = self.blacklistedNodes; - blacklistedNodes.forEach(blacklistedNode => { + blacklistedNodes.forEach((blacklistedNode) => { addTermToFieldList( excludeNodesByField, blacklistedNode.data.field, @@ -1331,7 +1331,7 @@ function GraphWorkspace(options) { //Create negative boosting queries to avoid matching what you already have in the workspace. const notExistingNodes = []; - Object.keys(excludeNodesByField).forEach(fieldName => { + Object.keys(excludeNodesByField).forEach((fieldName) => { const termsQuery = {}; termsQuery[fieldName] = excludeNodesByField[fieldName]; notExistingNodes.push({ @@ -1359,7 +1359,7 @@ function GraphWorkspace(options) { return result; }; - this.getSelectedIntersections = function(callback) { + this.getSelectedIntersections = function (callback) { if (self.selectedNodes.length === 0) { return self.getAllIntersections(callback, self.nodes); } @@ -1372,7 +1372,7 @@ function GraphWorkspace(options) { return self.getAllIntersections(callback, self.getAllSelectedNodes()); }; - this.jLHScore = function(subsetFreq, subsetSize, supersetFreq, supersetSize) { + this.jLHScore = function (subsetFreq, subsetSize, supersetFreq, supersetSize) { const subsetProbability = subsetFreq / subsetSize; const supersetProbability = supersetFreq / supersetSize; @@ -1392,13 +1392,13 @@ function GraphWorkspace(options) { // Determines union/intersection stats for neighbours of a node. // TODO - could move server-side as a graph API function? - this.getAllIntersections = function(callback, nodes) { + this.getAllIntersections = function (callback, nodes) { //Ensure these are all top-level nodes only - nodes = nodes.filter(function(n) { + nodes = nodes.filter(function (n) { return n.parent === undefined; }); - const allQueries = nodes.map(function(node) { + const allQueries = nodes.map(function (node) { return self.buildNodeQuery(node); }); @@ -1436,7 +1436,7 @@ function GraphWorkspace(options) { request.aggs.sources.filters.filters['bg' + n] = query; request.aggs.sources.aggs.targets.filters.filters['fg' + n] = query; }); - searcher(self.options.indexName, request, function(data) { + searcher(self.options.indexName, request, function (data) { const termIntersects = []; const fullDocCounts = []; const allDocCount = data.aggregations.all.doc_count; @@ -1499,7 +1499,7 @@ function GraphWorkspace(options) { termIntersects.push(termIntersect); }); }); - termIntersects.sort(function(a, b) { + termIntersects.sort(function (a, b) { if (b.mergeConfidence !== a.mergeConfidence) { return b.mergeConfidence - a.mergeConfidence; } @@ -1520,14 +1520,14 @@ function GraphWorkspace(options) { // Internal utility function for calling the Graph API and handling the response // by merging results into existing nodes in this workspace. - this.callElasticsearch = function(request) { + this.callElasticsearch = function (request) { self.lastRequest = JSON.stringify(request, null, '\t'); - graphExplorer(self.options.indexName, request, function(data) { + graphExplorer(self.options.indexName, request, function (data) { self.lastResponse = JSON.stringify(data, null, '\t'); const edges = []; //Label the nodes with field number for CSS styling - data.vertices.forEach(node => { - self.options.vertex_fields.some(fieldDef => { + data.vertices.forEach((node) => { + self.options.vertex_fields.some((fieldDef) => { if (node.field === fieldDef.name) { node.color = fieldDef.color; node.icon = fieldDef.icon; @@ -1542,10 +1542,10 @@ function GraphWorkspace(options) { const minLineSize = 2; const maxLineSize = 10; let maxEdgeWeight = 0.00000001; - data.connections.forEach(edge => { + data.connections.forEach((edge) => { maxEdgeWeight = Math.max(maxEdgeWeight, edge.weight); }); - data.connections.forEach(edge => { + data.connections.forEach((edge) => { edges.push({ source: edge.source, target: edge.target, diff --git a/x-pack/plugins/graph/public/angular/graph_client_workspace.test.js b/x-pack/plugins/graph/public/angular/graph_client_workspace.test.js index 7ffb16d986a21..fe6a782373eb2 100644 --- a/x-pack/plugins/graph/public/angular/graph_client_workspace.test.js +++ b/x-pack/plugins/graph/public/angular/graph_client_workspace.test.js @@ -6,16 +6,16 @@ import { createWorkspace } from './graph_client_workspace'; -describe('graphui-workspace', function() { - describe('createWorkspace()', function() { +describe('graphui-workspace', function () { + describe('createWorkspace()', function () { // var fooResource=null; let mockedResult = null; let init = null; - beforeEach(function() { + beforeEach(function () { //Setup logic here // fooResource={"foo":"bar"}; - init = function() { - const callNodeProxy = function(indexName, query, responseHandler) { + init = function () { + const callNodeProxy = function (indexName, query, responseHandler) { responseHandler(mockedResult); }; const options = { @@ -45,11 +45,11 @@ describe('graphui-workspace', function() { }; }; }); - it('initializeWorkspace', function() { + it('initializeWorkspace', function () { const { workspace } = init(); expect(workspace.nodes.length).toEqual(0); }); - it('simpleSearch', function() { + it('simpleSearch', function () { //Test that a graph is loaded from a free-text search const { workspace } = init(); @@ -91,7 +91,7 @@ describe('graphui-workspace', function() { expect(nodeD).toBe(undefined); }); - it('expandTest', function() { + it('expandTest', function () { //Test that a graph can be expanded const { workspace } = init(); @@ -155,7 +155,7 @@ describe('graphui-workspace', function() { expect(workspace.edges.length).toEqual(2); }); - it('selectionTest', function() { + it('selectionTest', function () { //Test selections on a graph const { workspace } = init(); // graph is a1->a2 and b1->b2 @@ -237,7 +237,7 @@ describe('graphui-workspace', function() { expect(workspace.selectedNodes.length).toEqual(2); }); - it('undoRedoDeletes', function() { + it('undoRedoDeletes', function () { const { workspace } = init(); // graph is a1->a2 mockedResult = { @@ -293,7 +293,7 @@ describe('graphui-workspace', function() { expect(workspace.nodes.length).toEqual(2); }); - it('undoRedoGroupings', function() { + it('undoRedoGroupings', function () { const { workspace } = init(); // graph is a1->a2 mockedResult = { diff --git a/x-pack/plugins/graph/public/app.js b/x-pack/plugins/graph/public/app.js index d4199fbd092b4..08b13e9d5c541 100644 --- a/x-pack/plugins/graph/public/app.js +++ b/x-pack/plugins/graph/public/app.js @@ -57,15 +57,15 @@ export function initGraphApp(angularModule, deps) { const app = angularModule; - app.directive('vennDiagram', function(reactDirective) { + app.directive('vennDiagram', function (reactDirective) { return reactDirective(VennDiagram); }); - app.directive('graphVisualization', function(reactDirective) { + app.directive('graphVisualization', function (reactDirective) { return reactDirective(GraphVisualization); }); - app.directive('graphListing', function(reactDirective) { + app.directive('graphListing', function (reactDirective) { return reactDirective(Listing, [ ['coreStart', { watchDepth: 'reference' }], ['createItem', { watchDepth: 'reference' }], @@ -81,7 +81,7 @@ export function initGraphApp(angularModule, deps) { ]); }); - app.directive('graphApp', function(reactDirective) { + app.directive('graphApp', function (reactDirective) { return reactDirective( GraphApp, [ @@ -102,33 +102,33 @@ export function initGraphApp(angularModule, deps) { ); }); - app.directive('graphVisualization', function(reactDirective) { + app.directive('graphVisualization', function (reactDirective) { return reactDirective(GraphVisualization, undefined, { restrict: 'A' }); }); - app.config(function($routeProvider) { + app.config(function ($routeProvider) { $routeProvider .when('/home', { template: listingTemplate, badge: getReadonlyBadge, - controller: function($location, $scope) { + controller: function ($location, $scope) { $scope.listingLimit = savedObjects.settings.getListingLimit(); $scope.initialPageSize = savedObjects.settings.getPerPage(); $scope.create = () => { $location.url(getNewPath()); }; - $scope.find = search => { + $scope.find = (search) => { return findSavedWorkspace( { savedObjectsClient, basePath: coreStart.http.basePath }, search, $scope.listingLimit ); }; - $scope.editItem = workspace => { + $scope.editItem = (workspace) => { $location.url(getEditPath(workspace)); }; - $scope.getViewUrl = workspace => getEditUrl(addBasePath, workspace); - $scope.delete = workspaces => + $scope.getViewUrl = (workspace) => getEditUrl(addBasePath, workspace); + $scope.delete = (workspaces) => deleteSavedWorkspace( savedObjectsClient, workspaces.map(({ id }) => id) @@ -143,9 +143,9 @@ export function initGraphApp(angularModule, deps) { template: appTemplate, badge: getReadonlyBadge, resolve: { - savedWorkspace: function($rootScope, $route, $location) { + savedWorkspace: function ($rootScope, $route, $location) { return $route.current.params.id - ? getSavedWorkspace(savedObjectsClient, $route.current.params.id).catch(function(e) { + ? getSavedWorkspace(savedObjectsClient, $route.current.params.id).catch(function (e) { toastNotifications.addError(e, { title: i18n.translate('xpack.graph.missingWorkspaceErrorMessage', { defaultMessage: "Couldn't load graph with ID", @@ -160,16 +160,16 @@ export function initGraphApp(angularModule, deps) { }) : getSavedWorkspace(savedObjectsClient); }, - indexPatterns: function() { + indexPatterns: function () { return savedObjectsClient .find({ type: 'index-pattern', fields: ['title', 'type'], perPage: 10000, }) - .then(response => response.savedObjects); + .then((response) => response.savedObjects); }, - GetIndexPatternProvider: function() { + GetIndexPatternProvider: function () { return indexPatterns; }, }, @@ -180,7 +180,7 @@ export function initGraphApp(angularModule, deps) { }); //======== Controller for basic UI ================== - app.controller('graphuiPlugin', function($scope, $route, $location) { + app.controller('graphuiPlugin', function ($scope, $route, $location) { function handleError(err) { const toastTitle = i18n.translate('xpack.graph.errorToastTitle', { defaultMessage: 'Graph Error', @@ -214,7 +214,7 @@ export function initGraphApp(angularModule, deps) { $scope.loading = true; return coreStart.http .post('../api/graph/graphExplore', request) - .then(function(data) { + .then(function (data) { const response = data.resp; if (response.timed_out) { toastNotifications.addWarning( @@ -233,7 +233,7 @@ export function initGraphApp(angularModule, deps) { } //Helper function for the graphClientWorkspace to perform a query - const callSearchNodeProxy = function(indexName, query, responseHandler) { + const callSearchNodeProxy = function (indexName, query, responseHandler) { const request = { body: JSON.stringify({ index: indexName, @@ -243,7 +243,7 @@ export function initGraphApp(angularModule, deps) { $scope.loading = true; coreStart.http .post('../api/graph/searchProxy', request) - .then(function(data) { + .then(function (data) { const response = data.resp; responseHandler(response); }) @@ -268,10 +268,10 @@ export function initGraphApp(angularModule, deps) { indexName: indexPattern, vertex_fields: [], // Here we have the opportunity to look up labels for nodes... - nodeLabeller: function() { + nodeLabeller: function () { // console.log(newNodes); }, - changeHandler: function() { + changeHandler: function () { //Allows DOM to update with graph layout changes. $scope.$apply(); }, @@ -281,10 +281,10 @@ export function initGraphApp(angularModule, deps) { }; $scope.workspace = createWorkspace(options); }, - setLiveResponseFields: fields => { + setLiveResponseFields: (fields) => { $scope.liveResponseFields = fields; }, - setUrlTemplates: urlTemplates => { + setUrlTemplates: (urlTemplates) => { $scope.urlTemplates = urlTemplates; }, getWorkspace: () => { @@ -302,7 +302,7 @@ export function initGraphApp(angularModule, deps) { $scope.workspaceInitialized = true; }, savePolicy: graphSavePolicy, - changeUrl: newUrl => { + changeUrl: (newUrl) => { $scope.$evalAsync(() => { $location.url(newUrl); }); @@ -326,8 +326,8 @@ export function initGraphApp(angularModule, deps) { const allSavingDisabled = graphSavePolicy === 'none'; $scope.spymode = 'request'; $scope.colors = colorChoices; - $scope.isColorDark = color => isColorDark(...hexToRgb(color)); - $scope.nodeClick = function(n, $event) { + $scope.isColorDark = (color) => isColorDark(...hexToRgb(color)); + $scope.nodeClick = function (n, $event) { //Selection logic - shift key+click helps selects multiple nodes // Without the shift key we deselect all prior selections (perhaps not // a great idea for touch devices with no concept of shift key) @@ -344,14 +344,14 @@ export function initGraphApp(angularModule, deps) { } }; - $scope.clickEdge = function(edge) { + $scope.clickEdge = function (edge) { $scope.workspace.getAllIntersections($scope.handleMergeCandidatesCallback, [ edge.topSrc, edge.topTarget, ]); }; - $scope.submit = function(searchTerm) { + $scope.submit = function (searchTerm) { $scope.workspaceInitialized = true; const numHops = 2; if (searchTerm.startsWith('{')) { @@ -372,28 +372,28 @@ export function initGraphApp(angularModule, deps) { $scope.workspace.simpleSearch(searchTerm, $scope.liveResponseFields, numHops); }; - $scope.selectSelected = function(node) { + $scope.selectSelected = function (node) { $scope.detail = { latestNodeSelection: node, }; return ($scope.selectedSelectedVertex = node); }; - $scope.isSelectedSelected = function(node) { + $scope.isSelectedSelected = function (node) { return $scope.selectedSelectedVertex === node; }; - $scope.openUrlTemplate = function(template) { + $scope.openUrlTemplate = function (template) { const url = template.url; const newUrl = url.replace(urlTemplateRegex, template.encoder.encode($scope.workspace)); window.open(newUrl, '_blank'); }; - $scope.aceLoaded = editor => { + $scope.aceLoaded = (editor) => { editor.$blockScrolling = Infinity; }; - $scope.setDetail = function(data) { + $scope.setDetail = function (data) { $scope.detail = data; }; @@ -421,7 +421,7 @@ export function initGraphApp(angularModule, deps) { }), confirmModalOptions ) - .then(isConfirmed => { + .then((isConfirmed) => { if (isConfirmed) { callback(); } @@ -429,7 +429,7 @@ export function initGraphApp(angularModule, deps) { } $scope.confirmWipeWorkspace = canWipeWorkspace; - $scope.performMerge = function(parentId, childId) { + $scope.performMerge = function (parentId, childId) { let found = true; while (found) { found = false; @@ -448,9 +448,9 @@ export function initGraphApp(angularModule, deps) { $scope.detail = null; }; - $scope.handleMergeCandidatesCallback = function(termIntersects) { + $scope.handleMergeCandidatesCallback = function (termIntersects) { const mergeCandidates = []; - termIntersects.forEach(ti => { + termIntersects.forEach((ti) => { mergeCandidates.push({ id1: ti.id1, id2: ti.id2, @@ -477,8 +477,8 @@ export function initGraphApp(angularModule, deps) { tooltip: i18n.translate('xpack.graph.topNavMenu.newWorkspaceTooltip', { defaultMessage: 'Create a new workspace', }), - run: function() { - canWipeWorkspace(function() { + run: function () { + canWipeWorkspace(function () { $scope.$evalAsync(() => { if ($location.url() === '/workspace/') { $route.reload(); @@ -516,7 +516,7 @@ export function initGraphApp(angularModule, deps) { }); } }, - disableButton: function() { + disableButton: function () { return allSavingDisabled || !hasFieldsSelector(store.getState()); }, run: () => { @@ -530,7 +530,7 @@ export function initGraphApp(angularModule, deps) { } $scope.topNavMenu.push({ key: 'inspect', - disableButton: function() { + disableButton: function () { return $scope.workspace === null; }, label: i18n.translate('xpack.graph.topNavMenu.inspectLabel', { @@ -550,7 +550,7 @@ export function initGraphApp(angularModule, deps) { $scope.topNavMenu.push({ key: 'settings', - disableButton: function() { + disableButton: function () { return datasourceSelector(store.getState()).type === 'none'; }, label: i18n.translate('xpack.graph.topNavMenu.settingsLabel', { @@ -605,7 +605,7 @@ export function initGraphApp(angularModule, deps) { }; $scope.closeMenus = () => { - _.forOwn($scope.menus, function(_, key) { + _.forOwn($scope.menus, function (_, key) { $scope.menus[key] = false; }); }; diff --git a/x-pack/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts index 7c0fb867b9ada..b46bc88500e0a 100644 --- a/x-pack/plugins/graph/public/application.ts +++ b/x-pack/plugins/graph/public/application.ts @@ -77,7 +77,7 @@ export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) true ); - const licenseSubscription = deps.licensing.license$.subscribe(license => { + const licenseSubscription = deps.licensing.license$.subscribe((license) => { const info = checkLicense(license); const licenseAllowsToShowThisPage = info.showAppLink && info.enableAppLink; diff --git a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx index 211458e67d05b..cd2227bf6a18c 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx @@ -215,7 +215,7 @@ export function FieldEditor({ })} > { + onChange={(choices) => { // value is always defined because it's an unclearable single selection const newFieldName = choices[0].value!; @@ -260,7 +260,7 @@ export function FieldEditor({ > { + onChange={(newColor) => { updateProp('color', newColor); }} compressed @@ -286,7 +286,7 @@ export function FieldEditor({ ); }} - options={iconChoices.map(currentIcon => ({ + options={iconChoices.map((currentIcon) => ({ label: currentIcon.label, value: currentIcon, }))} @@ -296,7 +296,7 @@ export function FieldEditor({ value: icon, }, ]} - onChange={choices => { + onChange={(choices) => { updateProp('icon', choices[0].value!); }} compressed @@ -378,7 +378,7 @@ function toOptions( currentField: WorkspaceField ): Array<{ label: string; value: string; type: ButtonHTMLAttributes['type'] }> { return fields - .filter(field => !field.selected || field === currentField) + .filter((field) => !field.selected || field === currentField) .map(({ name, type }) => ({ label: name, value: name, diff --git a/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx b/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx index ac656ebdd9512..f213fe6b509bf 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx @@ -76,28 +76,13 @@ describe('field_manager', () => { ); - getInstance = () => - instance - .find(FieldManager) - .dive() - .dive() - .dive(); + getInstance = () => instance.find(FieldManager).dive().dive().dive(); }); it('should list editors for all selected fields', () => { expect(getInstance().find(FieldEditor).length).toEqual(2); - expect( - getInstance() - .find(FieldEditor) - .at(0) - .prop('field').name - ).toEqual('field1'); - expect( - getInstance() - .find(FieldEditor) - .at(1) - .prop('field').name - ).toEqual('field2'); + expect(getInstance().find(FieldEditor).at(0).prop('field').name).toEqual('field1'); + expect(getInstance().find(FieldEditor).at(1).prop('field').name).toEqual('field2'); }); it('should show selected non-aggregatable fields in picker, but hide unselected ones', () => { @@ -122,11 +107,9 @@ describe('field_manager', () => { ).toEqual(['field1', 'field2', 'field3']); act(() => { - getInstance() - .find(FieldPicker) - .dive() - .find(EuiSelectable) - .prop('onChange')([{ checked: 'on', label: 'field3' }]); + getInstance().find(FieldPicker).dive().find(EuiSelectable).prop('onChange')([ + { checked: 'on', label: 'field3' }, + ]); }); expect(dispatchSpy).toHaveBeenCalledWith({ @@ -139,12 +122,8 @@ describe('field_manager', () => { it('should deselect field', () => { act(() => { - getInstance() - .find(FieldEditor) - .at(0) - .dive() - .find(EuiContextMenu) - .prop('panels')![0].items![2].onClick!({} as any); + getInstance().find(FieldEditor).at(0).dive().find(EuiContextMenu).prop('panels')![0].items![2] + .onClick!({} as any); }); expect(dispatchSpy).toHaveBeenCalledWith({ @@ -157,12 +136,8 @@ describe('field_manager', () => { it('should show remove non-aggregatable fields from picker after deselection', () => { act(() => { - getInstance() - .find(FieldEditor) - .at(1) - .dive() - .find(EuiContextMenu) - .prop('panels')![0].items![2].onClick!({} as any); + getInstance().find(FieldEditor).at(1).dive().find(EuiContextMenu).prop('panels')![0].items![2] + .onClick!({} as any); }); expect( getInstance() @@ -198,12 +173,8 @@ describe('field_manager', () => { }); expect( - getInstance() - .find(FieldEditor) - .at(0) - .dive() - .find(EuiContextMenu) - .prop('panels')![0].items![1].name + getInstance().find(FieldEditor).at(0).dive().find(EuiContextMenu).prop('panels')![0].items![1] + .name ).toEqual('Enable field'); }); @@ -231,37 +202,26 @@ describe('field_manager', () => { }); expect( - getInstance() - .find(FieldEditor) - .at(1) - .dive() - .find(EuiContextMenu) - .prop('panels')![0].items![1].name + getInstance().find(FieldEditor).at(1).dive().find(EuiContextMenu).prop('panels')![0].items![1] + .name ).toEqual('Disable field'); }); it('should change color', () => { - const fieldEditor = getInstance() - .find(FieldEditor) - .at(1) - .dive(); + const fieldEditor = getInstance().find(FieldEditor).at(1).dive(); const getDisplayForm = () => shallow(fieldEditor.find(EuiContextMenu).prop('panels')![1].content as ReactElement); act(() => { - getDisplayForm() - .find(EuiColorPicker) - .prop('onChange')!('#aaa', { + getDisplayForm().find(EuiColorPicker).prop('onChange')!('#aaa', { rgba: [170, 170, 170, 1], hex: '#aaa', isValid: true, }); }); fieldEditor.update(); - getDisplayForm() - .find(EuiButton) - .prop('onClick')!({} as any); + getDisplayForm().find(EuiButton).prop('onClick')!({} as any); expect(dispatchSpy).toHaveBeenCalledWith({ type: 'x-pack/graph/fields/UPDATE_FIELD_PROPERTIES', diff --git a/x-pack/plugins/graph/public/components/field_manager/field_manager.tsx b/x-pack/plugins/graph/public/components/field_manager/field_manager.tsx index 9bca5b82e58aa..d0e44542674ef 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_manager.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_manager.tsx @@ -40,7 +40,7 @@ export function FieldManagerComponent(props: { }) { return ( - {props.selectedFields.map(field => ( + {props.selectedFields.map((field) => ( @@ -58,7 +58,7 @@ export const FieldManager = connect( allFields: fieldsSelector(state), selectedFields: selectedFieldsSelector(state), }), - dispatch => + (dispatch) => bindActionCreators( { updateFieldProperties, diff --git a/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx index f2dc9ba0c6490..ae32e8d2ce6d6 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx @@ -88,8 +88,8 @@ export function FieldPicker({ }} searchable options={fieldOptions} - onChange={newOptions => { - newOptions.forEach(option => { + onChange={(newOptions) => { + newOptions.forEach((option) => { if (option.checked === 'on' && !fieldMap[option.label].selected) { selectField(option.label); } else if (option.checked !== 'on' && fieldMap[option.label].selected) { @@ -119,8 +119,8 @@ function toOptions( // don't show non-aggregatable fields, except for the case when they are already selected. // this is necessary to ensure backwards compatibility with existing workspaces that might // contain non-aggregatable fields. - .filter(field => isExplorable(field) || field.selected) - .map(field => ({ + .filter((field) => isExplorable(field) || field.selected) + .map((field) => ({ label: field.name, prepend: , checked: field.selected ? 'on' : undefined, diff --git a/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx index be3ebee9f0e23..1dd83f17ac8d4 100644 --- a/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx +++ b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx @@ -136,10 +136,7 @@ describe('graph_visualization', () => { edges={edges} /> ); - instance - .find('.gphNode') - .first() - .simulate('click', {}); + instance.find('.gphNode').first().simulate('click', {}); expect(nodeClickSpy).toHaveBeenCalledWith(nodes[0], {}); }); @@ -153,10 +150,7 @@ describe('graph_visualization', () => { edges={edges} /> ); - instance - .find('.gphEdge') - .first() - .simulate('click'); + instance.find('.gphEdge').first().simulate('click'); expect(edgeClickSpy).toHaveBeenCalledWith(edges[0]); }); }); diff --git a/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.tsx b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.tsx index 162e4d01db6a4..8561989c8a6bf 100644 --- a/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.tsx +++ b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.tsx @@ -36,7 +36,7 @@ export interface GraphVisualizationProps { } function registerZooming(element: SVGSVGElement) { - const blockScroll = function() { + const blockScroll = function () { (d3.event as Event).preventDefault(); }; d3.select(element) @@ -69,7 +69,7 @@ export function GraphVisualization({ height="100%" pointerEvents="all" id="graphSvg" - ref={element => { + ref={(element) => { if (element && svgRoot.current !== element) { svgRoot.current = element; registerZooming(element); @@ -79,7 +79,7 @@ export function GraphVisualization({ {edges && - edges.map(edge => ( + edges.map((edge) => ( {nodes && nodes - .filter(node => !node.parent) - .map(node => ( + .filter((node) => !node.parent) + .map((node) => ( { + onClick={(e) => { nodeClick(node, e); }} - onMouseDown={e => { + onMouseDown={(e) => { // avoid selecting text when selecting nodes if (e.ctrlKey || e.shiftKey) { e.preventDefault(); diff --git a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 4404ea494b288..583be123a48ce 100644 --- a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -217,7 +217,7 @@ export const GuidancePanel = connect( hasFields: hasFieldsSelector(state), }; }, - dispatch => ({ + (dispatch) => ({ onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => { dispatch( requestDatasource({ diff --git a/x-pack/plugins/graph/public/components/helpers.ts b/x-pack/plugins/graph/public/components/helpers.ts index e53f7f5b12713..db1d1e78f254a 100644 --- a/x-pack/plugins/graph/public/components/helpers.ts +++ b/x-pack/plugins/graph/public/components/helpers.ts @@ -5,5 +5,5 @@ */ export function isEqual(a: T, b: T) { - return (Object.keys(a) as Array).every(key => a[key] === b[key]); + return (Object.keys(a) as Array).every((key) => a[key] === b[key]); } diff --git a/x-pack/plugins/graph/public/components/save_modal.tsx b/x-pack/plugins/graph/public/components/save_modal.tsx index c4459fb1a794f..265d8155e46d6 100644 --- a/x-pack/plugins/graph/public/components/save_modal.tsx +++ b/x-pack/plugins/graph/public/components/save_modal.tsx @@ -37,7 +37,7 @@ export function SaveModal({ const [dataConsent, setDataConsent] = useState(false); return ( { + onSave={(props) => { onSave({ ...props, newDescription, dataConsent }); }} onClose={onClose} @@ -58,7 +58,7 @@ export function SaveModal({ { + onChange={(e) => { setDescription(e.target.value); }} fullWidth @@ -80,7 +80,7 @@ export function SaveModal({ defaultMessage: 'Save graph content', })} checked={dataConsent} - onChange={e => { + onChange={(e) => { setDataConsent(e.target.checked); }} /> diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index 10778124e2011..100122af943e1 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -24,7 +24,7 @@ import { Provider } from 'react-redux'; jest.mock('ui/new_platform'); jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); -const waitForIndexPatternFetch = () => new Promise(r => setTimeout(r)); +const waitForIndexPatternFetch = () => new Promise((r) => setTimeout(r)); function wrapSearchBarInContext(testProps: OuterSearchBarProps) { const services = { diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index ab6d94a78ceec..a74e0ccfb46b5 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -97,7 +97,7 @@ export function SearchBarComponent(props: SearchBarProps) { return ( { + onSubmit={(e) => { e.preventDefault(); if (!isLoading && currentIndexPattern) { onQuerySubmit(queryToString(query, currentIndexPattern)); @@ -185,7 +185,7 @@ export const SearchBar = connect( datasource.current.type === 'indexpattern' ? datasource.current : undefined, }; }, - dispatch => ({ + (dispatch) => ({ onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => { dispatch( requestDatasource({ diff --git a/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx b/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx index 5231f7ddad6df..191655ec7bc17 100644 --- a/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx +++ b/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx @@ -31,7 +31,7 @@ export function AdvancedSettingsForm({ } function getNumberUpdater>(key: K) { - return function({ target: { valueAsNumber } }: { target: { valueAsNumber: number } }) { + return function ({ target: { valueAsNumber } }: { target: { valueAsNumber: number } }) { updateSetting(key, Number.isNaN(valueAsNumber) ? 1 : valueAsNumber); }; } @@ -125,7 +125,7 @@ export function AdvancedSettingsForm({ { defaultMessage: 'No diversification' } )} singleSelection={{ asPlainText: true }} - options={allFields.map(field => ({ label: field.name, value: field }))} + options={allFields.map((field) => ({ label: field.name, value: field }))} selectedOptions={ advancedSettings.sampleDiversityField ? [ @@ -136,7 +136,7 @@ export function AdvancedSettingsForm({ ] : [] } - onChange={choices => { + onChange={(choices) => { updateSetting( 'sampleDiversityField', choices.length === 1 ? choices[0].value : undefined diff --git a/x-pack/plugins/graph/public/components/settings/blacklist_form.tsx b/x-pack/plugins/graph/public/components/settings/blacklist_form.tsx index f7ae04ef9dbcc..68cdcc1fbb7b1 100644 --- a/x-pack/plugins/graph/public/components/settings/blacklist_form.tsx +++ b/x-pack/plugins/graph/public/components/settings/blacklist_form.tsx @@ -48,7 +48,7 @@ export function BlacklistForm({ {blacklistedNodes && unblacklistNode && blacklistedNodes.length > 0 && ( <> - {blacklistedNodes.map(node => ( + {blacklistedNodes.map((node) => ( } key={getListKey(node)} @@ -77,7 +77,7 @@ export function BlacklistForm({ size="s" fill onClick={() => { - blacklistedNodes.forEach(node => { + blacklistedNodes.forEach((node) => { unblacklistNode(node); }); }} diff --git a/x-pack/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/plugins/graph/public/components/settings/settings.test.tsx index 0109e1f5a5ac7..b392a26ecf0d3 100644 --- a/x-pack/plugins/graph/public/components/settings/settings.test.tsx +++ b/x-pack/plugins/graph/public/components/settings/settings.test.tsx @@ -148,7 +148,7 @@ describe('settings', () => { act(() => { instance .find(EuiTab) - .findWhere(node => node.key() === tab) + .findWhere((node) => node.key() === tab) .prop('onClick')!({}); }); instance.update(); @@ -185,7 +185,7 @@ describe('settings', () => { }); it('should switch tab to blacklist', () => { - expect(instance.find(EuiListGroupItem).map(item => item.prop('label'))).toEqual([ + expect(instance.find(EuiListGroupItem).map((item) => item.prop('label'))).toEqual([ 'blacklisted node 1', 'blacklisted node 2', ]); @@ -219,25 +219,19 @@ describe('settings', () => { instance.update(); - expect(instance.find(EuiListGroupItem).map(item => item.prop('label'))).toEqual([ + expect(instance.find(EuiListGroupItem).map((item) => item.prop('label'))).toEqual([ 'blacklisted node 3', ]); }); it('should delete node', () => { - instance - .find(EuiListGroupItem) - .at(0) - .prop('extraAction')!.onClick!({} as any); + instance.find(EuiListGroupItem).at(0).prop('extraAction')!.onClick!({} as any); expect(angularProps.unblacklistNode).toHaveBeenCalledWith(angularProps.blacklistedNodes![0]); }); it('should delete all nodes', () => { - instance - .find('[data-test-subj="graphUnblacklistAll"]') - .find(EuiButton) - .simulate('click'); + instance.find('[data-test-subj="graphUnblacklistAll"]').find(EuiButton).simulate('click'); expect(angularProps.unblacklistNode).toHaveBeenCalledWith(angularProps.blacklistedNodes![0]); expect(angularProps.unblacklistNode).toHaveBeenCalledWith(angularProps.blacklistedNodes![1]); @@ -251,11 +245,9 @@ describe('settings', () => { function insert(formIndex: number, label: string, value: string) { act(() => { - templateForm(formIndex) - .find({ label }) - .first() - .find(EuiFieldText) - .prop('onChange')!({ target: { value } } as React.ChangeEvent); + templateForm(formIndex).find({ label }).first().find(EuiFieldText).prop('onChange')!({ + target: { value }, + } as React.ChangeEvent); }); instance.update(); } @@ -265,12 +257,7 @@ describe('settings', () => { }); it('should switch tab to url templates', () => { - expect( - instance - .find(EuiAccordion) - .at(0) - .prop('buttonContent') - ).toEqual('template'); + expect(instance.find(EuiAccordion).at(0).prop('buttonContent')).toEqual('template'); }); it('should delete url template', () => { @@ -283,9 +270,7 @@ describe('settings', () => { it('should update url template', () => { insert(0, 'Title', 'Updated title'); act(() => { - templateForm(0) - .find('form') - .simulate('submit'); + templateForm(0).find('form').simulate('submit'); }); expect(dispatchSpy).toHaveBeenCalledWith( saveTemplate({ index: 0, template: { ...initialTemplate, description: 'Updated title' } }) @@ -302,9 +287,7 @@ describe('settings', () => { insert(1, 'Title', 'Title'); act(() => { - templateForm(1) - .find('form') - .simulate('submit'); + templateForm(1).find('form').simulate('submit'); }); expect(dispatchSpy).toHaveBeenCalledWith( saveTemplate({ diff --git a/x-pack/plugins/graph/public/components/settings/settings.tsx b/x-pack/plugins/graph/public/components/settings/settings.tsx index d6613cc1e0b9a..3baf6b6a0a2e3 100644 --- a/x-pack/plugins/graph/public/components/settings/settings.tsx +++ b/x-pack/plugins/graph/public/components/settings/settings.tsx @@ -123,7 +123,7 @@ export const Settings = connect + (dispatch) => bindActionCreators( { updateSettings, diff --git a/x-pack/plugins/graph/public/components/settings/url_template_form.tsx b/x-pack/plugins/graph/public/components/settings/url_template_form.tsx index e92d06c3b93a5..ba0017a16f393 100644 --- a/x-pack/plugins/graph/public/components/settings/url_template_form.tsx +++ b/x-pack/plugins/graph/public/components/settings/url_template_form.tsx @@ -100,7 +100,7 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { encoder: currentTemplate.encoder.type === 'kql' ? currentTemplate.encoder - : outlinkEncoders.find(enc => enc.type === 'kql')!, + : outlinkEncoders.find((enc) => enc.type === 'kql')!, }); setAutoformatUrl(false); } @@ -131,13 +131,13 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { 'gphUrlTemplateList__accordion--isOpen': open, })} buttonClassName="gphUrlTemplateList__accordionbutton" - onToggle={isOpen => { + onToggle={(isOpen) => { setOpen(isOpen); }} paddingSize="m" > { + onSubmit={(e) => { e.preventDefault(); onSubmit(currentTemplate); if (!isUpdateForm(props)) { @@ -157,7 +157,7 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { fullWidth value={currentTemplate.description} isInvalid={touched.description && !currentTemplate.description} - onChange={e => setValue('description', e.target.value)} + onChange={(e) => setValue('description', e.target.value)} placeholder={i18n.translate( 'xpack.graph.settings.drillDowns.urlDescriptionInputPlaceholder', { defaultMessage: 'Search on Google' } @@ -212,11 +212,11 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { fullWidth placeholder="https://www.google.co.uk/#q={{gquery}}" value={currentTemplate.url} - onChange={e => { + onChange={(e) => { setValue('url', e.target.value); setAutoformatUrl(false); }} - onPaste={e => { + onPaste={(e) => { e.preventDefault(); const pastedUrl = e.clipboardData.getData('text/plain'); if (isKibanaUrl(pastedUrl)) { @@ -238,14 +238,14 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { fullWidth singleSelection={{ asPlainText: true }} isClearable={false} - options={outlinkEncoders.map(encoder => ({ label: encoder.title, value: encoder }))} + options={outlinkEncoders.map((encoder) => ({ label: encoder.title, value: encoder }))} selectedOptions={[ { label: currentTemplate.encoder.title, value: currentTemplate.encoder, }, ]} - onChange={choices => { + onChange={(choices) => { // choices[0].value can't be null because `isClearable` is set to false above setValue('encoder', choices[0].value!); }} @@ -258,7 +258,7 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { })} >

- {urlTemplateIconChoices.map(icon => ( + {urlTemplateIconChoices.map((icon) => ( formId !== id)); + setUncommittedForms(uncommittedForms.filter((formId) => formId !== id)); } return ( @@ -39,7 +39,7 @@ export function UrlTemplateList({ key={getListKey(template)} id={getListKey(template)} initialTemplate={template} - onSubmit={newTemplate => { + onSubmit={(newTemplate) => { saveTemplate({ index, template: newTemplate }); }} onRemove={() => { @@ -48,11 +48,11 @@ export function UrlTemplateList({ /> ))} - {uncommittedForms.map(id => ( + {uncommittedForms.map((id) => ( { + onSubmit={(newTemplate) => { saveTemplate({ index: -1, template: newTemplate }); removeUncommittedForm(id); }} diff --git a/x-pack/plugins/graph/public/components/settings/use_list_keys.test.tsx b/x-pack/plugins/graph/public/components/settings/use_list_keys.test.tsx index 08821c39a58c4..c80296feccdd4 100644 --- a/x-pack/plugins/graph/public/components/settings/use_list_keys.test.tsx +++ b/x-pack/plugins/graph/public/components/settings/use_list_keys.test.tsx @@ -36,7 +36,7 @@ describe('use_list_keys', () => { }); expect(instance.find('li').length).toEqual(5); - instance.find('li').forEach(el => { + instance.find('li').forEach((el) => { expect(ids.has(el.key())).toEqual(true); }); }); @@ -72,7 +72,7 @@ describe('use_list_keys', () => { }); expect(instance.find('li').length).toEqual(4); - instance.find('li').forEach(el => { + instance.find('li').forEach((el) => { expect(ids.has(el.key())).toEqual(true); }); }); @@ -80,7 +80,7 @@ describe('use_list_keys', () => { function collectIds(instance: ReactWrapper) { const ids = new Set(); - instance.find('li').forEach(el => { + instance.find('li').forEach((el) => { ids.add(el.key()); }); return ids; diff --git a/x-pack/plugins/graph/public/components/settings/use_list_keys.ts b/x-pack/plugins/graph/public/components/settings/use_list_keys.ts index 790d1d77615a5..ecf81d53056d4 100644 --- a/x-pack/plugins/graph/public/components/settings/use_list_keys.ts +++ b/x-pack/plugins/graph/public/components/settings/use_list_keys.ts @@ -30,7 +30,7 @@ export function useListKeys(list: T[]) { const idStore = useRef>(new Map()); const currentIdMap = useMemo(() => { const newMap: Map = new Map(); - list.forEach(item => { + list.forEach((item) => { if (idStore.current.has(item)) { newMap.set(item, idStore.current.get(item)!); } else { diff --git a/x-pack/plugins/graph/public/components/source_picker.tsx b/x-pack/plugins/graph/public/components/source_picker.tsx index 9172f6ba1c65c..acc965eaf3276 100644 --- a/x-pack/plugins/graph/public/components/source_picker.tsx +++ b/x-pack/plugins/graph/public/components/source_picker.tsx @@ -42,7 +42,7 @@ export function SourcePicker({ name: i18n.translate('xpack.graph.sourceModal.savedObjectType.indexPattern', { defaultMessage: 'Index pattern', }), - showSavedObject: indexPattern => !indexPattern.attributes.type, + showSavedObject: (indexPattern) => !indexPattern.attributes.type, includeFields: ['type'], }, ]} diff --git a/x-pack/plugins/graph/public/helpers/as_observable.ts b/x-pack/plugins/graph/public/helpers/as_observable.ts index 48372521babaf..f095002ef25cc 100644 --- a/x-pack/plugins/graph/public/helpers/as_observable.ts +++ b/x-pack/plugins/graph/public/helpers/as_observable.ts @@ -26,7 +26,7 @@ interface Props { export function asAngularSyncedObservable(collectProps: () => Props, angularDigest: () => void) { const boundCollectProps = () => { const collectedProps = collectProps(); - Object.keys(collectedProps).forEach(key => { + Object.keys(collectedProps).forEach((key) => { const currentValue = collectedProps[key]; if (typeof currentValue === 'function') { collectedProps[key] = (...args: unknown[]) => { diff --git a/x-pack/plugins/graph/public/helpers/kql_encoder.ts b/x-pack/plugins/graph/public/helpers/kql_encoder.ts index a12917fb93daa..3cdf8d91352c2 100644 --- a/x-pack/plugins/graph/public/helpers/kql_encoder.ts +++ b/x-pack/plugins/graph/public/helpers/kql_encoder.ts @@ -15,7 +15,7 @@ function escapeQuotes(str: string) { export function asKQL(workspace: Workspace, joinBy: 'and' | 'or') { const nodes = workspace.returnUnpackedGroupeds(workspace.getSelectedOrAllNodes()); const clauses = nodes.map( - node => `"${escapeQuotes(node.data.field)}" : "${escapeQuotes(node.data.term)}"` + (node) => `"${escapeQuotes(node.data.field)}" : "${escapeQuotes(node.data.term)}"` ); const expression = clauses.join(` ${joinBy} `); diff --git a/x-pack/plugins/graph/public/helpers/outlink_encoders.ts b/x-pack/plugins/graph/public/helpers/outlink_encoders.ts index 437641f3f2e8a..3590150fb7305 100644 --- a/x-pack/plugins/graph/public/helpers/outlink_encoders.ts +++ b/x-pack/plugins/graph/public/helpers/outlink_encoders.ts @@ -152,7 +152,7 @@ export const outlinkEncoders: OutlinkEncoder[] = [ const luceneChars = '+-&|!(){}[]^"~*?:\\'; q = q .split('') - .map(char => (luceneChars.includes(char) ? `\\${char}` : char)) + .map((char) => (luceneChars.includes(char) ? `\\${char}` : char)) .join(''); return encodeURIComponent(q); }, diff --git a/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts index 2933e94b86e86..421a72c989757 100644 --- a/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts +++ b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts @@ -74,10 +74,10 @@ export function findSavedWorkspace( perPage: size, searchFields: ['title^3', 'description'], }) - .then(resp => { + .then((resp) => { return { total: resp.total, - hits: resp.savedObjects.map(hit => mapHits(hit, urlFor(basePath, hit.id))), + hits: resp.savedObjects.map((hit) => mapHits(hit, urlFor(basePath, hit.id))), }; }); } diff --git a/x-pack/plugins/graph/public/helpers/style_choices.ts b/x-pack/plugins/graph/public/helpers/style_choices.ts index 46fec39bfce06..f228c9f052b6b 100644 --- a/x-pack/plugins/graph/public/helpers/style_choices.ts +++ b/x-pack/plugins/graph/public/helpers/style_choices.ts @@ -167,12 +167,12 @@ export const iconChoices = [ ]; export const getSuitableIcon = (fieldName: string) => - iconChoices.find(choice => choice.patterns.some(pattern => pattern.test(fieldName))) || + iconChoices.find((choice) => choice.patterns.some((pattern) => pattern.test(fieldName))) || iconChoices[0]; export const iconChoicesByClass: Partial> = {}; -iconChoices.forEach(icon => { +iconChoices.forEach((icon) => { iconChoicesByClass[icon.class] = icon; }); @@ -251,7 +251,7 @@ export const urlTemplateIconChoices = [ ]; export const urlTemplateIconChoicesByClass: Partial> = {}; -urlTemplateIconChoices.forEach(icon => { +urlTemplateIconChoices.forEach((icon) => { urlTemplateIconChoicesByClass[icon.class] = icon; }); diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 9d6d083b3e4b9..e97735c50388f 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -107,7 +107,7 @@ export class GraphPlugin if (this.licensing === null) { throw new Error('Start called before setup'); } - this.licensing.license$.subscribe(license => { + this.licensing.license$.subscribe((license) => { toggleNavLink(checkLicense(license), core.chrome.navLinks); }); } diff --git a/x-pack/plugins/graph/public/services/fetch_top_nodes.ts b/x-pack/plugins/graph/public/services/fetch_top_nodes.ts index d7df3513dba8d..f1f701498372b 100644 --- a/x-pack/plugins/graph/public/services/fetch_top_nodes.ts +++ b/x-pack/plugins/graph/public/services/fetch_top_nodes.ts @@ -55,7 +55,7 @@ function getTopTermsResult(response: TopTermsAggResponse, fieldName: string) { return []; } return response.aggregations.sample[createTopTermsAggName(fieldName)].buckets.map( - bucket => bucket.key + (bucket) => bucket.key ); } @@ -91,7 +91,7 @@ export async function fetchTopNodes( ) { const aggs = fields .map(({ name }) => name) - .map(fieldName => createTopTermsSubAgg(fieldName)) + .map((fieldName) => createTopTermsSubAgg(fieldName)) .reduce((allAggs, subAgg) => ({ ...allAggs, ...subAgg })); const body = createSamplerSearchBody(aggs); @@ -105,7 +105,7 @@ export async function fetchTopNodes( fields.forEach(({ name }) => { const topTerms = getTopTermsResult(response, name); - const fieldNodes = topTerms.map(term => createServerResultNode(name, term, fields)); + const fieldNodes = topTerms.map((term) => createServerResultNode(name, term, fields)); nodes.push(...fieldNodes); }); diff --git a/x-pack/plugins/graph/public/services/persistence/deserialize.ts b/x-pack/plugins/graph/public/services/persistence/deserialize.ts index 06106ed4c4f3f..6fd720a60edc0 100644 --- a/x-pack/plugins/graph/public/services/persistence/deserialize.ts +++ b/x-pack/plugins/graph/public/services/persistence/deserialize.ts @@ -42,7 +42,7 @@ function deserializeUrlTemplate({ iconClass, ...serializableProps }: SerializedUrlTemplate) { - const encoder = outlinkEncoders.find(outlinkEncoder => outlinkEncoder.id === encoderID); + const encoder = outlinkEncoders.find((outlinkEncoder) => outlinkEncoder.id === encoderID); if (!encoder) { return; } @@ -68,7 +68,7 @@ export function lookupIndexPattern( ) { const serializedWorkspaceState: SerializedWorkspaceState = JSON.parse(savedWorkspace.wsState); const indexPattern = indexPatterns.find( - pattern => pattern.attributes.title === serializedWorkspaceState.indexPattern + (pattern) => pattern.attributes.title === serializedWorkspaceState.indexPattern ); if (indexPattern) { @@ -84,7 +84,7 @@ export function mapFields(indexPattern: IndexPattern): WorkspaceField[] { return indexPattern .getNonScriptedFields() .filter( - field => !blockedFieldNames.includes(field.name) && !indexPatternsUtils.isNestedField(field) + (field) => !blockedFieldNames.includes(field.name) && !indexPatternsUtils.isNestedField(field) ) .map((field, index) => ({ name: field.name, @@ -113,8 +113,8 @@ function getFieldsWithWorkspaceSettings( const allFields = mapFields(indexPattern); // merge in selected information into all fields - selectedFields.forEach(serializedField => { - const workspaceField = allFields.find(field => field.name === serializedField.name); + selectedFields.forEach((serializedField) => { + const workspaceField = allFields.find((field) => field.name === serializedField.name); if (!workspaceField) { return; } @@ -132,8 +132,8 @@ function getBlacklistedNodes( serializedWorkspaceState: SerializedWorkspaceState, allFields: WorkspaceField[] ) { - return serializedWorkspaceState.blacklist.map(serializedNode => { - const currentField = allFields.find(field => field.name === serializedNode.field)!; + return serializedWorkspaceState.blacklist.map((serializedNode) => { + const currentField = allFields.find((field) => field.name === serializedNode.field)!; return { x: 0, y: 0, @@ -169,16 +169,16 @@ function getNodesAndEdges( allFields: WorkspaceField[] ): GraphData { return { - nodes: persistedWorkspaceState.vertices.map(serializedNode => ({ + nodes: persistedWorkspaceState.vertices.map((serializedNode) => ({ ...serializedNode, id: '', - icon: allFields.find(field => field.name === serializedNode.field)!.icon, + icon: allFields.find((field) => field.name === serializedNode.field)!.icon, data: { field: serializedNode.field, term: serializedNode.term, }, })), - edges: persistedWorkspaceState.links.map(serializedEdge => ({ + edges: persistedWorkspaceState.links.map((serializedEdge) => ({ ...serializedEdge, id: '', })), @@ -210,7 +210,7 @@ export function savedWorkspaceToAppState( indexPattern, persistedWorkspaceState.selectedFields ); - const selectedFields = allFields.filter(field => field.selected); + const selectedFields = allFields.filter((field) => field.selected); workspaceInstance.options.vertex_fields = selectedFields; // ================== advanced settings ============================= @@ -224,7 +224,7 @@ export function savedWorkspaceToAppState( // restore reference to sample diversity field const serializedField = advancedSettings.sampleDiversityField; advancedSettings.sampleDiversityField = allFields.find( - field => field.name === serializedField.name + (field) => field.name === serializedField.name ); } diff --git a/x-pack/plugins/graph/public/services/persistence/saved_workspace_references.ts b/x-pack/plugins/graph/public/services/persistence/saved_workspace_references.ts index 0948d7a88fce8..c92c831242ad2 100644 --- a/x-pack/plugins/graph/public/services/persistence/saved_workspace_references.ts +++ b/x-pack/plugins/graph/public/services/persistence/saved_workspace_references.ts @@ -53,7 +53,7 @@ export function injectReferences( return; } const indexPatternReference = references.find( - reference => reference.name === state.indexPatternRefName + (reference) => reference.name === state.indexPatternRefName ); if (!indexPatternReference) { // Throw an error as "indexPatternRefName" means the reference exists within diff --git a/x-pack/plugins/graph/public/services/persistence/serialize.ts b/x-pack/plugins/graph/public/services/persistence/serialize.ts index cc6af1b9222f1..6cbebc995d84a 100644 --- a/x-pack/plugins/graph/public/services/persistence/serialize.ts +++ b/x-pack/plugins/graph/public/services/persistence/serialize.ts @@ -97,13 +97,13 @@ export function appStateToSavedWorkspace( canSaveData: boolean ) { const blacklist: SerializedNode[] = canSaveData - ? workspace.blacklistedNodes.map(node => serializeNode(node)) + ? workspace.blacklistedNodes.map((node) => serializeNode(node)) : []; const vertices: SerializedNode[] = canSaveData - ? workspace.nodes.map(node => serializeNode(node, workspace.nodes)) + ? workspace.nodes.map((node) => serializeNode(node, workspace.nodes)) : []; const links: SerializedEdge[] = canSaveData - ? workspace.edges.map(edge => serializeEdge(edge, workspace.nodes)) + ? workspace.edges.map((edge) => serializeEdge(edge, workspace.nodes)) : []; const mappedUrlTemplates = urlTemplates.map(serializeUrlTemplate); diff --git a/x-pack/plugins/graph/public/services/save_modal.tsx b/x-pack/plugins/graph/public/services/save_modal.tsx index 94b5de3be13ac..730fe7b065e47 100644 --- a/x-pack/plugins/graph/public/services/save_modal.tsx +++ b/x-pack/plugins/graph/public/services/save_modal.tsx @@ -60,7 +60,7 @@ export function openSaveModal({ isTitleDuplicateConfirmed, onTitleDuplicate, }; - return saveWorkspace(saveOptions, dataConsent, services).then(response => { + return saveWorkspace(saveOptions, dataConsent, services).then((response) => { // If the save wasn't successful, put the original values back. if (!('id' in response) || !Boolean(response.id)) { workspace.title = currentTitle; diff --git a/x-pack/plugins/graph/public/services/source_modal.tsx b/x-pack/plugins/graph/public/services/source_modal.tsx index 20a5b6d0786bd..d5ffe4c0c9651 100644 --- a/x-pack/plugins/graph/public/services/source_modal.tsx +++ b/x-pack/plugins/graph/public/services/source_modal.tsx @@ -26,7 +26,7 @@ export function openSourceModal( { + onIndexPatternSelected={(indexPattern) => { onSelected(indexPattern); modalRef.close(); }} diff --git a/x-pack/plugins/graph/public/state_management/advanced_settings.ts b/x-pack/plugins/graph/public/state_management/advanced_settings.ts index e6325c1e7fd68..27c784ba71ee1 100644 --- a/x-pack/plugins/graph/public/state_management/advanced_settings.ts +++ b/x-pack/plugins/graph/public/state_management/advanced_settings.ts @@ -52,7 +52,7 @@ export const syncSettingsSaga = ({ getWorkspace, notifyAngular }: GraphStoreDepe notifyAngular(); } - return function*() { + return function* () { yield takeLatest(updateSettings.match, syncSettings); }; }; diff --git a/x-pack/plugins/graph/public/state_management/datasource.sagas.ts b/x-pack/plugins/graph/public/state_management/datasource.sagas.ts index 018b3b42b9157..f468ce5beb21c 100644 --- a/x-pack/plugins/graph/public/state_management/datasource.sagas.ts +++ b/x-pack/plugins/graph/public/state_management/datasource.sagas.ts @@ -50,7 +50,7 @@ export const datasourceSaga = ({ } } - return function*() { + return function* () { yield takeLatest(requestDatasource.match, fetchFields); }; }; diff --git a/x-pack/plugins/graph/public/state_management/datasource.test.ts b/x-pack/plugins/graph/public/state_management/datasource.test.ts index 84f3741604e20..13b7080d776a2 100644 --- a/x-pack/plugins/graph/public/state_management/datasource.test.ts +++ b/x-pack/plugins/graph/public/state_management/datasource.test.ts @@ -12,7 +12,7 @@ import { fieldsSelector } from './fields'; import { updateSettings } from './advanced_settings'; import { IndexPattern } from '../../../../../src/plugins/data/public'; -const waitForPromise = () => new Promise(r => setTimeout(r)); +const waitForPromise = () => new Promise((r) => setTimeout(r)); describe('datasource saga', () => { let env: MockedGraphEnvironment; diff --git a/x-pack/plugins/graph/public/state_management/datasource.ts b/x-pack/plugins/graph/public/state_management/datasource.ts index fac4f0da1edb0..4f86b6b0fd072 100644 --- a/x-pack/plugins/graph/public/state_management/datasource.ts +++ b/x-pack/plugins/graph/public/state_management/datasource.ts @@ -57,7 +57,7 @@ export const datasourceReducer = reducerWithInitialState(initia current: newDatasource, loading: true, })) - .case(datasourceLoaded, datasource => ({ + .case(datasourceLoaded, (datasource) => ({ ...datasource, loading: false, })) @@ -66,5 +66,5 @@ export const datasourceReducer = reducerWithInitialState(initia export const datasourceSelector = (state: GraphState) => state.datasource; export const hasDatasourceSelector = createSelector( datasourceSelector, - datasource => datasource.current.type !== 'none' + (datasource) => datasource.current.type !== 'none' ); diff --git a/x-pack/plugins/graph/public/state_management/fields.ts b/x-pack/plugins/graph/public/state_management/fields.ts index 865de323332c4..a54f9fd07b166 100644 --- a/x-pack/plugins/graph/public/state_management/fields.ts +++ b/x-pack/plugins/graph/public/state_management/fields.ts @@ -33,7 +33,7 @@ export const fieldsReducer = reducerWithInitialState(initialFields) .case(setDatasource, () => initialFields) .case(loadFields, (_currentFields, newFields) => { const newFieldMap: Record = {}; - newFields.forEach(field => { + newFields.forEach((field) => { newFieldMap[field.name] = field; }); @@ -51,16 +51,16 @@ export const fieldsReducer = reducerWithInitialState(initialFields) .build(); export const fieldMapSelector = (state: GraphState) => state.fields; -export const fieldsSelector = createSelector(fieldMapSelector, fields => Object.values(fields)); -export const selectedFieldsSelector = createSelector(fieldsSelector, fields => - fields.filter(field => field.selected) +export const fieldsSelector = createSelector(fieldMapSelector, (fields) => Object.values(fields)); +export const selectedFieldsSelector = createSelector(fieldsSelector, (fields) => + fields.filter((field) => field.selected) ); -export const liveResponseFieldsSelector = createSelector(selectedFieldsSelector, fields => - fields.filter(field => field.hopSize && field.hopSize > 0) +export const liveResponseFieldsSelector = createSelector(selectedFieldsSelector, (fields) => + fields.filter((field) => field.hopSize && field.hopSize > 0) ); export const hasFieldsSelector = createSelector( selectedFieldsSelector, - fields => fields.length > 0 + (fields) => fields.length > 0 ); /** @@ -72,7 +72,7 @@ export const updateSaveButtonSaga = ({ notifyAngular }: GraphStoreDependencies) function* notify(): IterableIterator { notifyAngular(); } - return function*() { + return function* () { yield takeLatest(matchesOne(selectField, deselectField), notify); }; }; @@ -94,7 +94,7 @@ export const syncFieldsSaga = ({ getWorkspace, setLiveResponseFields }: GraphSto workspace.options.vertex_fields = selectedFieldsSelector(currentState); setLiveResponseFields(liveResponseFieldsSelector(currentState)); } - return function*() { + return function* () { yield takeEvery( matchesOne(loadFields, selectField, deselectField, updateFieldProperties), syncFields @@ -116,7 +116,7 @@ export const syncNodeStyleSaga = ({ getWorkspace, notifyAngular }: GraphStoreDep } const newColor = action.payload.fieldProperties.color; if (newColor) { - workspace.nodes.forEach(function(node) { + workspace.nodes.forEach(function (node) { if (node.data.field === action.payload.fieldName) { node.color = newColor; } @@ -125,7 +125,7 @@ export const syncNodeStyleSaga = ({ getWorkspace, notifyAngular }: GraphStoreDep const newIcon = action.payload.fieldProperties.icon; if (newIcon) { - workspace.nodes.forEach(function(node) { + workspace.nodes.forEach(function (node) { if (node.data.field === action.payload.fieldName) { node.icon = newIcon; } @@ -137,7 +137,7 @@ export const syncNodeStyleSaga = ({ getWorkspace, notifyAngular }: GraphStoreDep workspace.options.vertex_fields = selectedFields; } - return function*() { + return function* () { yield takeLatest(updateFieldProperties.match, syncNodeStyle); }; }; diff --git a/x-pack/plugins/graph/public/state_management/helpers.ts b/x-pack/plugins/graph/public/state_management/helpers.ts index 215691d454484..05773e545ec5e 100644 --- a/x-pack/plugins/graph/public/state_management/helpers.ts +++ b/x-pack/plugins/graph/public/state_management/helpers.ts @@ -25,4 +25,4 @@ export type InferActionType = X extends ActionCreator ? T : never; * @param actionCreators The action creators to create a unified matcher for */ export const matchesOne = (...actionCreators: Array>) => (action: AnyAction) => - actionCreators.some(actionCreator => actionCreator.match(action)); + actionCreators.some((actionCreator) => actionCreator.match(action)); diff --git a/x-pack/plugins/graph/public/state_management/meta_data.ts b/x-pack/plugins/graph/public/state_management/meta_data.ts index 560216568f65f..5ed2b68e22c86 100644 --- a/x-pack/plugins/graph/public/state_management/meta_data.ts +++ b/x-pack/plugins/graph/public/state_management/meta_data.ts @@ -53,7 +53,7 @@ export const syncBreadcrumbSaga = ({ chrome, changeUrl }: GraphStoreDependencies }, }); } - return function*() { + return function* () { // initial sync yield call(syncBreadcrumb); yield takeLatest(updateMetaData.match, syncBreadcrumb); diff --git a/x-pack/plugins/graph/public/state_management/mocks.ts b/x-pack/plugins/graph/public/state_management/mocks.ts index 02a5830ffd6be..5a0269d691de2 100644 --- a/x-pack/plugins/graph/public/state_management/mocks.ts +++ b/x-pack/plugins/graph/public/state_management/mocks.ts @@ -103,7 +103,7 @@ export function createMockGraphStore({ store.dispatch = jest.fn(store.dispatch); - sagas.forEach(sagaCreator => { + sagas.forEach((sagaCreator) => { sagaMiddleware.run(sagaCreator(mockedDeps)); }); diff --git a/x-pack/plugins/graph/public/state_management/persistence.test.ts b/x-pack/plugins/graph/public/state_management/persistence.test.ts index 285bf2d6a0ea9..efad5f95fd839 100644 --- a/x-pack/plugins/graph/public/state_management/persistence.test.ts +++ b/x-pack/plugins/graph/public/state_management/persistence.test.ts @@ -15,7 +15,7 @@ import { lookupIndexPattern, appStateToSavedWorkspace } from '../services/persis import { settingsSelector } from './advanced_settings'; import { openSaveModal } from '../services/save_modal'; -const waitForPromise = () => new Promise(r => setTimeout(r)); +const waitForPromise = () => new Promise((r) => setTimeout(r)); jest.mock('../services/persistence', () => ({ lookupIndexPattern: jest.fn(() => ({ id: '123', attributes: { title: 'test-pattern' } })), diff --git a/x-pack/plugins/graph/public/state_management/persistence.ts b/x-pack/plugins/graph/public/state_management/persistence.ts index 8dd1386f70e6e..cd2c6680c1fd2 100644 --- a/x-pack/plugins/graph/public/state_management/persistence.ts +++ b/x-pack/plugins/graph/public/state_management/persistence.ts @@ -87,7 +87,7 @@ export const loadingSaga = ({ getWorkspace()!.runLayout(); } - return function*() { + return function* () { yield takeLatest(loadSavedWorkspace.match, deserializeWorkspace); }; }; @@ -119,7 +119,7 @@ export const savingSaga = (deps: GraphStoreDependencies) => { } } - return function*() { + return function* () { yield takeLatest(saveWorkspace.match, persistWorkspace); }; }; diff --git a/x-pack/plugins/graph/public/state_management/url_templates.ts b/x-pack/plugins/graph/public/state_management/url_templates.ts index d9f95a498ea5b..19de52d444209 100644 --- a/x-pack/plugins/graph/public/state_management/url_templates.ts +++ b/x-pack/plugins/graph/public/state_management/url_templates.ts @@ -35,7 +35,7 @@ function generateDefaultTemplate( datasource: IndexpatternDatasource, addBasePath: (url: string) => string ): UrlTemplate { - const appPath = modifyUrl('/', parsed => { + const appPath = modifyUrl('/', (parsed) => { parsed.query._a = rison.encode({ columns: ['_source'], index: datasource.id, @@ -78,7 +78,7 @@ export const urlTemplatesReducer = (addBasePath: (url: string) => string) => if (datasource.type === 'none') { return initialTemplates; } - const customTemplates = templates.filter(template => !template.isDefault); + const customTemplates = templates.filter((template) => !template.isDefault); return [...customTemplates, generateDefaultTemplate(datasource, addBasePath)]; }) .case(loadTemplates, (_currentTemplates, newTemplates) => newTemplates) @@ -90,7 +90,7 @@ export const urlTemplatesReducer = (addBasePath: (url: string) => string) => : templates.map((template, index) => (index === indexToUpdate ? newTemplate : template)); }) .case(removeTemplate, (templates, templateToDelete) => - templates.filter(template => template !== templateToDelete) + templates.filter((template) => template !== templateToDelete) ) .build(); @@ -108,7 +108,7 @@ export const syncTemplatesSaga = ({ setUrlTemplates, notifyAngular }: GraphStore notifyAngular(); } - return function*() { + return function* () { yield takeEvery( matchesOne(loadTemplates, saveTemplate, removeTemplate, requestDatasource, setDatasource), syncTemplates diff --git a/x-pack/plugins/graph/public/state_management/workspace.ts b/x-pack/plugins/graph/public/state_management/workspace.ts index b18b8185ceeca..7c28d09c6424f 100644 --- a/x-pack/plugins/graph/public/state_management/workspace.ts +++ b/x-pack/plugins/graph/public/state_management/workspace.ts @@ -60,7 +60,7 @@ export const fillWorkspaceSaga = ({ } } - return function*() { + return function* () { yield takeLatest(fillWorkspace.match, fetchNodes); }; }; diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index ceced840bdbc6..648010eeeafeb 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -32,7 +32,9 @@ export function registerExploreRoute({ { core: { elasticsearch: { - dataClient: { callAsCurrentUser: callCluster }, + legacy: { + client: { callAsCurrentUser: callCluster }, + }, }, }, }, @@ -57,7 +59,7 @@ export function registerExploreRoute({ error, 'body.error.root_cause', [] as Array<{ type: string; reason: string }> - ).find(cause => { + ).find((cause) => { return ( cause.reason.includes('Fielddata is disabled on text fields') || cause.reason.includes('No support for examining floating point') || diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts index 6e9fe508af3d3..645e6b520013f 100644 --- a/x-pack/plugins/graph/server/routes/search.ts +++ b/x-pack/plugins/graph/server/routes/search.ts @@ -7,6 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { LicenseState, verifyApiAccess } from '../lib/license_state'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/server'; export function registerSearchRoute({ router, @@ -31,7 +32,9 @@ export function registerSearchRoute({ core: { uiSettings: { client: uiSettings }, elasticsearch: { - dataClient: { callAsCurrentUser: callCluster }, + legacy: { + client: { callAsCurrentUser: callCluster }, + }, }, }, }, @@ -39,7 +42,7 @@ export function registerSearchRoute({ response ) => { verifyApiAccess(licenseState); - const includeFrozen = await uiSettings.get('search:includeFrozen'); + const includeFrozen = await uiSettings.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); try { return response.ok({ body: { diff --git a/x-pack/plugins/graph/server/sample_data/register_sample_data.ts b/x-pack/plugins/graph/server/sample_data/register_sample_data.ts index 9a05b656b61a4..d11f5ce7bb489 100644 --- a/x-pack/plugins/graph/server/sample_data/register_sample_data.ts +++ b/x-pack/plugins/graph/server/sample_data/register_sample_data.ts @@ -25,7 +25,7 @@ export function registerSampleData( throw new Error('License state has to be initialized before registering sample data'); } let registered = false; - licenseUpdates.subscribe(licenseInformation => { + licenseUpdates.subscribe((licenseInformation) => { if (!registered && licenseInformation.showAppLink) { registered = true; registerEcommerceSampleDataLink(sampleDataRegistry); diff --git a/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js index c27f3314e60ae..83be5520943b1 100644 --- a/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js +++ b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js @@ -37,17 +37,17 @@ export class GrokDebuggerComponent extends React.Component { this.grokdebuggerRequest = new GrokdebuggerRequest(); } - onRawEventChange = rawEvent => { + onRawEventChange = (rawEvent) => { this.setState({ rawEvent }); this.grokdebuggerRequest.rawEvent = rawEvent.trimEnd(); }; - onPatternChange = pattern => { + onPatternChange = (pattern) => { this.setState({ pattern }); this.grokdebuggerRequest.pattern = pattern.trimEnd(); }; - onCustomPatternsChange = customPatterns => { + onCustomPatternsChange = (customPatterns) => { this.setState({ customPatterns }); customPatterns = customPatterns.trim(); @@ -58,7 +58,7 @@ export class GrokDebuggerComponent extends React.Component { return; } - customPatterns.split('\n').forEach(customPattern => { + customPatterns.split('\n').forEach((customPattern) => { // Patterns are defined like so: // patternName patternDefinition // For example: diff --git a/x-pack/plugins/grokdebugger/public/plugin.js b/x-pack/plugins/grokdebugger/public/plugin.js index 5f1534df9f0ae..6ac600c9dc97b 100644 --- a/x-pack/plugins/grokdebugger/public/plugin.js +++ b/x-pack/plugins/grokdebugger/public/plugin.js @@ -28,7 +28,7 @@ export class Plugin { }, }); - plugins.licensing.license$.subscribe(license => { + plugins.licensing.license$.subscribe((license) => { if (!license.isActive && !devTool.isDisabled()) { devTool.disable(); } else if (devTool.isDisabled()) { diff --git a/x-pack/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js b/x-pack/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js index e26c9c5091e14..207093e72ca2c 100644 --- a/x-pack/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js +++ b/x-pack/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js @@ -17,10 +17,10 @@ export class GrokdebuggerService { .post(`${ROUTES.API_ROOT}/simulate`, { body: JSON.stringify(grokdebuggerRequest.upstreamJSON), }) - .then(response => { + .then((response) => { return GrokdebuggerResponse.fromUpstreamJSON(response); }) - .catch(e => { + .catch((e) => { throw e.body.message; }); } diff --git a/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts b/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts index 749f5e9ebf8f0..015a2e250bb0e 100644 --- a/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts +++ b/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts @@ -100,6 +100,6 @@ export class KibanaFramework { options?: any ) { const { elasticsearch } = requestContext.core; - return elasticsearch.dataClient.callAsCurrentUser(endpoint, options); + return elasticsearch.legacy.client.callAsCurrentUser(endpoint, options); } } diff --git a/x-pack/plugins/grokdebugger/server/plugin.js b/x-pack/plugins/grokdebugger/server/plugin.js index 06ddd92aefac9..49298fd1e5ab7 100644 --- a/x-pack/plugins/grokdebugger/server/plugin.js +++ b/x-pack/plugins/grokdebugger/server/plugin.js @@ -18,7 +18,7 @@ export class Plugin { setup(coreSetup, plugins) { const framework = new KibanaFramework(coreSetup); - plugins.licensing.license$.subscribe(license => { + plugins.licensing.license$.subscribe((license) => { framework.setLicense(license); }); diff --git a/x-pack/plugins/index_lifecycle_management/README.md b/x-pack/plugins/index_lifecycle_management/README.md new file mode 100644 index 0000000000000..3b72ac85810c6 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/README.md @@ -0,0 +1,95 @@ +# Index Lifecycle Management + +## Quick steps for testing ILM in Index Management + +You can test that the `Frozen` badge, phase filtering, and lifecycle information is surfaced in +Index Management by running this series of requests in Console: + +``` +PUT /_ilm/policy/full +{ + "policy": { + "phases" : { + "hot" : { + "min_age" : "0ms", + "actions" : { + "rollover" : { + "max_docs" : 1 + } + } + }, + "warm" : { + "min_age" : "15s", + "actions" : { + "forcemerge" : { + "max_num_segments" : 1 + }, + "shrink" : { + "number_of_shards" : 1 + } + } + }, + "cold" : { + "min_age" : "30s", + "actions" : { + "freeze": {} + } + }, + "delete" : { + "min_age" : "1d", + "actions" : { + "delete" : { } + } + } + } + } +} + +PUT _template/test +{ + "index_patterns": ["test-*"], + "settings": { + "number_of_shards": 3, + "number_of_replicas": 0, + "index.lifecycle.name": "full", + "index.lifecycle.rollover_alias": "test-alias" + } +} + +PUT /test-000001 +{ + "aliases": { + "test-alias": { + "is_write_index": true + } + } +} + +PUT test-alias/_doc/1 +{ + "a": "a" +} + +PUT /_cluster/settings +{ + "transient": { + "logger.org.elasticsearch.xpack.core.indexlifecycle": "TRACE", + "logger.org.elasticsearch.xpack.indexlifecycle": "TRACE", + "logger.org.elasticsearch.xpack.core.ilm": "TRACE", + "logger.org.elasticsearch.xpack.ilm": "TRACE", + "indices.lifecycle.poll_interval": "10s" + } +} +``` + +Then go into Index Management and, after about 1 minute, you'll see a frozen index and +you'll be able to filter by the various lifecycle phases and statuses. + +![image](https://user-images.githubusercontent.com/1238659/78087831-29ee3180-7377-11ea-8e24-14cdc4035bb2.png) + +Next, add the Kibana sample data and attach the `full` policy to the index that gets created. +After about a minute, there should be an error on this index. When you click the index you'll see +ILM information in the detail panel as well as an error. You can dismiss the error by clicking +`Manage > Retry lifecycle step`. + +![image](https://user-images.githubusercontent.com/1238659/78087984-a6811000-7377-11ea-880e-1a7b182c14f1.png) \ No newline at end of file diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index d64c8c6239fcd..9441ffd731524 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -92,6 +92,7 @@ Array [ exports[`extend index management ilm summary extension should return extension when index has lifecycle error 1`] = ` testy @@ -564,6 +565,7 @@ exports[`extend index management ilm summary extension should return extension w exports[`extend index management ilm summary extension should return extension when index has lifecycle policy 1`] = ` testy diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap index 857a63826505e..5edc5a9343fc3 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap @@ -91,11 +91,10 @@ exports[`policy table should show empty state when there are not any policies 1`
path); +initHttp(axios.create({ adapter: axiosXhrAdapter }), (path) => path); initUiMetric({ reportUiStats: () => {} }); initNotification({ addDanger: () => {}, @@ -59,9 +59,7 @@ const policies = []; for (let i = 0; i < 105; i++) { policies.push({ version: i, - modified_date: moment() - .subtract(i, 'days') - .valueOf(), + modified_date: moment().subtract(i, 'days').valueOf(), linkedIndices: i % 2 === 0 ? [`index${i}`] : null, name: `testy${i}`, policy: { @@ -80,7 +78,7 @@ const activatePhase = (rendered, phase) => { const expectedErrorMessages = (rendered, expectedErrorMessages) => { const errorMessages = rendered.find('.euiFormErrorText'); expect(errorMessages.length).toBe(expectedErrorMessages.length); - expectedErrorMessages.forEach(expectedErrorMessage => { + expectedErrorMessages.forEach((expectedErrorMessage) => { let foundErrorMessage; for (let i = 0; i < errorMessages.length; i++) { if (errorMessages.at(i).text() === expectedErrorMessage) { @@ -90,7 +88,7 @@ const expectedErrorMessages = (rendered, expectedErrorMessages) => { expect(foundErrorMessage).toBe(true); }); }; -const noRollover = rendered => { +const noRollover = (rendered) => { findTestSubject(rendered, 'rolloverSwitch').simulate('click'); rendered.update(); }; @@ -112,7 +110,7 @@ const setPhaseIndexPriority = (rendered, phase, priority) => { priorityInput.simulate('change', { target: { value: priority } }); rendered.update(); }; -const save = rendered => { +const save = (rendered) => { const saveButton = findTestSubject(rendered, 'savePolicyButton'); saveButton.simulate('click'); rendered.update(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js b/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js index 78c5c181eea62..60e3e9443bec9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js @@ -12,6 +12,7 @@ import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import sinon from 'sinon'; import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test'; +import { scopedHistoryMock } from '../../../../../src/core/public/mocks'; import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; import { fetchedPolicies } from '../../public/application/store/actions'; import { indexLifecycleManagementStore } from '../../public/application/store'; @@ -19,7 +20,7 @@ import { PolicyTable } from '../../public/application/sections/policy_table'; import { init as initHttp } from '../../public/application/services/http'; import { init as initUiMetric } from '../../public/application/services/ui_metric'; -initHttp(axios.create({ adapter: axiosXhrAdapter }), path => path); +initHttp(axios.create({ adapter: axiosXhrAdapter }), (path) => path); initUiMetric({ reportUiStats: () => {} }); let server = null; @@ -29,9 +30,7 @@ const policies = []; for (let i = 0; i < 105; i++) { policies.push({ version: i, - modified_date: moment() - .subtract(i, 'days') - .valueOf(), + modified_date: moment().subtract(i, 'days').valueOf(), linkedIndices: i % 2 === 0 ? [`index${i}`] : null, name: `testy${i}`, }); @@ -39,20 +38,20 @@ for (let i = 0; i < 105; i++) { jest.mock(''); let component = null; -const snapshot = rendered => { +const snapshot = (rendered) => { expect(rendered).toMatchSnapshot(); }; -const mountedSnapshot = rendered => { +const mountedSnapshot = (rendered) => { expect(takeMountedSnapshot(rendered)).toMatchSnapshot(); }; -const names = rendered => { +const names = (rendered) => { return findTestSubject(rendered, 'policyTablePolicyNameLink'); }; -const namesText = rendered => { - return names(rendered).map(button => button.text()); +const namesText = (rendered) => { + return names(rendered).map((button) => button.text()); }; -const testSort = headerName => { +const testSort = (headerName) => { const rendered = mountWithIntl(component); const nameHeader = findTestSubject(rendered, `policyTableHeaderCell-${headerName}`).find( 'button' @@ -64,7 +63,7 @@ const testSort = headerName => { rendered.update(); snapshot(namesText(rendered)); }; -const openContextMenu = buttonIndex => { +const openContextMenu = (buttonIndex) => { const rendered = mountWithIntl(component); const actionsButton = findTestSubject(rendered, 'policyActionsContextMenuButton'); actionsButton.at(buttonIndex).simulate('click'); @@ -77,7 +76,7 @@ describe('policy table', () => { store = indexLifecycleManagementStore(); component = ( - + {}} /> ); store.dispatch(fetchedPolicies(policies)); @@ -92,7 +91,7 @@ describe('policy table', () => { store = indexLifecycleManagementStore(); component = ( - + {}} /> ); const rendered = mountWithIntl(component); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js index 900de27ca36ab..4fa1838115840 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js +++ b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js @@ -23,7 +23,7 @@ import { init as initUiMetric } from '../public/application/services/ui_metric'; // We need to init the http with a mock for any tests that depend upon the http service. // For example, add_lifecycle_confirm_modal makes an API request in its componentDidMount // lifecycle method. If we don't mock this, CI will fail with "Call retries were exceeded". -initHttp(axios.create({ adapter: axiosXhrAdapter }), path => path); +initHttp(axios.create({ adapter: axiosXhrAdapter }), (path) => path); initUiMetric({ reportUiStats: () => {} }); jest.mock('../../../plugins/index_management/public', async () => { @@ -115,6 +115,10 @@ const indexWithLifecycleError = { moment.tz.setDefault('utc'); +const getUrlForApp = (appId, options) => { + return appId + '/' + (options ? options.path : ''); +}; + describe('extend index management', () => { describe('retry lifecycle action extension', () => { test('should return null when no indices have index lifecycle policy', () => { @@ -171,13 +175,17 @@ describe('extend index management', () => { describe('add lifecycle policy action extension', () => { test('should return null when index has index lifecycle policy', () => { - const extension = addLifecyclePolicyActionExtension({ indices: [indexWithLifecyclePolicy] }); + const extension = addLifecyclePolicyActionExtension( + { indices: [indexWithLifecyclePolicy] }, + getUrlForApp + ); expect(extension).toBeNull(); }); test('should return null when more than one index is passed', () => { const extension = addLifecyclePolicyActionExtension({ indices: [indexWithoutLifecyclePolicy, indexWithoutLifecyclePolicy], + getUrlForApp, }); expect(extension).toBeNull(); }); @@ -185,6 +193,7 @@ describe('extend index management', () => { test('should return extension when one index is passed and it does not have lifecycle policy', () => { const extension = addLifecyclePolicyActionExtension({ indices: [indexWithoutLifecyclePolicy], + getUrlForApp, }); expect(extension.renderConfirmModal).toBeDefined; const component = extension.renderConfirmModal(jest.fn()); @@ -220,20 +229,20 @@ describe('extend index management', () => { describe('ilm summary extension', () => { test('should render null when index has no index lifecycle policy', () => { - const extension = ilmSummaryExtension(indexWithoutLifecyclePolicy); + const extension = ilmSummaryExtension(indexWithoutLifecyclePolicy, getUrlForApp); const rendered = mountWithIntl(extension); expect(rendered.isEmptyRender()).toBeTruthy(); }); test('should return extension when index has lifecycle policy', () => { - const extension = ilmSummaryExtension(indexWithLifecyclePolicy); + const extension = ilmSummaryExtension(indexWithLifecyclePolicy, getUrlForApp); expect(extension).toBeDefined(); const rendered = mountWithIntl(extension); expect(rendered).toMatchSnapshot(); }); test('should return extension when index has lifecycle error', () => { - const extension = ilmSummaryExtension(indexWithLifecycleError); + const extension = ilmSummaryExtension(indexWithLifecycleError, getUrlForApp); expect(extension).toBeDefined(); const rendered = mountWithIntl(extension); expect(rendered).toMatchSnapshot(); diff --git a/x-pack/plugins/index_lifecycle_management/common/constants/index.ts b/x-pack/plugins/index_lifecycle_management/common/constants/index.ts index 59e623934c60d..5c89b917163d8 100644 --- a/x-pack/plugins/index_lifecycle_management/common/constants/index.ts +++ b/x-pack/plugins/index_lifecycle_management/common/constants/index.ts @@ -17,6 +17,4 @@ export const PLUGIN = { }), }; -export const BASE_PATH = '/management/data/index_lifecycle_management/'; - export const API_BASE_PATH = '/api/index_lifecycle_management'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx index 993dced20bbe6..11cd5d181f4ad 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx @@ -5,25 +5,35 @@ */ import React, { useEffect } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { ScopedHistory, ApplicationStart } from 'kibana/public'; import { METRIC_TYPE } from '@kbn/analytics'; -import { BASE_PATH } from '../../common/constants'; import { UIM_APP_LOAD } from './constants'; import { EditPolicy } from './sections/edit_policy'; import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services/ui_metric'; -export const App = () => { +export const App = ({ + history, + navigateToApp, +}: { + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; +}) => { useEffect(() => trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD), []); return ( - + - - - + + } + /> + - + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx index a7d88d31e58fc..eddbb5528ad84 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -7,17 +7,22 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; -import { I18nStart } from 'kibana/public'; +import { I18nStart, ScopedHistory, ApplicationStart } from 'kibana/public'; import { UnmountCallback } from 'src/core/public'; import { App } from './app'; import { indexLifecycleManagementStore } from './store'; -export const renderApp = (element: Element, I18nContext: I18nStart['Context']): UnmountCallback => { +export const renderApp = ( + element: Element, + I18nContext: I18nStart['Context'], + history: ScopedHistory, + navigateToApp: ApplicationStart['navigateToApp'] +): UnmountCallback => { render( - + , element diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js index 1f2468e79ebd3..d4605ceb43499 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js @@ -12,7 +12,7 @@ import { PHASE_COLD, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../const import { ColdPhase as PresentationComponent } from './cold_phase'; export const ColdPhase = connect( - state => ({ + (state) => ({ phaseData: getPhase(state, PHASE_COLD), hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], }), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js index 02cd1a968b617..d5c0744e5eb07 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js @@ -89,7 +89,7 @@ export class ColdPhase extends PureComponent { } id={`${PHASE_COLD}-${PHASE_ENABLED}`} checked={phaseData[PHASE_ENABLED]} - onChange={e => { + onChange={(e) => { setPhaseData(PHASE_ENABLED, e.target.checked); }} aria-controls="coldPhaseContent" @@ -146,7 +146,7 @@ export class ColdPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_REPLICA_COUNT, e.target.value); }} min={0} @@ -187,7 +187,7 @@ export class ColdPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_FREEZE_ENABLED, e.target.checked); }} label={freezeLabel} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js index 74ec9b2c98ed9..84bd17e3637e8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js @@ -11,7 +11,7 @@ import { PHASE_DELETE, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../con import { DeletePhase as PresentationComponent } from './delete_phase'; export const DeletePhase = connect( - state => ({ + (state) => ({ phaseData: getPhase(state, PHASE_DELETE), hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], }), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js index 146b5c36847db..3b3e489d38f7d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js @@ -63,7 +63,7 @@ export class DeletePhase extends PureComponent { } id={`${PHASE_DELETE}-${PHASE_ENABLED}`} checked={phaseData[PHASE_ENABLED]} - onChange={e => { + onChange={(e) => { setPhaseData(PHASE_ENABLED, e.target.checked); }} aria-controls="deletePhaseContent" diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js index 818af79466f6b..5f1451afdcc31 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js @@ -12,11 +12,11 @@ import { PHASE_HOT, PHASE_WARM, WARM_PHASE_ON_ROLLOVER } from '../../../../const import { HotPhase as PresentationComponent } from './hot_phase'; export const HotPhase = connect( - state => ({ + (state) => ({ phaseData: getPhase(state, PHASE_HOT), }), { setPhaseData: (key, value) => setPhaseData(PHASE_HOT, key, value), - setWarmPhaseOnRollover: value => setPhaseData(PHASE_WARM, WARM_PHASE_ON_ROLLOVER, value), + setWarmPhaseOnRollover: (value) => setPhaseData(PHASE_WARM, WARM_PHASE_ON_ROLLOVER, value), } )(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js index 475d26bb2e3c0..b420442198712 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js @@ -100,7 +100,7 @@ export class HotPhase extends PureComponent { { + onChange={async (e) => { const { checked } = e.target; setPhaseData(PHASE_ROLLOVER_ENABLED, checked); setWarmPhaseOnRollover(checked); @@ -130,7 +130,7 @@ export class HotPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_ROLLOVER_MAX_SIZE_STORED, e.target.value); }} min={1} @@ -153,7 +153,7 @@ export class HotPhase extends PureComponent { } )} value={phaseData[PHASE_ROLLOVER_MAX_SIZE_STORED_UNITS]} - onChange={e => { + onChange={(e) => { setPhaseData(PHASE_ROLLOVER_MAX_SIZE_STORED_UNITS, e.target.value); }} options={[ @@ -216,7 +216,7 @@ export class HotPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_ROLLOVER_MAX_DOCUMENTS, e.target.value); }} min={1} @@ -239,7 +239,7 @@ export class HotPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_ROLLOVER_MAX_AGE, e.target.value); }} min={1} @@ -262,7 +262,7 @@ export class HotPhase extends PureComponent { } )} value={phaseData[PHASE_ROLLOVER_MAX_AGE_UNITS]} - onChange={e => { + onChange={(e) => { setPhaseData(PHASE_ROLLOVER_MAX_AGE_UNITS, e.target.value); }} options={[ diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js index 2300979851329..28bc8671f29e2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js @@ -68,7 +68,7 @@ function getUnitsAriaLabelForPhase(phase) { } } -export const MinAgeInput = props => { +export const MinAgeInput = (props) => { const { rolloverEnabled, errors, phaseData, phase, setPhaseData, isShowingErrors } = props; let daysOptionLabel; @@ -140,6 +140,41 @@ export const MinAgeInput = props => { defaultMessage: 'hours from index creation', } ); + + minutesOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel', + { + defaultMessage: 'minutes from index creation', + } + ); + + secondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel', + { + defaultMessage: 'seconds from index creation', + } + ); + + millisecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel', + { + defaultMessage: 'milliseconds from index creation', + } + ); + + microsecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel', + { + defaultMessage: 'microseconds from index creation', + } + ); + + nanosecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel', + { + defaultMessage: 'nanoseconds from index creation', + } + ); } return ( @@ -166,7 +201,7 @@ export const MinAgeInput = props => { { + onChange={async (e) => { setPhaseData(PHASE_ROLLOVER_MINIMUM_AGE, e.target.value); }} min={0} @@ -178,7 +213,7 @@ export const MinAgeInput = props => { setPhaseData(PHASE_ROLLOVER_MINIMUM_AGE_UNITS, e.target.value)} + onChange={(e) => setPhaseData(PHASE_ROLLOVER_MINIMUM_AGE_UNITS, e.target.value)} options={[ { value: 'd', diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.container.js index a92959a5b31cf..0ddfcbb940aa4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.container.js @@ -11,7 +11,7 @@ import { fetchNodes } from '../../../../store/actions'; import { NodeAllocation as PresentationComponent } from './node_allocation'; export const NodeAllocation = connect( - state => ({ + (state) => ({ nodeOptions: getNodeOptions(state), }), { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.js index 528aa85beaecc..95c1878776688 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.js @@ -92,7 +92,7 @@ export class NodeAllocation extends Component { id={`${phase}-${PHASE_NODE_ATTRS}`} value={phaseData[PHASE_NODE_ATTRS] || ' '} options={nodeOptions} - onChange={e => { + onChange={(e) => { setPhaseData(PHASE_NODE_ATTRS, e.target.value); }} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js index 09db792b689da..bdcc1e23b4230 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js @@ -11,7 +11,7 @@ import { PHASE_INDEX_PRIORITY } from '../../../constants'; import { LearnMoreLink, OptionalLabel } from '../../components'; import { ErrableFormRow } from '../form_errors'; -export const SetPriorityInput = props => { +export const SetPriorityInput = (props) => { const { errors, phaseData, phase, setPhaseData, isShowingErrors } = props; return ( @@ -55,7 +55,7 @@ export const SetPriorityInput = props => { { + onChange={(e) => { setPhaseData(PHASE_INDEX_PRIORITY, e.target.value); }} min={0} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.container.js index e25360b94e029..d13ad31228860 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.container.js @@ -12,7 +12,7 @@ import { PHASE_WARM, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../const import { WarmPhase as PresentationComponent } from './warm_phase'; export const WarmPhase = connect( - state => ({ + (state) => ({ phaseData: getPhase(state, PHASE_WARM), hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], }), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js index 133e6b617c71e..55aec88c8bcab 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js @@ -108,7 +108,7 @@ export class WarmPhase extends PureComponent { } id={`${PHASE_WARM}-${PHASE_ENABLED}`} checked={phaseData[PHASE_ENABLED]} - onChange={e => { + onChange={(e) => { setPhaseData(PHASE_ENABLED, e.target.checked); }} aria-controls="warmPhaseContent" @@ -127,7 +127,7 @@ export class WarmPhase extends PureComponent { label={moveToWarmPhaseOnRolloverLabel} id={`${PHASE_WARM}-${WARM_PHASE_ON_ROLLOVER}`} checked={phaseData[WARM_PHASE_ON_ROLLOVER]} - onChange={e => { + onChange={(e) => { setPhaseData(WARM_PHASE_ON_ROLLOVER, e.target.checked); }} /> @@ -184,7 +184,7 @@ export class WarmPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_REPLICA_COUNT, e.target.value); }} min={0} @@ -225,7 +225,7 @@ export class WarmPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_SHRINK_ENABLED, e.target.checked); }} label={shrinkLabel} @@ -254,7 +254,7 @@ export class WarmPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_PRIMARY_SHARD_COUNT, e.target.value); }} min={1} @@ -294,7 +294,7 @@ export class WarmPhase extends PureComponent { label={forcemergeLabel} aria-label={forcemergeLabel} checked={phaseData[PHASE_FORCE_MERGE_ENABLED]} - onChange={e => { + onChange={(e) => { setPhaseData(PHASE_FORCE_MERGE_ENABLED, e.target.checked); }} aria-controls="forcemergeContent" @@ -318,7 +318,7 @@ export class WarmPhase extends PureComponent { { + onChange={(e) => { setPhaseData(PHASE_FORCE_MERGE_SEGMENTS, e.target.value); }} min={1} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js index 3e4ec06fa43e7..1c6ced8953211 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js @@ -29,7 +29,7 @@ import { findFirstError } from '../../services/find_errors'; import { EditPolicy as PresentationComponent } from './edit_policy'; export const EditPolicy = connect( - state => { + (state) => { const errors = validateLifecycle(state); const firstError = findFirstError(errors); return { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js index 040ce189ba2e6..998143929afef 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js @@ -36,7 +36,6 @@ import { } from '../../constants'; import { toasts } from '../../services/notification'; -import { goToPolicyList } from '../../services/navigation'; import { findFirstError } from '../../services/find_errors'; import { LearnMoreLink } from '../components'; import { NodeAttrsDetails } from './components/node_attrs_details'; @@ -63,10 +62,10 @@ export class EditPolicy extends Component { }; } - selectPolicy = policyName => { + selectPolicy = (policyName) => { const { setSelectedPolicy, policies } = this.props; - const selectedPolicy = policies.find(policy => { + const selectedPolicy = policies.find((policy) => { return policy.name === policyName; }); @@ -100,7 +99,7 @@ export class EditPolicy extends Component { backToPolicyList = () => { this.props.setSelectedPolicy(null); - goToPolicyList(); + this.props.history.push('/policies'); }; submit = async () => { @@ -125,7 +124,7 @@ export class EditPolicy extends Component { } }; - showNodeDetailsFlyout = selectedNodeAttrsForDetails => { + showNodeDetailsFlyout = (selectedNodeAttrsForDetails) => { this.setState({ isShowingNodeDetailsFlyout: true, selectedNodeAttrsForDetails }); }; @@ -222,7 +221,7 @@ export class EditPolicy extends Component { data-test-subj="saveAsNewSwitch" style={{ maxWidth: '100%' }} checked={saveAsNewPolicy} - onChange={async e => { + onChange={async (e) => { await setSaveAsNewPolicy(e.target.checked); }} label={ @@ -271,7 +270,7 @@ export class EditPolicy extends Component { { + onChange={async (e) => { await setSelectedPolicyName(e.target.value); }} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.js index 4b50e19f9baaa..28ebad209ad96 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.js @@ -15,7 +15,7 @@ export const ErrableFormRow = ({ errorKey, isShowingErrors, errors, children, .. {...rest} > - {Children.map(children, child => + {Children.map(children, (child) => cloneElement(child, { isInvalid: isShowingErrors && errors[errorKey].length > 0, }) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js index 6129678edf3b7..8e53569047d8f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js @@ -108,7 +108,7 @@ export class AddPolicyToTemplateConfirmModal extends Component { } getSelectedTemplate() { const { templates, templateName } = this.state; - return find(templates, template => template.name === templateName); + return find(templates, (template) => template.name === templateName); } renderForm() { const { templates, templateName, templateError } = this.state; @@ -143,7 +143,7 @@ export class AddPolicyToTemplateConfirmModal extends Component { { + onChange={(e) => { this.setState({ templateError: null, templateName: e.target.value }); }} /> @@ -170,7 +170,7 @@ export class AddPolicyToTemplateConfirmModal extends Component { > { + onChange={(e) => { this.setState({ aliasName: e.target.value }); }} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.container.js index 7db980abeba8a..8bd78774d2d55 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.container.js @@ -25,28 +25,28 @@ import { import { PolicyTable as PresentationComponent } from './policy_table'; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { - policyFilterChanged: filter => { + policyFilterChanged: (filter) => { dispatch(policyFilterChanged({ filter })); }, - policyPageChanged: pageNumber => { + policyPageChanged: (pageNumber) => { dispatch(policyPageChanged({ pageNumber })); }, - policyPageSizeChanged: pageSize => { + policyPageSizeChanged: (pageSize) => { dispatch(policyPageSizeChanged({ pageSize })); }, policySortChanged: (sortField, isSortAscending) => { dispatch(policySortChanged({ sortField, isSortAscending })); }, - fetchPolicies: withIndices => { + fetchPolicies: (withIndices) => { dispatch(fetchPolicies(withIndices)); }, }; }; export const PolicyTable = connect( - state => ({ + (state) => ({ totalNumberOfPolicies: getPolicies(state).length, policies: getPageOfPolicies(state), pager: getPolicyPager(state), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js index d406d86bc6ce7..dad259681eb7a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js @@ -36,9 +36,8 @@ import { } from '@elastic/eui'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; - +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; import { getIndexListUri } from '../../../../../../../index_management/public'; -import { BASE_PATH } from '../../../../../../common/constants'; import { UIM_EDIT_CLICK } from '../../../../constants'; import { getPolicyPath } from '../../../../services/navigation'; import { flattenPanelTree } from '../../../../services/flatten_panel_tree'; @@ -141,7 +140,7 @@ export class PolicyTable extends Component { this.props.fetchPolicies(true); this.setState({ renderDeleteConfirmModal: null, policyToDelete: null }); }; - onSort = column => { + onSort = (column) => { const { sortField, isSortAscending, policySortChanged } = this.props; const newIsSortAscending = sortField === column ? !isSortAscending : true; policySortChanged(column, newIsSortAscending); @@ -181,8 +180,9 @@ export class PolicyTable extends Component { /* eslint-disable-next-line @elastic/eui/href-or-on-click */ trackUiMetric('click', UIM_EDIT_CLICK)} + {...reactRouterNavigate(this.props.history, getPolicyPath(value), () => + trackUiMetric('click', UIM_EDIT_CLICK) + )} > {value} @@ -201,7 +201,7 @@ export class PolicyTable extends Component { renderCreatePolicyButton() { return ( { - window.location.hash = getIndexListUri(`ilm.policy:${policy.name}`); + this.props.navigateToApp('management', { + path: `/data/index_management${getIndexListUri(`ilm.policy:${policy.name}`)}`, + }); }, }); } @@ -286,22 +288,22 @@ export class PolicyTable extends Component { }; return flattenPanelTree(panelTree); } - togglePolicyPopover = policy => { + togglePolicyPopover = (policy) => { if (this.isPolicyPopoverOpen(policy)) { this.closePolicyPopover(policy); } else { this.openPolicyPopover(policy); } }; - isPolicyPopoverOpen = policy => { + isPolicyPopoverOpen = (policy) => { return this.state.policyPopover === policy.name; }; - closePolicyPopover = policy => { + closePolicyPopover = (policy) => { if (this.isPolicyPopoverOpen(policy)) { this.setState({ policyPopover: null }); } }; - openPolicyPopover = policy => { + openPolicyPopover = (policy) => { this.setState({ policyPopover: policy.name }); }; buildRowCells(policy) { @@ -374,7 +376,7 @@ export class PolicyTable extends Component { buildRows() { const { policies = [] } = this.props; - return policies.map(policy => { + return policies.map((policy) => { const { name } = policy; return {this.buildRowCells(policy)}; }); @@ -394,7 +396,7 @@ export class PolicyTable extends Component { ); } - onItemSelectionChanged = selectedPolicies => { + onItemSelectionChanged = (selectedPolicies) => { this.setState({ selectedPolicies }); }; @@ -455,7 +457,7 @@ export class PolicyTable extends Component { { + onChange={(event) => { policyFilterChanged(event.target.value); }} data-test-subj="policyTableFilterInput" diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/api.js b/x-pack/plugins/index_lifecycle_management/public/application/services/api.js index 1cb2089ab66db..6b46d6e6ea735 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/api.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/api.js @@ -28,8 +28,7 @@ export async function loadIndexTemplates() { } export async function loadPolicies(withIndices) { - const query = withIndices ? '?withIndices=true' : ''; - return await sendGet('policies', query); + return await sendGet('policies', { withIndices }); } export async function savePolicy(policy) { @@ -43,28 +42,28 @@ export async function deletePolicy(policyName) { return response; } -export const retryLifecycleForIndex = async indexNames => { +export const retryLifecycleForIndex = async (indexNames) => { const response = await sendPost(`index/retry`, { indexNames }); // Only track successful actions. trackUiMetric('count', UIM_INDEX_RETRY_STEP); return response; }; -export const removeLifecycleForIndex = async indexNames => { +export const removeLifecycleForIndex = async (indexNames) => { const response = await sendPost(`index/remove`, { indexNames }); // Only track successful actions. trackUiMetric('count', UIM_POLICY_DETACH_INDEX); return response; }; -export const addLifecyclePolicyToIndex = async body => { +export const addLifecyclePolicyToIndex = async (body) => { const response = await sendPost(`index/add`, body); // Only track successful actions. trackUiMetric('count', UIM_POLICY_ATTACH_INDEX); return response; }; -export const addLifecyclePolicyToTemplate = async body => { +export const addLifecyclePolicyToTemplate = async (body) => { const response = await sendPost(`template`, body); // Only track successful actions. trackUiMetric('count', UIM_POLICY_ATTACH_INDEX_TEMPLATE); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.js b/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.js index 6d2e3dae57f46..dcc9036463b82 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.js @@ -6,9 +6,9 @@ export const filterItems = (fields, filter = '', items = []) => { const lowerFilter = filter.toLowerCase(); - return items.filter(item => { + return items.filter((item) => { const actualFields = fields || Object.keys(item); - const indexOfMatch = actualFields.findIndex(field => { + const indexOfMatch = actualFields.findIndex((field) => { const normalizedField = String(item[field]).toLowerCase(); return normalizedField.includes(lowerFilter); }); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/flatten_panel_tree.js b/x-pack/plugins/index_lifecycle_management/public/application/services/flatten_panel_tree.js index e060e22965cb3..2bb3903a6ef45 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/flatten_panel_tree.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/flatten_panel_tree.js @@ -8,7 +8,7 @@ export const flattenPanelTree = (tree, array = []) => { array.push(tree); if (tree.items) { - tree.items.forEach(item => { + tree.items.forEach((item) => { if (item.panel) { flattenPanelTree(item.panel, array); item.panel = item.panel.id; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts index 2d518ebb3015e..72e9d51d8fdeb 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts @@ -4,12 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../../common/constants'; - -export const goToPolicyList = () => { - window.location.hash = `${BASE_PATH}policies`; -}; - export const getPolicyPath = (policyName: string): string => { - return encodeURI(`#${BASE_PATH}policies/edit/${encodeURIComponent(policyName)}`); + return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.js b/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.js index 8e9896dac0b07..1b1446bb735c1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.js @@ -6,8 +6,8 @@ import { sortBy } from 'lodash'; -const stringSort = fieldName => item => item[fieldName]; -const arraySort = fieldName => item => (item[fieldName] || []).length; +const stringSort = (fieldName) => (item) => item[fieldName]; +const arraySort = (fieldName) => (item) => (item[fieldName] || []).length; const sorters = { version: stringSort('version'), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts index ca987441c7ce9..d71e38d0b31de 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts @@ -51,7 +51,7 @@ export function getUiMetricsForPhases(phases: any): any { }; // We only care about whether the user has interacted with the priority of *any* phase at all. - return [PHASE_HOT, PHASE_WARM, PHASE_COLD].some(phase => { + return [PHASE_HOT, PHASE_WARM, PHASE_COLD].some((phase) => { // If the priority is different than the default, we'll consider it a user interaction, // even if the user has set it to undefined. return ( diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js index b665a847851b3..f2520abc7a441 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js @@ -14,7 +14,7 @@ export const setSelectedPrimaryShardCount = createAction('SET_SELECTED_PRIMARY_S export const setSelectedReplicaCount = createAction('SET_SELECTED_REPLICA_COUNT'); export const fetchedNodes = createAction('FETCHED_NODES'); let fetchingNodes = false; -export const fetchNodes = () => async dispatch => { +export const fetchNodes = () => async (dispatch) => { try { if (!fetchingNodes) { fetchingNodes = true; @@ -38,7 +38,7 @@ export const fetchedNodeDetails = createAction( details, }) ); -export const fetchNodeDetails = selectedNodeAttrs => async dispatch => { +export const fetchNodeDetails = (selectedNodeAttrs) => async (dispatch) => { let details; try { details = await loadNodeDetails(selectedNodeAttrs); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/policies.js b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/policies.js index b6064af6e38b2..aa20c0eb1d326 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/policies.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/policies.js @@ -22,7 +22,7 @@ export const policyPageChanged = createAction('POLICY_PAGE_CHANGED'); export const policySortDirectionChanged = createAction('POLICY_SORT_DIRECTION_CHANGED'); export const policyFilterChanged = createAction('POLICY_FILTER_CHANGED'); -export const fetchPolicies = (withIndices, callback) => async dispatch => { +export const fetchPolicies = (withIndices, callback) => async (dispatch) => { let policies; try { policies = await loadPolicies(withIndices); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/general.js b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/general.js index d0f0973ad81f2..2d01749be3087 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/general.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/general.js @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const getBootstrapEnabled = state => state.general.bootstrapEnabled; -export const getIndexName = state => state.general.indexName; -export const getAliasName = state => state.general.aliasName; +export const getBootstrapEnabled = (state) => state.general.bootstrapEnabled; +export const getIndexName = (state) => state.general.indexName; +export const getAliasName = (state) => state.general.aliasName; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js index 750a7feb19c3d..03538fad9aa83 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js @@ -209,7 +209,7 @@ export const policyNameAlreadyUsedErrorMessage = i18n.translate( defaultMessage: 'That policy name is already used.', } ); -export const validateLifecycle = state => { +export const validateLifecycle = (state) => { // This method of deep copy does not always work but it should be fine here const errors = JSON.parse(JSON.stringify(ERROR_STRUCTURE)); const policyName = getSelectedPolicyName(state); @@ -236,7 +236,7 @@ export const validateLifecycle = state => { ) { errors[STRUCTURE_POLICY_NAME].push(policyNameMustBeDifferentErrorMessage); } else if (getSelectedOriginalPolicyName(state) !== getSelectedPolicyName(state)) { - const policyNames = getPolicies(state).map(policy => policy.name); + const policyNames = getPolicies(state).map((policy) => policy.name); if (policyNames.includes(getSelectedPolicyName(state))) { errors[STRUCTURE_POLICY_NAME].push(policyNameAlreadyUsedErrorMessage); } @@ -254,7 +254,7 @@ export const validateLifecycle = state => { return errors; }; -export const getLifecycle = state => { +export const getLifecycle = (state) => { const policyName = getSelectedPolicyName(state); const phases = Object.entries(getPhases(state)).reduce((accum, [phaseName, phase]) => { // Hot is ALWAYS enabled @@ -270,7 +270,9 @@ export const getLifecycle = state => { if (phaseName === PHASE_DELETE) { accum[phaseName].actions = { ...accum[phaseName].actions, - delete: {}, + delete: { + ...accum[phaseName].actions.delete, + }, }; } } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/nodes.js b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/nodes.js index a92b7154a51fd..63d849217f59e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/nodes.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/nodes.js @@ -6,14 +6,14 @@ import { createSelector } from 'reselect'; -export const getNodes = state => state.nodes.nodes; +export const getNodes = (state) => state.nodes.nodes; -export const getNodeOptions = createSelector([state => getNodes(state)], nodes => { +export const getNodeOptions = createSelector([(state) => getNodes(state)], (nodes) => { if (!nodes) { return null; } - const options = Object.keys(nodes).map(attrs => ({ + const options = Object.keys(nodes).map((attrs) => ({ text: `${attrs} (${nodes[attrs].length})`, value: attrs, })); @@ -26,14 +26,14 @@ export const getNodeOptions = createSelector([state => getNodes(state)], nodes = } }); -export const getSelectedPrimaryShardCount = state => state.nodes.selectedPrimaryShardCount; +export const getSelectedPrimaryShardCount = (state) => state.nodes.selectedPrimaryShardCount; -export const getSelectedReplicaCount = state => +export const getSelectedReplicaCount = (state) => state.nodes.selectedReplicaCount !== undefined ? state.nodes.selectedReplicaCount : 1; -export const getSelectedNodeAttrs = state => state.nodes.selectedNodeAttrs; +export const getSelectedNodeAttrs = (state) => state.nodes.selectedNodeAttrs; -export const getNodesFromSelectedNodeAttrs = state => { +export const getNodesFromSelectedNodeAttrs = (state) => { const nodes = getNodes(state)[getSelectedNodeAttrs(state)]; if (nodes) { return nodes.length; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/policies.js b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/policies.js index 591a1cb3d3699..a3aef8679817d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/policies.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/policies.js @@ -42,23 +42,23 @@ import { defaultEmptyHotPhase, } from '../defaults'; -export const getPolicies = state => state.policies.policies; +export const getPolicies = (state) => state.policies.policies; export const getPolicyByName = (state, name) => - getPolicies(state).find(policy => policy.name === name) || {}; -export const getIsNewPolicy = state => state.policies.selectedPolicy.isNew; -export const getSelectedPolicy = state => state.policies.selectedPolicy; -export const getIsSelectedPolicySet = state => state.policies.selectedPolicySet; -export const getSelectedOriginalPolicyName = state => state.policies.originalPolicyName; -export const getPolicyFilter = state => state.policies.filter; -export const getPolicySort = state => state.policies.sort; -export const getPolicyCurrentPage = state => state.policies.currentPage; -export const getPolicyPageSize = state => state.policies.pageSize; -export const isPolicyListLoaded = state => state.policies.isLoaded; + getPolicies(state).find((policy) => policy.name === name) || {}; +export const getIsNewPolicy = (state) => state.policies.selectedPolicy.isNew; +export const getSelectedPolicy = (state) => state.policies.selectedPolicy; +export const getIsSelectedPolicySet = (state) => state.policies.selectedPolicySet; +export const getSelectedOriginalPolicyName = (state) => state.policies.originalPolicyName; +export const getPolicyFilter = (state) => state.policies.filter; +export const getPolicySort = (state) => state.policies.sort; +export const getPolicyCurrentPage = (state) => state.policies.currentPage; +export const getPolicyPageSize = (state) => state.policies.pageSize; +export const isPolicyListLoaded = (state) => state.policies.isLoaded; const getFilteredPolicies = createSelector(getPolicies, getPolicyFilter, (policies, filter) => { return filterItems(['name'], filter, policies); }); -export const getTotalPolicies = createSelector(getFilteredPolicies, filteredPolicies => { +export const getTotalPolicies = createSelector(getFilteredPolicies, (filteredPolicies) => { return filteredPolicies.length; }); export const getPolicyPager = createSelector( @@ -80,16 +80,16 @@ export const getPageOfPolicies = createSelector( return pagedPolicies; } ); -export const getSaveAsNewPolicy = state => state.policies.selectedPolicy.saveAsNew; +export const getSaveAsNewPolicy = (state) => state.policies.selectedPolicy.saveAsNew; -export const getSelectedPolicyName = state => { +export const getSelectedPolicyName = (state) => { if (!getSaveAsNewPolicy(state)) { return getSelectedOriginalPolicyName(state); } return state.policies.selectedPolicy.name; }; -export const getPhases = state => state.policies.selectedPolicy.phases; +export const getPhases = (state) => state.policies.selectedPolicy.phases; export const getPhase = (state, phase) => getPhases(state)[phase]; @@ -100,7 +100,7 @@ export const getPhaseData = (state, phase, key) => { return getPhase(state, phase)[key]; }; -export const splitSizeAndUnits = field => { +export const splitSizeAndUnits = (field) => { let size; let units; @@ -116,8 +116,8 @@ export const splitSizeAndUnits = field => { }; }; -export const isNumber = value => typeof value === 'number'; -export const isEmptyObject = obj => { +export const isNumber = (value) => typeof value === 'number'; +export const isEmptyObject = (obj) => { return !obj || (Object.entries(obj).length === 0 && obj.constructor === Object); }; @@ -166,7 +166,7 @@ const phaseFromES = (phase, phaseName, defaultEmptyPolicy) => { if (actions.allocate) { const allocate = actions.allocate; if (allocate.require) { - Object.entries(allocate.require).forEach(entry => { + Object.entries(allocate.require).forEach((entry) => { policy[PHASE_NODE_ATTRS] = entry.join(':'); }); // checking for null or undefined here @@ -198,7 +198,7 @@ const phaseFromES = (phase, phaseName, defaultEmptyPolicy) => { return policy; }; -export const policyFromES = policy => { +export const policyFromES = (policy) => { const { name, policy: { phases }, diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js index 143895150172d..0bd313c9a9f8d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js @@ -23,7 +23,6 @@ import { EuiModalHeaderTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../../common/constants'; import { loadPolicies, addLifecyclePolicyToIndex } from '../../application/services/api'; import { showApiError } from '../../application/services/api_errors'; import { toasts } from '../../application/services/notification'; @@ -82,7 +81,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { ); } }; - renderAliasFormElement = selectedPolicy => { + renderAliasFormElement = (selectedPolicy) => { const { selectedAlias } = this.state; const { index } = this.props; const showAliasSelect = @@ -118,7 +117,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { ); } - const aliasOptions = aliases.map(alias => { + const aliasOptions = aliases.map((alias) => { return { text: alias, value: alias, @@ -145,7 +144,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { { + onChange={(e) => { this.setState({ selectedAlias: e.target.value }); }} /> @@ -155,7 +154,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { renderForm() { const { policies, selectedPolicyName, policyError } = this.state; const selectedPolicy = selectedPolicyName - ? policies.find(policy => policy.name === selectedPolicyName) + ? policies.find((policy) => policy.name === selectedPolicyName) : null; const options = policies.map(({ name }) => { @@ -188,7 +187,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { { + onChange={(e) => { this.setState({ policyError: null, selectedPolicyName: e.target.value }); }} /> @@ -216,7 +215,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { } render() { const { policies } = this.state; - const { indexName, closeModal } = this.props; + const { indexName, closeModal, getUrlForApp } = this.props; const title = (

- + {value}; + content = ( + + {value} + + ); } else { content = value; } diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.js index 4e0d2383c7d79..048ed44bd58b2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.js @@ -100,7 +100,7 @@ export class RemoveLifecyclePolicyConfirmModal extends Component {

    - {indexNames.map(indexName => ( + {indexNames.map((indexName) => (
  • {indexName}
  • ))}
diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js index 40ff04408002f..e7afc8f12859c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js @@ -17,7 +17,7 @@ import { RemoveLifecyclePolicyConfirmModal } from './components/remove_lifecycle const stepPath = 'ilm.step'; export const retryLifecycleActionExtension = ({ indices }) => { - const allHaveErrors = every(indices, index => { + const allHaveErrors = every(indices, (index) => { return index.ilm && index.ilm.failed_step; }); if (!allHaveErrors) { @@ -35,14 +35,14 @@ export const retryLifecycleActionExtension = ({ indices }) => { 'xpack.indexLifecycleMgmt.retryIndexLifecycleAction.retriedLifecycleMessage', { defaultMessage: 'Called retry lifecycle step for: {indexNames}', - values: { indexNames: indexNames.map(indexName => `"${indexName}"`).join(', ') }, + values: { indexNames: indexNames.map((indexName) => `"${indexName}"`).join(', ') }, } ), }; }; export const removeLifecyclePolicyActionExtension = ({ indices, reloadIndices }) => { - const allHaveIlm = every(indices, index => { + const allHaveIlm = every(indices, (index) => { return index.ilm && index.ilm.managed; }); if (!allHaveIlm) { @@ -50,7 +50,7 @@ export const removeLifecyclePolicyActionExtension = ({ indices, reloadIndices }) } const indexNames = indices.map(({ name }) => name); return { - renderConfirmModal: closeModal => { + renderConfirmModal: (closeModal) => { return ( { +export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices, getUrlForApp }) => { if (indices.length !== 1) { return null; } @@ -79,13 +79,14 @@ export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices }) => } const indexName = index.name; return { - renderConfirmModal: closeModal => { + renderConfirmModal: (closeModal) => { return ( ); }, @@ -96,12 +97,12 @@ export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices }) => }; }; -export const ilmBannerExtension = indices => { +export const ilmBannerExtension = (indices) => { const { Query } = EuiSearchBar; if (!indices.length) { return null; } - const indicesWithLifecycleErrors = indices.filter(index => { + const indicesWithLifecycleErrors = indices.filter((index) => { return get(index, stepPath) === 'ERROR'; }); const numIndicesWithLifecycleErrors = indicesWithLifecycleErrors.length; @@ -123,12 +124,12 @@ export const ilmBannerExtension = indices => { }; }; -export const ilmSummaryExtension = index => { - return ; +export const ilmSummaryExtension = (index, getUrlForApp) => { + return ; }; -export const ilmFilterExtension = indices => { - const hasIlm = any(indices, index => index.ilm && index.ilm.managed); +export const ilmFilterExtension = (indices) => { + const hasIlm = any(indices, (index) => index.ilm && index.ilm.managed); if (!hasIlm) { return []; } else { @@ -193,7 +194,7 @@ export const ilmFilterExtension = indices => { } }; -export const addAllExtensions = extensionsService => { +export const addAllExtensions = (extensionsService) => { extensionsService.addAction(retryLifecycleActionExtension); extensionsService.addAction(removeLifecyclePolicyActionExtension); extensionsService.addAction(addLifecyclePolicyActionExtension); diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index 8fce57b0e79b0..49856dee47fba 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -42,11 +42,12 @@ export class IndexLifecycleManagementPlugin { id: PLUGIN.ID, title: PLUGIN.TITLE, order: 2, - mount: async ({ element }) => { + mount: async ({ element, history }) => { const [coreStart] = await getStartServices(); const { i18n: { Context: I18nContext }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + application: { navigateToApp }, } = coreStart; // Initialize additional services. @@ -55,7 +56,7 @@ export class IndexLifecycleManagementPlugin { ); const { renderApp } = await import('./application'); - return renderApp(element, I18nContext); + return renderApp(element, I18nContext, history, navigateToApp); }, }); diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts index 9627f6399eaaf..b8870a83ac5ac 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts @@ -45,7 +45,7 @@ export function registerAddPolicyRoute({ router, license, lib }: RouteDependenci try { await addLifecyclePolicy( - context.core.elasticsearch.dataClient.callAsCurrentUser, + context.core.elasticsearch.legacy.client.callAsCurrentUser, indexName, policyName, alias diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts index 8ec94a8591785..b1bc2264fc529 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts @@ -37,7 +37,10 @@ export function registerRemoveRoute({ router, license, lib }: RouteDependencies) const { indexNames } = body; try { - await removeLifecycle(context.core.elasticsearch.dataClient.callAsCurrentUser, indexNames); + await removeLifecycle( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + indexNames + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts index 1e2d621cab173..0a5ca1730d170 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts @@ -37,7 +37,10 @@ export function registerRetryRoute({ router, license, lib }: RouteDependencies) const { indexNames } = body; try { - await retryLifecycle(context.core.elasticsearch.dataClient.callAsCurrentUser, indexNames); + await retryLifecycle( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + indexNames + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts index 6ff1f147e7ea7..db0352ec83ee0 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts @@ -46,7 +46,9 @@ export function registerDetailsRoute({ router, license, lib }: RouteDependencies const { nodeAttrs } = params; try { - const stats = await fetchNodeStats(context.core.elasticsearch.dataClient.callAsCurrentUser); + const stats = await fetchNodeStats( + context.core.elasticsearch.legacy.client.callAsCurrentUser + ); const okResponse = { body: findMatchingNodes(stats, nodeAttrs) }; return response.ok(okResponse); } catch (e) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts index 73d85c78d3b11..91b542284a041 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts @@ -51,7 +51,9 @@ export function registerListRoute({ router, config, license, lib }: RouteDepende { path: addBasePath('/nodes/list'), validate: false }, license.guardApiRoute(async (context, request, response) => { try { - const stats = await fetchNodeStats(context.core.elasticsearch.dataClient.callAsCurrentUser); + const stats = await fetchNodeStats( + context.core.elasticsearch.legacy.client.callAsCurrentUser + ); const okResponse = { body: convertStatsIntoList(stats, disallowedNodeAttributes) }; return response.ok(okResponse); } catch (e) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts index a9c6bab58fdd9..7bf3f96e2b2ef 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts @@ -67,7 +67,7 @@ const warmPhaseSchema = schema.maybe( actions: schema.object({ set_priority: setPrioritySchema, unfollow: unfollowSchema, - read_only: schema.maybe(schema.object({})), // Readonly has no options + readonly: schema.maybe(schema.object({})), // Readonly has no options allocate: allocateSchema, shrink: schema.maybe( schema.object({ @@ -91,6 +91,11 @@ const coldPhaseSchema = schema.maybe( unfollow: unfollowSchema, allocate: allocateSchema, freeze: schema.maybe(schema.object({})), // Freeze has no options + searchable_snapshot: schema.maybe( + schema.object({ + snapshot_repository: schema.string(), + }) + ), }), }) ); @@ -104,7 +109,11 @@ const deletePhaseSchema = schema.maybe( policy: schema.string(), }) ), - delete: schema.maybe(schema.object({})), // Delete has no options + delete: schema.maybe( + schema.object({ + delete_searchable_snapshot: schema.maybe(schema.boolean()), + }) + ), }), }) ); @@ -128,7 +137,11 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies) const { name, phases } = body; try { - await createPolicy(context.core.elasticsearch.dataClient.callAsCurrentUser, name, phases); + await createPolicy( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + name, + phases + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts index e08297f4d7bc4..241668780ea25 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts @@ -33,7 +33,10 @@ export function registerDeleteRoute({ router, license, lib }: RouteDependencies) const { policyNames } = params; try { - await deletePolicies(context.core.elasticsearch.dataClient.callAsCurrentUser, policyNames); + await deletePolicies( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + policyNames + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts index 294b7c4c65cba..d581cc15f3053 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts @@ -66,7 +66,7 @@ export function registerFetchRoute({ router, license, lib }: RouteDependencies) license.guardApiRoute(async (context, request, response) => { const query = request.query as typeof querySchema.type; const { withIndices } = query; - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; try { const policiesMap = await fetchPolicies(callAsCurrentUser); diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts index 0da8535f8d4ec..ed05cc270e4c1 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts @@ -60,7 +60,7 @@ export function registerAddPolicyRoute({ router, license, lib }: RouteDependenci try { await updateIndexTemplate( - context.core.elasticsearch.dataClient.callAsCurrentUser, + context.core.elasticsearch.legacy.client.callAsCurrentUser, templateName, policyName, aliasName diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts index a2dc67cb77afe..546d9674b0956 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts @@ -21,7 +21,7 @@ function isReservedSystemTemplate(templateName: string, indexPatterns: string[]) return ( templateName.startsWith('kibana_index_template') || (templateName.startsWith('.') && - indexPatterns.every(pattern => { + indexPatterns.every((pattern) => { return !pattern.includes('*'); })) ); @@ -66,7 +66,7 @@ export function registerFetchRoute({ router, license, lib }: RouteDependencies) license.guardApiRoute(async (context, request, response) => { try { const templates = await fetchTemplates( - context.core.elasticsearch.dataClient.callAsCurrentUser + context.core.elasticsearch.legacy.client.callAsCurrentUser ); const okResponse = { body: filterAndFormatTemplates(templates) }; return response.ok(okResponse); diff --git a/x-pack/plugins/index_lifecycle_management/server/services/license.ts b/x-pack/plugins/index_lifecycle_management/server/services/license.ts index 31d3654c51e3e..2d863e283d440 100644 --- a/x-pack/plugins/index_lifecycle_management/server/services/license.ts +++ b/x-pack/plugins/index_lifecycle_management/server/services/license.ts @@ -35,7 +35,7 @@ export class License { { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } ) { - licensing.license$.subscribe(license => { + licensing.license$.subscribe((license) => { const { state, message } = license.check(pluginId, minimumLicenseType); const hasRequiredLicense = state === 'valid'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts deleted file mode 100644 index a323fdf714d8d..0000000000000 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts +++ /dev/null @@ -1,209 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ReactWrapper } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import { - registerTestBed, - TestBed, - TestBedConfig, - findTestSubject, - nextTick, -} from '../../../../../test_utils'; -import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths -import { BASE_PATH } from '../../../common/constants'; -import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths -import { TemplateDeserialized } from '../../../common'; -import { WithAppDependencies, services } from './setup_environment'; - -const testBedConfig: TestBedConfig = { - store: () => indexManagementStore(services as any), - memoryRouter: { - initialEntries: [`${BASE_PATH}indices?includeHidden=true`], - componentRoutePath: `${BASE_PATH}:section(indices|templates)`, - }, - doMountAsync: true, -}; - -const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); - -export interface IdxMgmtHomeTestBed extends TestBed { - findAction: (action: 'edit' | 'clone' | 'delete') => ReactWrapper; - actions: { - selectHomeTab: (tab: 'indicesTab' | 'templatesTab') => void; - selectDetailsTab: (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => void; - selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void; - clickReloadButton: () => void; - clickTemplateAction: ( - name: TemplateDeserialized['name'], - action: 'edit' | 'clone' | 'delete' - ) => void; - clickTemplateAt: (index: number) => void; - clickCloseDetailsButton: () => void; - clickActionMenu: (name: TemplateDeserialized['name']) => void; - getIncludeHiddenIndicesToggleStatus: () => boolean; - clickIncludeHiddenIndicesToggle: () => void; - }; -} - -export const setup = async (): Promise => { - const testBed = await initTestBed(); - - /** - * Additional helpers - */ - const findAction = (action: 'edit' | 'clone' | 'delete') => { - const actions = ['edit', 'clone', 'delete']; - const { component } = testBed; - - return component.find('.euiContextMenuItem').at(actions.indexOf(action)); - }; - - /** - * User Actions - */ - - const selectHomeTab = (tab: 'indicesTab' | 'templatesTab') => { - testBed.find(tab).simulate('click'); - }; - - const selectDetailsTab = (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => { - const tabs = ['summary', 'settings', 'mappings', 'aliases']; - - testBed - .find('templateDetails.tab') - .at(tabs.indexOf(tab)) - .simulate('click'); - }; - - const clickReloadButton = () => { - const { find } = testBed; - find('reloadButton').simulate('click'); - }; - - const clickActionMenu = async (templateName: TemplateDeserialized['name']) => { - const { component } = testBed; - - // When a table has > 2 actions, EUI displays an overflow menu with an id "-actions" - // The template name may contain a period (.) so we use bracket syntax for selector - component.find(`div[id="${templateName}-actions"] button`).simulate('click'); - }; - - const clickTemplateAction = ( - templateName: TemplateDeserialized['name'], - action: 'edit' | 'clone' | 'delete' - ) => { - const actions = ['edit', 'clone', 'delete']; - const { component } = testBed; - - clickActionMenu(templateName); - - component - .find('.euiContextMenuItem') - .at(actions.indexOf(action)) - .simulate('click'); - }; - - const clickTemplateAt = async (index: number) => { - const { component, table, router } = testBed; - const { rows } = table.getMetaData('templateTable'); - const templateLink = findTestSubject(rows[index].reactWrapper, 'templateDetailsLink'); - - await act(async () => { - const { href } = templateLink.props(); - router.navigateTo(href!); - await nextTick(); - component.update(); - }); - }; - - const clickCloseDetailsButton = () => { - const { find } = testBed; - - find('closeDetailsButton').simulate('click'); - }; - - const clickIncludeHiddenIndicesToggle = () => { - const { find } = testBed; - find('indexTableIncludeHiddenIndicesToggle').simulate('click'); - }; - - const getIncludeHiddenIndicesToggleStatus = () => { - const { find } = testBed; - const props = find('indexTableIncludeHiddenIndicesToggle').props(); - return Boolean(props['aria-checked']); - }; - - const selectIndexDetailsTab = async ( - tab: 'settings' | 'mappings' | 'stats' | 'edit_settings' - ) => { - const indexDetailsTabs = ['settings', 'mappings', 'stats', 'edit_settings']; - const { find, component } = testBed; - await act(async () => { - find('detailPanelTab') - .at(indexDetailsTabs.indexOf(tab)) - .simulate('click'); - }); - component.update(); - }; - - return { - ...testBed, - findAction, - actions: { - selectHomeTab, - selectDetailsTab, - selectIndexDetailsTab, - clickReloadButton, - clickTemplateAction, - clickTemplateAt, - clickCloseDetailsButton, - clickActionMenu, - getIncludeHiddenIndicesToggleStatus, - clickIncludeHiddenIndicesToggle, - }, - }; -}; - -type IdxMgmtTestSubjects = TestSubjects; - -export type TestSubjects = - | 'aliasesTab' - | 'appTitle' - | 'cell' - | 'closeDetailsButton' - | 'createTemplateButton' - | 'deleteSystemTemplateCallOut' - | 'deleteTemplateButton' - | 'deleteTemplatesConfirmation' - | 'documentationLink' - | 'emptyPrompt' - | 'manageTemplateButton' - | 'mappingsTab' - | 'noAliasesCallout' - | 'noMappingsCallout' - | 'noSettingsCallout' - | 'indicesList' - | 'indicesTab' - | 'indexTableIncludeHiddenIndicesToggle' - | 'indexTableIndexNameLink' - | 'reloadButton' - | 'reloadIndicesButton' - | 'row' - | 'sectionError' - | 'sectionLoading' - | 'settingsTab' - | 'summaryTab' - | 'summaryTitle' - | 'systemTemplatesSwitch' - | 'templateDetails' - | 'templateDetails.manageTemplateButton' - | 'templateDetails.sectionLoading' - | 'templateDetails.tab' - | 'templateDetails.title' - | 'templateList' - | 'templateTable' - | 'templatesTab'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts index e5bce31ee6de1..da461609f0b83 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts @@ -12,7 +12,7 @@ type HttpResponse = Record | any[]; // Register helpers to mock HTTP Requests const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { const setLoadTemplatesResponse = (response: HttpResponse = []) => { - server.respondWith('GET', `${API_BASE_PATH}/templates`, [ + server.respondWith('GET', `${API_BASE_PATH}/index-templates`, [ 200, { 'Content-Type': 'application/json' }, JSON.stringify(response), @@ -28,7 +28,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { }; const setDeleteTemplateResponse = (response: HttpResponse = []) => { - server.respondWith('DELETE', `${API_BASE_PATH}/templates`, [ + server.respondWith('POST', `${API_BASE_PATH}/delete-index-templates`, [ 200, { 'Content-Type': 'application/json' }, JSON.stringify(response), @@ -39,7 +39,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { const status = error ? error.status || 400 : 200; const body = error ? error.body : response; - server.respondWith('GET', `${API_BASE_PATH}/templates/:id`, [ + server.respondWith('GET', `${API_BASE_PATH}/index-templates/:id`, [ status, { 'Content-Type': 'application/json' }, JSON.stringify(body), @@ -50,7 +50,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { const status = error ? error.body.status || 400 : 200; const body = error ? JSON.stringify(error.body) : JSON.stringify(response); - server.respondWith('PUT', `${API_BASE_PATH}/templates`, [ + server.respondWith('POST', `${API_BASE_PATH}/index-templates`, [ status, { 'Content-Type': 'application/json' }, body, @@ -61,7 +61,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { const status = error ? error.status || 400 : 200; const body = error ? JSON.stringify(error.body) : JSON.stringify(response); - server.respondWith('PUT', `${API_BASE_PATH}/templates/:name`, [ + server.respondWith('PUT', `${API_BASE_PATH}/index-templates/:name`, [ status, { 'Content-Type': 'application/json' }, body, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts index 66021b531919a..8e7755a65af3c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts @@ -4,18 +4,50 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setup as homeSetup } from './home.helpers'; -import { setup as templateCreateSetup } from './template_create.helpers'; -import { setup as templateCloneSetup } from './template_clone.helpers'; -import { setup as templateEditSetup } from './template_edit.helpers'; +import './mocks'; export { nextTick, getRandomString, findTestSubject, TestBed } from '../../../../../test_utils'; -export { setupEnvironment } from './setup_environment'; +export { setupEnvironment, WithAppDependencies, services } from './setup_environment'; -export const pageHelpers = { - home: { setup: homeSetup }, - templateCreate: { setup: templateCreateSetup }, - templateClone: { setup: templateCloneSetup }, - templateEdit: { setup: templateEditSetup }, -}; +export type TestSubjects = + | 'aliasesTab' + | 'appTitle' + | 'cell' + | 'closeDetailsButton' + | 'createTemplateButton' + | 'createLegacyTemplateButton' + | 'deleteSystemTemplateCallOut' + | 'deleteTemplateButton' + | 'deleteTemplatesConfirmation' + | 'documentationLink' + | 'emptyPrompt' + | 'manageTemplateButton' + | 'mappingsTab' + | 'noAliasesCallout' + | 'noMappingsCallout' + | 'noSettingsCallout' + | 'indicesList' + | 'indicesTab' + | 'indexTableIncludeHiddenIndicesToggle' + | 'indexTableIndexNameLink' + | 'reloadButton' + | 'reloadIndicesButton' + | 'row' + | 'sectionError' + | 'sectionLoading' + | 'settingsTab' + | 'summaryTab' + | 'summaryTitle' + | 'systemTemplatesSwitch' + | 'templateDetails' + | 'templateDetails.manageTemplateButton' + | 'templateDetails.sectionLoading' + | 'templateDetails.tab' + | 'templateDetails.title' + | 'templateList' + | 'templateTable' + | 'templatesTab' + | 'legacyTemplateTable' + | 'viewButton' + | 'filterList.filterItem'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/mocks.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/mocks.ts new file mode 100644 index 0000000000000..6260a987e270a --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/mocks.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +(window as any).Worker = class Worker { + onmessage() {} + postMessage() {} +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index 1eaf7efd17395..0a49191fdb149 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + /* eslint-disable @kbn/eslint/no-restricted-paths */ import React from 'react'; import axios from 'axios'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts deleted file mode 100644 index 4c5cfcd826844..0000000000000 --- a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts +++ /dev/null @@ -1,589 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { act } from 'react-dom/test-utils'; -import * as fixtures from '../../test/fixtures'; -import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers'; -import { IdxMgmtHomeTestBed } from './helpers/home.helpers'; -import { API_BASE_PATH } from '../../common/constants'; - -const { setup } = pageHelpers.home; - -const removeWhiteSpaceOnArrayValues = (array: any[]) => - array.map(value => { - if (!value.trim) { - return value; - } - return value.trim(); - }); - -jest.mock('ui/new_platform'); - -describe('', () => { - const { server, httpRequestsMockHelpers } = setupEnvironment(); - let testBed: IdxMgmtHomeTestBed; - - afterAll(() => { - server.restore(); - }); - - describe('on component mount', () => { - beforeEach(async () => { - httpRequestsMockHelpers.setLoadIndicesResponse([]); - - testBed = await setup(); - - await act(async () => { - const { component } = testBed; - - await nextTick(); - component.update(); - }); - }); - - test('sets the hash query param base on include hidden indices toggle', () => { - const { actions } = testBed; - expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); - expect(window.location.hash.includes('includeHidden=true')).toBe(true); - actions.clickIncludeHiddenIndicesToggle(); - expect(window.location.hash.includes('includeHidden=true')).toBe(false); - // Note: this test modifies the shared location.hash state, we put it back the way it was - actions.clickIncludeHiddenIndicesToggle(); - expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); - expect(window.location.hash.includes('includeHidden=true')).toBe(true); - }); - - test('should set the correct app title', () => { - const { exists, find } = testBed; - expect(exists('appTitle')).toBe(true); - expect(find('appTitle').text()).toEqual('Index Management'); - }); - - test('should have a link to the documentation', () => { - const { exists, find } = testBed; - expect(exists('documentationLink')).toBe(true); - expect(find('documentationLink').text()).toBe('Index Management docs'); - }); - - describe('tabs', () => { - test('should have 2 tabs', () => { - const { find } = testBed; - const templatesTab = find('templatesTab'); - const indicesTab = find('indicesTab'); - - expect(indicesTab.length).toBe(1); - expect(indicesTab.text()).toEqual('Indices'); - expect(templatesTab.length).toBe(1); - expect(templatesTab.text()).toEqual('Index Templates'); - }); - - test('should navigate to Index Templates tab', async () => { - const { exists, actions, component } = testBed; - - expect(exists('indicesList')).toBe(true); - expect(exists('templateList')).toBe(false); - - httpRequestsMockHelpers.setLoadTemplatesResponse([]); - - actions.selectHomeTab('templatesTab'); - - await act(async () => { - await nextTick(); - component.update(); - }); - - expect(exists('indicesList')).toBe(false); - expect(exists('templateList')).toBe(true); - }); - }); - - describe('index templates', () => { - describe('when there are no index templates', () => { - beforeEach(async () => { - const { actions, component } = testBed; - - httpRequestsMockHelpers.setLoadTemplatesResponse([]); - - actions.selectHomeTab('templatesTab'); - - await act(async () => { - await nextTick(); - component.update(); - }); - }); - - test('should display an empty prompt', async () => { - const { exists } = testBed; - - expect(exists('sectionLoading')).toBe(false); - expect(exists('emptyPrompt')).toBe(true); - }); - }); - - describe('when there are index templates', () => { - const template1 = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - template: { - settings: { - index: { - number_of_shards: '1', - lifecycle: { - name: 'my_ilm_policy', - }, - }, - }, - }, - }); - const template2 = fixtures.getTemplate({ - name: `b${getRandomString()}`, - indexPatterns: ['template2Pattern1*'], - }); - const template3 = fixtures.getTemplate({ - name: `.c${getRandomString()}`, // mock system template - indexPatterns: ['template3Pattern1*', 'template3Pattern2', 'template3Pattern3'], - }); - - const templates = [template1, template2, template3]; - - beforeEach(async () => { - const { actions, component } = testBed; - - httpRequestsMockHelpers.setLoadTemplatesResponse(templates); - - actions.selectHomeTab('templatesTab'); - - await act(async () => { - await nextTick(); - component.update(); - }); - }); - - test('should list them in the table', async () => { - const { table } = testBed; - - const { tableCellsValues } = table.getMetaData('templateTable'); - - tableCellsValues.forEach((row, i) => { - const template = templates[i]; - const { name, indexPatterns, order, ilmPolicy } = template; - - const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : ''; - const orderFormatted = order ? order.toString() : order; - - expect(removeWhiteSpaceOnArrayValues(row)).toEqual([ - '', - name, - indexPatterns.join(', '), - ilmPolicyName, - orderFormatted, - '', - '', - '', - '', - ]); - }); - }); - - test('should have a button to reload the index templates', async () => { - const { component, exists, actions } = testBed; - const totalRequests = server.requests.length; - - expect(exists('reloadButton')).toBe(true); - - await act(async () => { - actions.clickReloadButton(); - await nextTick(); - component.update(); - }); - - expect(server.requests.length).toBe(totalRequests + 1); - expect(server.requests[server.requests.length - 1].url).toBe( - `${API_BASE_PATH}/templates` - ); - }); - - test('should have a button to create a new template', () => { - const { exists } = testBed; - expect(exists('createTemplateButton')).toBe(true); - }); - - test('should have a switch to view system templates', async () => { - const { table, exists, component, form } = testBed; - const { rows } = table.getMetaData('templateTable'); - - expect(rows.length).toEqual( - templates.filter(template => !template.name.startsWith('.')).length - ); - - expect(exists('systemTemplatesSwitch')).toBe(true); - - await act(async () => { - form.toggleEuiSwitch('systemTemplatesSwitch'); - await nextTick(); - component.update(); - }); - - const { rows: updatedRows } = table.getMetaData('templateTable'); - expect(updatedRows.length).toEqual(templates.length); - }); - - test('each row should have a link to the template details panel', async () => { - const { find, exists, actions } = testBed; - - await actions.clickTemplateAt(0); - - expect(exists('templateList')).toBe(true); - expect(exists('templateDetails')).toBe(true); - expect(find('templateDetails.title').text()).toBe(template1.name); - }); - - test('template actions column should have an option to delete', () => { - const { actions, findAction } = testBed; - const { name: templateName } = template1; - - actions.clickActionMenu(templateName); - - const deleteAction = findAction('delete'); - - expect(deleteAction.text()).toEqual('Delete'); - }); - - test('template actions column should have an option to clone', () => { - const { actions, findAction } = testBed; - const { name: templateName } = template1; - - actions.clickActionMenu(templateName); - - const cloneAction = findAction('clone'); - - expect(cloneAction.text()).toEqual('Clone'); - }); - - test('template actions column should have an option to edit', () => { - const { actions, findAction } = testBed; - const { name: templateName } = template1; - - actions.clickActionMenu(templateName); - - const editAction = findAction('edit'); - - expect(editAction.text()).toEqual('Edit'); - }); - - describe('delete index template', () => { - test('should show a confirmation when clicking the delete template button', async () => { - const { actions } = testBed; - const { name: templateName } = template1; - - await actions.clickTemplateAction(templateName, 'delete'); - - // We need to read the document "body" as the modal is added there and not inside - // the component DOM tree. - expect( - document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]') - ).not.toBe(null); - - expect( - document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]')! - .textContent - ).toContain('Delete template'); - }); - - test('should show a warning message when attempting to delete a system template', async () => { - const { component, form, actions } = testBed; - - await act(async () => { - form.toggleEuiSwitch('systemTemplatesSwitch'); - await nextTick(); - component.update(); - }); - - const { name: systemTemplateName } = template3; - await actions.clickTemplateAction(systemTemplateName, 'delete'); - - expect( - document.body.querySelector('[data-test-subj="deleteSystemTemplateCallOut"]') - ).not.toBe(null); - }); - - test('should send the correct HTTP request to delete an index template', async () => { - const { component, actions, table } = testBed; - const { rows } = table.getMetaData('templateTable'); - - const templateId = rows[0].columns[2].value; - - const { - name: templateName, - _kbnMeta: { formatVersion }, - } = template1; - await actions.clickTemplateAction(templateName, 'delete'); - - const modal = document.body.querySelector( - '[data-test-subj="deleteTemplatesConfirmation"]' - ); - const confirmButton: HTMLButtonElement | null = modal!.querySelector( - '[data-test-subj="confirmModalConfirmButton"]' - ); - - httpRequestsMockHelpers.setDeleteTemplateResponse({ - results: { - successes: [templateId], - errors: [], - }, - }); - - await act(async () => { - confirmButton!.click(); - await nextTick(); - component.update(); - }); - - const latestRequest = server.requests[server.requests.length - 1]; - - expect(latestRequest.method).toBe('POST'); - expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-templates`); - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - templates: [{ name: template1.name, formatVersion }], - }); - }); - }); - - describe('detail panel', () => { - beforeEach(async () => { - const template = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - }); - - httpRequestsMockHelpers.setLoadTemplateResponse(template); - }); - - test('should show details when clicking on a template', async () => { - const { exists, actions } = testBed; - - expect(exists('templateDetails')).toBe(false); - - await actions.clickTemplateAt(0); - - expect(exists('templateDetails')).toBe(true); - }); - - describe('on mount', () => { - beforeEach(async () => { - const { actions } = testBed; - - await actions.clickTemplateAt(0); - }); - - test('should set the correct title', async () => { - const { find } = testBed; - const { name } = template1; - - expect(find('templateDetails.title').text()).toEqual(name); - }); - - it('should have a close button and be able to close flyout', async () => { - const { actions, component, exists } = testBed; - - expect(exists('closeDetailsButton')).toBe(true); - expect(exists('summaryTab')).toBe(true); - - actions.clickCloseDetailsButton(); - - await act(async () => { - await nextTick(); - component.update(); - }); - - expect(exists('summaryTab')).toBe(false); - }); - - it('should have a manage button', async () => { - const { actions, exists } = testBed; - - await actions.clickTemplateAt(0); - - expect(exists('templateDetails.manageTemplateButton')).toBe(true); - }); - }); - - describe('tabs', () => { - test('should have 4 tabs', async () => { - const template = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - template: { - settings: { - index: { - number_of_shards: '1', - }, - }, - mappings: { - _source: { - enabled: false, - }, - properties: { - created_at: { - type: 'date', - format: 'EEE MMM dd HH:mm:ss Z yyyy', - }, - }, - }, - aliases: { - alias1: {}, - }, - }, - }); - - const { find, actions, exists } = testBed; - - httpRequestsMockHelpers.setLoadTemplateResponse(template); - - await actions.clickTemplateAt(0); - - expect(find('templateDetails.tab').length).toBe(4); - expect(find('templateDetails.tab').map(t => t.text())).toEqual([ - 'Summary', - 'Settings', - 'Mappings', - 'Aliases', - ]); - - // Summary tab should be initial active tab - expect(exists('summaryTab')).toBe(true); - - // Navigate and verify all tabs - actions.selectDetailsTab('settings'); - expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(true); - - actions.selectDetailsTab('aliases'); - expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(false); - expect(exists('aliasesTab')).toBe(true); - - actions.selectDetailsTab('mappings'); - expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(false); - expect(exists('aliasesTab')).toBe(false); - expect(exists('mappingsTab')).toBe(true); - }); - - test('should show an info callout if data is not present', async () => { - const templateWithNoOptionalFields = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - }); - - const { actions, find, exists, component } = testBed; - - httpRequestsMockHelpers.setLoadTemplateResponse(templateWithNoOptionalFields); - - await actions.clickTemplateAt(0); - - await act(async () => { - await nextTick(); - component.update(); - }); - - expect(find('templateDetails.tab').length).toBe(4); - expect(exists('summaryTab')).toBe(true); - - // Navigate and verify callout message per tab - actions.selectDetailsTab('settings'); - expect(exists('noSettingsCallout')).toBe(true); - - actions.selectDetailsTab('mappings'); - expect(exists('noMappingsCallout')).toBe(true); - - actions.selectDetailsTab('aliases'); - expect(exists('noAliasesCallout')).toBe(true); - }); - }); - - describe('error handling', () => { - it('should render an error message if error fetching template details', async () => { - const { actions, exists } = testBed; - const error = { - status: 404, - error: 'Not found', - message: 'Template not found', - }; - - httpRequestsMockHelpers.setLoadTemplateResponse(undefined, { body: error }); - - await actions.clickTemplateAt(0); - - expect(exists('sectionError')).toBe(true); - // Manage button should not render if error - expect(exists('templateDetails.manageTemplateButton')).toBe(false); - }); - }); - }); - }); - }); - }); - - describe('index detail panel with % character in index name', () => { - const indexName = 'test%'; - beforeEach(async () => { - const index = { - health: 'green', - status: 'open', - primary: 1, - replica: 1, - documents: 10000, - documents_deleted: 100, - size: '156kb', - primary_size: '156kb', - name: indexName, - }; - httpRequestsMockHelpers.setLoadIndicesResponse([index]); - - testBed = await setup(); - const { component, find } = testBed; - - component.update(); - - find('indexTableIndexNameLink') - .at(0) - .simulate('click'); - }); - - test('should encode indexName when loading settings in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('settings'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); - }); - - test('should encode indexName when loading mappings in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('mappings'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/mapping/${encodeURIComponent(indexName)}`); - }); - - test('should encode indexName when loading stats in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('stats'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/stats/${encodeURIComponent(indexName)}`); - }); - - test('should encode indexName when editing settings in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('edit_settings'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); - }); - }); -}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts new file mode 100644 index 0000000000000..c58109364890a --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; +import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: TestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/indices?includeHidden=true`], + componentRoutePath: `/:section(indices|templates)`, + }, + doMountAsync: true, +}; + +const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); + +export interface HomeTestBed extends TestBed { + actions: { + selectHomeTab: (tab: 'indicesTab' | 'templatesTab') => void; + }; +} + +export const setup = async (): Promise => { + const testBed = await initTestBed(); + + /** + * User Actions + */ + + const selectHomeTab = (tab: 'indicesTab' | 'templatesTab') => { + testBed.find(tab).simulate('click'); + }; + + return { + ...testBed, + actions: { + selectHomeTab, + }, + }; +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts new file mode 100644 index 0000000000000..a7ac2ebf9bb02 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { act } from 'react-dom/test-utils'; + +import { setupEnvironment, nextTick } from '../helpers'; +import { HomeTestBed, setup } from './home.helpers'; + +/** + * The below import is required to avoid a console error warn from the "brace" package + * console.warn ../node_modules/brace/index.js:3999 + Could not load worker ReferenceError: Worker is not defined + at createWorker (//node_modules/brace/index.js:17992:5) + */ +import { stubWebWorker } from '../../../../../test_utils/stub_web_worker'; +stubWebWorker(); + +describe('', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: HomeTestBed; + + afterAll(() => { + server.restore(); + }); + + describe('on component mount', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([]); + + testBed = await setup(); + + await act(async () => { + const { component } = testBed; + + await nextTick(); + component.update(); + }); + }); + + test('should set the correct app title', () => { + const { exists, find } = testBed; + expect(exists('appTitle')).toBe(true); + expect(find('appTitle').text()).toEqual('Index Management'); + }); + + test('should have a link to the documentation', () => { + const { exists, find } = testBed; + expect(exists('documentationLink')).toBe(true); + expect(find('documentationLink').text()).toBe('Index Management docs'); + }); + + describe('tabs', () => { + test('should have 2 tabs', () => { + const { find } = testBed; + const templatesTab = find('templatesTab'); + const indicesTab = find('indicesTab'); + + expect(indicesTab.length).toBe(1); + expect(indicesTab.text()).toEqual('Indices'); + expect(templatesTab.length).toBe(1); + expect(templatesTab.text()).toEqual('Index Templates'); + }); + + test('should navigate to Index Templates tab', async () => { + const { exists, actions, component } = testBed; + + expect(exists('indicesList')).toBe(true); + expect(exists('templateList')).toBe(false); + + httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] }); + + actions.selectHomeTab('templatesTab'); + + await act(async () => { + await nextTick(); + component.update(); + }); + + expect(exists('indicesList')).toBe(false); + expect(exists('templateList')).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts new file mode 100644 index 0000000000000..98bd3077670a7 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; + +import { + registerTestBed, + TestBed, + TestBedConfig, + findTestSubject, +} from '../../../../../test_utils'; +// NOTE: We have to use the Home component instead of the TemplateList component because we depend +// upon react router to provide the name of the template to load in the detail panel. +import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { TemplateDeserialized } from '../../../common'; +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: TestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/indices`], + componentRoutePath: `/:section(indices|templates)`, + }, + doMountAsync: true, +}; + +const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); + +export interface IndexTemplatesTabTestBed extends TestBed { + findAction: (action: 'edit' | 'clone' | 'delete') => ReactWrapper; + actions: { + goToTemplatesList: () => void; + selectDetailsTab: (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => void; + clickReloadButton: () => void; + clickTemplateAction: ( + name: TemplateDeserialized['name'], + action: 'edit' | 'clone' | 'delete' + ) => void; + clickTemplateAt: (index: number) => void; + clickCloseDetailsButton: () => void; + clickActionMenu: (name: TemplateDeserialized['name']) => void; + toggleViewItem: (view: 'composable' | 'system') => void; + }; +} + +export const setup = async (): Promise => { + const testBed = await initTestBed(); + + /** + * Additional helpers + */ + const findAction = (action: 'edit' | 'clone' | 'delete') => { + const actions = ['edit', 'clone', 'delete']; + const { component } = testBed; + + return component.find('.euiContextMenuItem').at(actions.indexOf(action)); + }; + + /** + * User Actions + */ + + const goToTemplatesList = () => { + testBed.find('templatesTab').simulate('click'); + }; + + const selectDetailsTab = (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => { + const tabs = ['summary', 'settings', 'mappings', 'aliases']; + + testBed.find('templateDetails.tab').at(tabs.indexOf(tab)).simulate('click'); + }; + + const clickReloadButton = () => { + const { find } = testBed; + find('reloadButton').simulate('click'); + }; + + const clickActionMenu = async (templateName: TemplateDeserialized['name']) => { + const { component } = testBed; + + // When a table has > 2 actions, EUI displays an overflow menu with an id "-actions" + // The template name may contain a period (.) so we use bracket syntax for selector + component.find(`div[id="${templateName}-actions"] button`).simulate('click'); + }; + + const clickTemplateAction = ( + templateName: TemplateDeserialized['name'], + action: 'edit' | 'clone' | 'delete' + ) => { + const actions = ['edit', 'clone', 'delete']; + const { component } = testBed; + + clickActionMenu(templateName); + + component.find('.euiContextMenuItem').at(actions.indexOf(action)).simulate('click'); + }; + + const clickTemplateAt = async (index: number) => { + const { component, table, router } = testBed; + const { rows } = table.getMetaData('legacyTemplateTable'); + const templateLink = findTestSubject(rows[index].reactWrapper, 'templateDetailsLink'); + + const { href } = templateLink.props(); + await act(async () => { + router.navigateTo(href!); + }); + component.update(); + }; + + const clickCloseDetailsButton = () => { + const { find } = testBed; + + find('closeDetailsButton').simulate('click'); + }; + + const toggleViewItem = (view: 'composable' | 'system') => { + const { find, component } = testBed; + const views = ['composable', 'system']; + + // First open the pop over + act(() => { + find('viewButton').simulate('click'); + }); + component.update(); + + // Then click on a filter item + act(() => { + find('filterList.filterItem').at(views.indexOf(view)).simulate('click'); + }); + component.update(); + }; + + return { + ...testBed, + findAction, + actions: { + goToTemplatesList, + selectDetailsTab, + clickReloadButton, + clickTemplateAction, + clickTemplateAt, + clickCloseDetailsButton, + clickActionMenu, + toggleViewItem, + }, + }; +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts new file mode 100644 index 0000000000000..8f6a8dddeb195 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts @@ -0,0 +1,497 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { act } from 'react-dom/test-utils'; + +import * as fixtures from '../../../test/fixtures'; +import { API_BASE_PATH } from '../../../common/constants'; +import { setupEnvironment, getRandomString } from '../helpers'; + +import { IndexTemplatesTabTestBed, setup } from './index_templates_tab.helpers'; + +const removeWhiteSpaceOnArrayValues = (array: any[]) => + array.map((value) => { + if (typeof value !== 'string') { + return value; + } + + // Convert non breaking spaces ( ) to ordinary space + return value.trim().replace(/\s/g, ' '); + }); + +describe('Index Templates tab', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: IndexTemplatesTabTestBed; + + afterAll(() => { + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([]); + + await act(async () => { + testBed = await setup(); + }); + }); + + describe('when there are no index templates', () => { + beforeEach(async () => { + const { actions, component } = testBed; + + httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] }); + + await act(async () => { + actions.goToTemplatesList(); + }); + component.update(); + }); + + test('should display an empty prompt', async () => { + const { exists } = testBed; + + expect(exists('sectionLoading')).toBe(false); + expect(exists('emptyPrompt')).toBe(true); + }); + }); + + describe('when there are index templates', () => { + // Add a default loadIndexTemplate response + httpRequestsMockHelpers.setLoadTemplateResponse(fixtures.getTemplate()); + + const template1 = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + template: { + settings: { + index: { + number_of_shards: '1', + lifecycle: { + name: 'my_ilm_policy', + }, + }, + }, + }, + }); + + const template2 = fixtures.getTemplate({ + name: `b${getRandomString()}`, + indexPatterns: ['template2Pattern1*'], + }); + + const template3 = fixtures.getTemplate({ + name: `.c${getRandomString()}`, // mock system template + indexPatterns: ['template3Pattern1*', 'template3Pattern2', 'template3Pattern3'], + }); + + const template4 = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template4Pattern1*', 'template4Pattern2'], + template: { + settings: { + index: { + number_of_shards: '1', + lifecycle: { + name: 'my_ilm_policy', + }, + }, + }, + }, + isLegacy: true, + }); + + const template5 = fixtures.getTemplate({ + name: `b${getRandomString()}`, + indexPatterns: ['template5Pattern1*'], + isLegacy: true, + }); + + const template6 = fixtures.getTemplate({ + name: `.c${getRandomString()}`, // mock system template + indexPatterns: ['template6Pattern1*', 'template6Pattern2', 'template6Pattern3'], + isLegacy: true, + }); + + const templates = [template1, template2, template3]; + const legacyTemplates = [template4, template5, template6]; + + beforeEach(async () => { + const { actions, component } = testBed; + + httpRequestsMockHelpers.setLoadTemplatesResponse({ templates, legacyTemplates }); + + await act(async () => { + actions.goToTemplatesList(); + }); + component.update(); + }); + + test('should list them in the table', async () => { + const { table } = testBed; + + const { tableCellsValues } = table.getMetaData('templateTable'); + const { tableCellsValues: legacyTableCellsValues } = table.getMetaData('legacyTemplateTable'); + + // Test composable table content + tableCellsValues.forEach((row, i) => { + const template = templates[i]; + const { name, indexPatterns, priority, ilmPolicy, composedOf } = template; + + const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : ''; + const composedOfString = composedOf ? composedOf.join(',') : ''; + const priorityFormatted = priority ? priority.toString() : ''; + + expect(removeWhiteSpaceOnArrayValues(row)).toEqual([ + name, + indexPatterns.join(', '), + ilmPolicyName, + composedOfString, + priorityFormatted, + 'M S A', // Mappings Settings Aliases badges + ]); + }); + + // Test legacy table content + legacyTableCellsValues.forEach((row, i) => { + const template = legacyTemplates[i]; + const { name, indexPatterns, order, ilmPolicy } = template; + + const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : ''; + const orderFormatted = order ? order.toString() : order; + + expect(removeWhiteSpaceOnArrayValues(row)).toEqual([ + '', + name, + indexPatterns.join(', '), + ilmPolicyName, + orderFormatted, + '', + '', + '', + '', + ]); + }); + }); + + test('should have a button to reload the index templates', async () => { + const { exists, actions } = testBed; + const totalRequests = server.requests.length; + + expect(exists('reloadButton')).toBe(true); + + await act(async () => { + actions.clickReloadButton(); + }); + + expect(server.requests.length).toBe(totalRequests + 1); + expect(server.requests[server.requests.length - 1].url).toBe( + `${API_BASE_PATH}/index-templates` + ); + }); + + test('should have a button to create a new template', () => { + const { exists } = testBed; + expect(exists('createLegacyTemplateButton')).toBe(true); + }); + + test('should have a switch to view system templates', async () => { + const { table, exists, actions } = testBed; + const { rows } = table.getMetaData('legacyTemplateTable'); + + expect(rows.length).toEqual( + legacyTemplates.filter((template) => !template.name.startsWith('.')).length + ); + + expect(exists('viewButton')).toBe(true); + + actions.toggleViewItem('system'); + + const { rows: updatedRows } = table.getMetaData('legacyTemplateTable'); + expect(updatedRows.length).toEqual(legacyTemplates.length); + }); + + test('each row should have a link to the template details panel', async () => { + const { find, exists, actions } = testBed; + + await actions.clickTemplateAt(0); + + expect(exists('templateList')).toBe(true); + expect(exists('templateDetails')).toBe(true); + expect(find('templateDetails.title').text()).toBe(legacyTemplates[0].name); + }); + + test('template actions column should have an option to delete', () => { + const { actions, findAction } = testBed; + const [{ name: templateName }] = legacyTemplates; + + actions.clickActionMenu(templateName); + + const deleteAction = findAction('delete'); + + expect(deleteAction.text()).toEqual('Delete'); + }); + + test('template actions column should have an option to clone', () => { + const { actions, findAction } = testBed; + const [{ name: templateName }] = legacyTemplates; + + actions.clickActionMenu(templateName); + + const cloneAction = findAction('clone'); + + expect(cloneAction.text()).toEqual('Clone'); + }); + + test('template actions column should have an option to edit', () => { + const { actions, findAction } = testBed; + const [{ name: templateName }] = legacyTemplates; + + actions.clickActionMenu(templateName); + + const editAction = findAction('edit'); + + expect(editAction.text()).toEqual('Edit'); + }); + + describe('delete index template', () => { + test('should show a confirmation when clicking the delete template button', async () => { + const { actions } = testBed; + const [{ name: templateName }] = legacyTemplates; + + await actions.clickTemplateAction(templateName, 'delete'); + + // We need to read the document "body" as the modal is added there and not inside + // the component DOM tree. + expect( + document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]') + ).not.toBe(null); + + expect( + document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]')!.textContent + ).toContain('Delete template'); + }); + + test('should show a warning message when attempting to delete a system template', async () => { + const { exists, actions } = testBed; + + actions.toggleViewItem('system'); + + const { name: systemTemplateName } = legacyTemplates[2]; + await actions.clickTemplateAction(systemTemplateName, 'delete'); + + expect(exists('deleteSystemTemplateCallOut')).toBe(true); + }); + + test('should send the correct HTTP request to delete an index template', async () => { + const { actions, table } = testBed; + const { rows } = table.getMetaData('legacyTemplateTable'); + + const templateId = rows[0].columns[2].value; + + const [ + { + name: templateName, + _kbnMeta: { isLegacy }, + }, + ] = legacyTemplates; + await actions.clickTemplateAction(templateName, 'delete'); + + const modal = document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]'); + const confirmButton: HTMLButtonElement | null = modal!.querySelector( + '[data-test-subj="confirmModalConfirmButton"]' + ); + + httpRequestsMockHelpers.setDeleteTemplateResponse({ + results: { + successes: [templateId], + errors: [], + }, + }); + + await act(async () => { + confirmButton!.click(); + }); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(latestRequest.method).toBe('POST'); + expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-index-templates`); + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ + templates: [{ name: legacyTemplates[0].name, isLegacy }], + }); + }); + }); + + describe('detail panel', () => { + beforeEach(async () => { + const template = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + isLegacy: true, + }); + + httpRequestsMockHelpers.setLoadTemplateResponse(template); + }); + + test('should show details when clicking on a template', async () => { + const { exists, actions } = testBed; + + expect(exists('templateDetails')).toBe(false); + + await actions.clickTemplateAt(0); + + expect(exists('templateDetails')).toBe(true); + }); + + describe('on mount', () => { + beforeEach(async () => { + const { actions } = testBed; + + await actions.clickTemplateAt(0); + }); + + test('should set the correct title', async () => { + const { find } = testBed; + const [{ name }] = legacyTemplates; + + expect(find('templateDetails.title').text()).toEqual(name); + }); + + it('should have a close button and be able to close flyout', async () => { + const { actions, component, exists } = testBed; + + expect(exists('closeDetailsButton')).toBe(true); + expect(exists('summaryTab')).toBe(true); + + await act(async () => { + actions.clickCloseDetailsButton(); + }); + component.update(); + + expect(exists('summaryTab')).toBe(false); + }); + + it('should have a manage button', async () => { + const { actions, exists } = testBed; + + await actions.clickTemplateAt(0); + + expect(exists('templateDetails.manageTemplateButton')).toBe(true); + }); + }); + + describe('tabs', () => { + test('should have 4 tabs', async () => { + const template = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + template: { + settings: { + index: { + number_of_shards: '1', + }, + }, + mappings: { + _source: { + enabled: false, + }, + properties: { + created_at: { + type: 'date', + format: 'EEE MMM dd HH:mm:ss Z yyyy', + }, + }, + }, + aliases: { + alias1: {}, + }, + }, + isLegacy: true, + }); + + const { find, actions, exists } = testBed; + + httpRequestsMockHelpers.setLoadTemplateResponse(template); + + await actions.clickTemplateAt(0); + + expect(find('templateDetails.tab').length).toBe(4); + expect(find('templateDetails.tab').map((t) => t.text())).toEqual([ + 'Summary', + 'Settings', + 'Mappings', + 'Aliases', + ]); + + // Summary tab should be initial active tab + expect(exists('summaryTab')).toBe(true); + + // Navigate and verify all tabs + actions.selectDetailsTab('settings'); + expect(exists('summaryTab')).toBe(false); + expect(exists('settingsTab')).toBe(true); + + actions.selectDetailsTab('aliases'); + expect(exists('summaryTab')).toBe(false); + expect(exists('settingsTab')).toBe(false); + expect(exists('aliasesTab')).toBe(true); + + actions.selectDetailsTab('mappings'); + expect(exists('summaryTab')).toBe(false); + expect(exists('settingsTab')).toBe(false); + expect(exists('aliasesTab')).toBe(false); + expect(exists('mappingsTab')).toBe(true); + }); + + test('should show an info callout if data is not present', async () => { + const templateWithNoOptionalFields = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + isLegacy: true, + }); + + const { actions, find, exists } = testBed; + + httpRequestsMockHelpers.setLoadTemplateResponse(templateWithNoOptionalFields); + + await actions.clickTemplateAt(0); + + expect(find('templateDetails.tab').length).toBe(4); + expect(exists('summaryTab')).toBe(true); + + // Navigate and verify callout message per tab + actions.selectDetailsTab('settings'); + expect(exists('noSettingsCallout')).toBe(true); + + actions.selectDetailsTab('mappings'); + expect(exists('noMappingsCallout')).toBe(true); + + actions.selectDetailsTab('aliases'); + expect(exists('noAliasesCallout')).toBe(true); + }); + }); + + describe('error handling', () => { + it('should render an error message if error fetching template details', async () => { + const { actions, exists } = testBed; + const error = { + status: 404, + error: 'Not found', + message: 'Template not found', + }; + + httpRequestsMockHelpers.setLoadTemplateResponse(undefined, { body: error }); + + await actions.clickTemplateAt(0); + + expect(exists('sectionError')).toBe(true); + // Manage button should not render if error + expect(exists('templateDetails.manageTemplateButton')).toBe(false); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts new file mode 100644 index 0000000000000..e995932dfa00d --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { act } from 'react-dom/test-utils'; + +import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; +import { IndexList } from '../../../public/application/sections/home/index_list'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: TestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/indices?includeHiddenIndices=true`], + componentRoutePath: `/:section(indices|templates)`, + }, + doMountAsync: true, +}; + +const initTestBed = registerTestBed(WithAppDependencies(IndexList), testBedConfig); + +export interface IndicesTestBed extends TestBed { + actions: { + selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void; + getIncludeHiddenIndicesToggleStatus: () => boolean; + clickIncludeHiddenIndicesToggle: () => void; + }; +} + +export const setup = async (): Promise => { + const testBed = await initTestBed(); + + /** + * User Actions + */ + + const clickIncludeHiddenIndicesToggle = () => { + const { find } = testBed; + find('indexTableIncludeHiddenIndicesToggle').simulate('click'); + }; + + const getIncludeHiddenIndicesToggleStatus = () => { + const { find } = testBed; + const props = find('indexTableIncludeHiddenIndicesToggle').props(); + return Boolean(props['aria-checked']); + }; + + const selectIndexDetailsTab = async ( + tab: 'settings' | 'mappings' | 'stats' | 'edit_settings' + ) => { + const indexDetailsTabs = ['settings', 'mappings', 'stats', 'edit_settings']; + const { find, component } = testBed; + await act(async () => { + find('detailPanelTab').at(indexDetailsTabs.indexOf(tab)).simulate('click'); + }); + component.update(); + }; + + return { + ...testBed, + actions: { + selectIndexDetailsTab, + getIncludeHiddenIndicesToggleStatus, + clickIncludeHiddenIndicesToggle, + }, + }; +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts new file mode 100644 index 0000000000000..11c25ffbb590f --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { act } from 'react-dom/test-utils'; + +import { API_BASE_PATH } from '../../../common/constants'; +import { setupEnvironment, nextTick } from '../helpers'; +import { IndicesTestBed, setup } from './indices_tab.helpers'; + +/** + * The below import is required to avoid a console error warn from the "brace" package + * console.warn ../node_modules/brace/index.js:3999 + Could not load worker ReferenceError: Worker is not defined + at createWorker (//node_modules/brace/index.js:17992:5) + */ +import { stubWebWorker } from '../../../../../test_utils/stub_web_worker'; +stubWebWorker(); + +describe('', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: IndicesTestBed; + + afterAll(() => { + server.restore(); + }); + + describe('on component mount', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([]); + + testBed = await setup(); + + await act(async () => { + const { component } = testBed; + + await nextTick(); + component.update(); + }); + }); + + test('toggles the include hidden button through URL hash correctly', () => { + const { actions } = testBed; + expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); + actions.clickIncludeHiddenIndicesToggle(); + expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(false); + // Note: this test modifies the shared location.hash state, we put it back the way it was + actions.clickIncludeHiddenIndicesToggle(); + expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); + }); + }); + + describe('index detail panel with % character in index name', () => { + const indexName = 'test%'; + beforeEach(async () => { + const index = { + health: 'green', + status: 'open', + primary: 1, + replica: 1, + documents: 10000, + documents_deleted: 100, + size: '156kb', + primary_size: '156kb', + name: indexName, + }; + httpRequestsMockHelpers.setLoadIndicesResponse([index]); + + testBed = await setup(); + const { component, find } = testBed; + + component.update(); + + find('indexTableIndexNameLink').at(0).simulate('click'); + }); + + test('should encode indexName when loading settings in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('settings'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); + }); + + test('should encode indexName when loading mappings in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('mappings'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/mapping/${encodeURIComponent(indexName)}`); + }); + + test('should encode indexName when loading stats in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('stats'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/stats/${encodeURIComponent(indexName)}`); + }); + + test('should encode indexName when editing settings in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('edit_settings'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/constants.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts similarity index 100% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/constants.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_clone.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts similarity index 76% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_clone.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts index 36498b99ba143..1a58cfa8fb55e 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_clone.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts @@ -5,16 +5,16 @@ */ import { registerTestBed, TestBedConfig } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { TemplateClone } from '../../../public/application/sections/template_clone'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies } from '../helpers'; + import { formSetup } from './template_form.helpers'; import { TEMPLATE_NAME } from './constants'; -import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}clone_template/${TEMPLATE_NAME}`], - componentRoutePath: `${BASE_PATH}clone_template/:name`, + initialEntries: [`/clone_template/${TEMPLATE_NAME}`], + componentRoutePath: `/clone_template/:name`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx similarity index 88% rename from x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx index fa9d13d1ddd07..6250ef0dc247d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx @@ -3,21 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React from 'react'; import { act } from 'react-dom/test-utils'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { TemplateFormTestBed } from './helpers/template_form.helpers'; -import { getTemplate } from '../../test/fixtures'; -import { - TEMPLATE_NAME, - INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, - MAPPINGS, -} from './helpers/constants'; - -const { setup } = pageHelpers.templateClone; +import { getTemplate } from '../../../test/fixtures'; +import { setupEnvironment, nextTick } from '../helpers'; -jest.mock('ui/new_platform'); +import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, MAPPINGS } from './constants'; +import { setup } from './template_clone.helpers'; +import { TemplateFormTestBed } from './template_form.helpers'; jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), @@ -58,6 +53,7 @@ describe.skip('', () => { template: { mappings: MAPPINGS, }, + isLegacy: true, }); beforeEach(async () => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_create.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts similarity index 77% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_create.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts index 14a44968a93c3..ab0a7b8567607 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_create.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts @@ -5,15 +5,15 @@ */ import { registerTestBed, TestBedConfig } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { TemplateCreate } from '../../../public/application/sections/template_create'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies } from '../helpers'; + import { formSetup, TestSubjects } from './template_form.helpers'; -import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}create_template`], - componentRoutePath: `${BASE_PATH}create_template`, + initialEntries: [`/create_template`], + componentRoutePath: `/create_template`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx similarity index 95% rename from x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index ec810faf687be..50b35fc76721a 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -3,23 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React from 'react'; import { act } from 'react-dom/test-utils'; -import { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../common'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { TemplateFormTestBed } from './helpers/template_form.helpers'; +import { CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from '../../../common'; +import { setupEnvironment, nextTick } from '../helpers'; + import { TEMPLATE_NAME, SETTINGS, MAPPINGS, ALIASES, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, -} from './helpers/constants'; - -const { setup } = pageHelpers.templateCreate; - -jest.mock('ui/new_platform'); +} from './constants'; +import { setup } from './template_create.helpers'; +import { TemplateFormTestBed } from './template_form.helpers'; jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), @@ -59,7 +58,8 @@ const KEYWORD_MAPPING_FIELD = { type: 'keyword', }; -describe('', () => { +// FLAKY: https://github.com/elastic/kibana/issues/67833 +describe.skip('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -259,7 +259,7 @@ describe('', () => { expect( find('summaryTabContent') .find('.euiTab') - .map(t => t.text()) + .map((t) => t.text()) ).toEqual(['Summary', 'Request']); }); @@ -344,7 +344,6 @@ describe('', () => { const latestRequest = server.requests[server.requests.length - 1]; const expected = { - isManaged: false, name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, template: { @@ -366,7 +365,8 @@ describe('', () => { aliases: ALIASES, }, _kbnMeta: { - formatVersion: DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT, + isLegacy: CREATE_LEGACY_TEMPLATE_BY_DEFAULT, + isManaged: false, }, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_edit.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts similarity index 77% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_edit.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts index af5fa8b79ecad..29ecd84e585ce 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_edit.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts @@ -5,16 +5,16 @@ */ import { registerTestBed, TestBedConfig } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { TemplateEdit } from '../../../public/application/sections/template_edit'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies } from '../helpers'; + import { formSetup, TestSubjects } from './template_form.helpers'; import { TEMPLATE_NAME } from './constants'; -import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}edit_template/${TEMPLATE_NAME}`], - componentRoutePath: `${BASE_PATH}edit_template/:name`, + initialEntries: [`/edit_template/${TEMPLATE_NAME}`], + componentRoutePath: `/edit_template/:name`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx similarity index 93% rename from x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index 0ed369e9b13f7..88067d479f7e7 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -3,13 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React from 'react'; import { act } from 'react-dom/test-utils'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { TemplateFormTestBed } from './helpers/template_form.helpers'; -import * as fixtures from '../../test/fixtures'; -import { TEMPLATE_NAME, SETTINGS, ALIASES, MAPPINGS as DEFAULT_MAPPING } from './helpers/constants'; +import * as fixtures from '../../../test/fixtures'; +import { setupEnvironment, nextTick } from '../helpers'; + +import { TEMPLATE_NAME, SETTINGS, ALIASES, MAPPINGS as DEFAULT_MAPPING } from './constants'; +import { setup } from './template_edit.helpers'; +import { TemplateFormTestBed } from './template_form.helpers'; const UPDATED_INDEX_PATTERN = ['updatedIndexPattern']; const UPDATED_MAPPING_TEXT_FIELD_NAME = 'updated_text_datatype'; @@ -22,10 +25,6 @@ const MAPPING = { }, }; -const { setup } = pageHelpers.templateEdit; - -jest.mock('ui/new_platform'); - jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions, @@ -63,6 +62,7 @@ describe.skip('', () => { const templateToEdit = fixtures.getTemplate({ name: 'index_template_without_mappings', indexPatterns: ['indexPattern1'], + isLegacy: true, }); beforeEach(async () => { @@ -103,6 +103,7 @@ describe.skip('', () => { template: { mappings: MAPPING, }, + isLegacy: true, }); beforeEach(async () => { @@ -207,9 +208,9 @@ describe.skip('', () => { settings: SETTINGS, aliases: ALIASES, }, - isManaged: false, _kbnMeta: { - formatVersion: templateToEdit._kbnMeta.formatVersion, + isManaged: false, + isLegacy: templateToEdit._kbnMeta.isLegacy, }, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts similarity index 94% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts index e0cffb7f0969a..fdf837a914cf1 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts @@ -3,9 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { TestBed, SetupFunc, UnwrapPromise } from '../../../../../test_utils'; import { TemplateDeserialized } from '../../../common'; -import { nextTick } from './index'; +import { nextTick } from '../helpers'; interface MappingField { name: string; @@ -34,10 +35,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { }; const clickEditButtonAtField = (index: number) => { - testBed - .find('editFieldButton') - .at(index) - .simulate('click'); + testBed.find('editFieldButton').at(index).simulate('click'); }; const clickEditFieldUpdateButton = () => { @@ -45,10 +43,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { }; const deleteMappingsFieldAt = (index: number) => { - testBed - .find('removeFieldButton') - .at(index) - .simulate('click'); + testBed.find('removeFieldButton').at(index).simulate('click'); testBed.find('confirmModalConfirmButton').simulate('click'); }; @@ -143,11 +138,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { const selectSummaryTab = (tab: 'summary' | 'request') => { const tabs = ['summary', 'request']; - testBed - .find('summaryTabContent') - .find('.euiTab') - .at(tabs.indexOf(tab)) - .simulate('click'); + testBed.find('summaryTabContent').find('.euiTab').at(tabs.indexOf(tab)).simulate('click'); }; const addMappingField = async (name: string, type: string) => { diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index 5e3edc78da5a8..8e8c2632a2372 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -37,8 +37,6 @@ import { findTestSubject } from '@elastic/eui/lib/test'; /* eslint-disable @kbn/eslint/no-restricted-paths */ import { notificationServiceMock } from '../../../../../src/core/public/notifications/notifications_service.mock'; -jest.mock('ui/new_platform'); - const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); let server = null; @@ -75,7 +73,7 @@ const status = (rendered, row = 0) => { .text(); }; -const snapshot = rendered => { +const snapshot = (rendered) => { expect(rendered).toMatchSnapshot(); }; const openMenuAndClickButton = (rendered, rowIndex, buttonIndex) => { @@ -108,11 +106,11 @@ const testAction = (buttonIndex, done, rowIndex = 0) => { openMenuAndClickButton(rendered, rowIndex, buttonIndex); snapshot(status(rendered, rowIndex)); }; -const names = rendered => { +const names = (rendered) => { return findTestSubject(rendered, 'indexTableIndexNameLink'); }; -const namesText = rendered => { - return names(rendered).map(button => button.text()); +const namesText = (rendered) => { + return names(rendered).map((button) => button.text()); }; describe('index table', () => { @@ -203,14 +201,14 @@ describe('index table', () => { snapshot( rendered .find('.euiPagination .euiPaginationButton .euiButtonEmpty__content > span') - .map(span => span.text()) + .map((span) => span.text()) ); const switchControl = rendered.find('.euiSwitch__button'); switchControl.simulate('click'); snapshot( rendered .find('.euiPagination .euiPaginationButton .euiButtonEmpty__content > span') - .map(span => span.text()) + .map((span) => span.text()) ); }); test('should filter based on content of search input', () => { @@ -247,7 +245,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map(span => span.text())); + snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); }); test('should show the right context menu options when one index is selected and closed', () => { const rendered = mountWithIntl(component); @@ -257,7 +255,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map(span => span.text())); + snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); }); test('should show the right context menu options when one open and one closed index is selected', () => { const rendered = mountWithIntl(component); @@ -268,7 +266,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map(span => span.text())); + snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); }); test('should show the right context menu options when more than one open index is selected', () => { const rendered = mountWithIntl(component); @@ -279,7 +277,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map(span => span.text())); + snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); }); test('should show the right context menu options when more than one closed index is selected', () => { const rendered = mountWithIntl(component); @@ -290,18 +288,18 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map(span => span.text())); + snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); }); - test('flush button works from context menu', done => { + test('flush button works from context menu', (done) => { testAction(8, done); }); - test('clear cache button works from context menu', done => { + test('clear cache button works from context menu', (done) => { testAction(7, done); }); - test('refresh button works from context menu', done => { + test('refresh button works from context menu', (done) => { testAction(6, done); }); - test('force merge button works from context menu', done => { + test('force merge button works from context menu', (done) => { const rendered = mountWithIntl(component); const rowIndex = 0; openMenuAndClickButton(rendered, rowIndex, 5); @@ -322,8 +320,8 @@ describe('index table', () => { }); // Commenting the following 2 tests as it works in the browser (status changes to "closed" or "open") but the // snapshot say the contrary. Need to be investigated. - test('close index button works from context menu', done => { - const modifiedIndices = indices.map(index => { + test('close index button works from context menu', (done) => { + const modifiedIndices = indices.map((index) => { return { ...index, status: index.name === 'testy0' ? 'close' : index.status, @@ -337,8 +335,8 @@ describe('index table', () => { ]); testAction(4, done); }); - test('open index button works from context menu', done => { - const modifiedIndices = indices.map(index => { + test('open index button works from context menu', (done) => { + const modifiedIndices = indices.map((index) => { return { ...index, status: index.name === 'testy1' ? 'open' : index.status, diff --git a/x-pack/plugins/index_management/__mocks__/ace.js b/x-pack/plugins/index_management/__mocks__/ace.js deleted file mode 100644 index 40ce7026eee11..0000000000000 --- a/x-pack/plugins/index_management/__mocks__/ace.js +++ /dev/null @@ -1,45 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export default { - edit: () => { - return { - navigateFileEnd() {}, - destroy() {}, - acequire() { - return { - setCompleters() {}, - }; - }, - setValue() {}, - setOptions() {}, - setTheme() {}, - setFontSize() {}, - setShowPrintMargin() {}, - getSession() { - return { - setUseWrapMode() {}, - setMode() {}, - setValue() {}, - on() {}, - }; - }, - renderer: { - setShowGutter() {}, - setScrollMargin() {}, - }, - setBehavioursEnabled() {}, - }; - }, - acequire() { - return { - setCompleters() {}, - }; - }, - setCompleters() { - return [{}]; - }, -}; diff --git a/x-pack/plugins/index_management/__mocks__/ui/documentation_links.js b/x-pack/plugins/index_management/__mocks__/ui/documentation_links.js deleted file mode 100644 index 0da03ba9b98ba..0000000000000 --- a/x-pack/plugins/index_management/__mocks__/ui/documentation_links.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const settingsDocumentationLink = 'https://stuff.com/docs'; diff --git a/x-pack/plugins/index_management/__mocks__/ui/notify.js b/x-pack/plugins/index_management/__mocks__/ui/notify.js deleted file mode 100644 index 3d64a99232bc3..0000000000000 --- a/x-pack/plugins/index_management/__mocks__/ui/notify.js +++ /dev/null @@ -1,15 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const toastNotifications = { - addInfo: () => {}, - addSuccess: () => {}, - addDanger: () => {}, - addWarning: () => {}, - addError: () => {}, -}; - -export function fatalError() {} diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts index 966e2e8e64838..526b9fede2a67 100644 --- a/x-pack/plugins/index_management/common/constants/index.ts +++ b/x-pack/plugins/index_management/common/constants/index.ts @@ -9,7 +9,7 @@ export { BASE_PATH } from './base_path'; export { API_BASE_PATH } from './api_base_path'; export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters'; export * from './index_statuses'; -export { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './index_templates'; +export { CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from './index_templates'; export { UIM_APP_NAME, diff --git a/x-pack/plugins/index_management/common/constants/index_templates.ts b/x-pack/plugins/index_management/common/constants/index_templates.ts index 788e96ee895ed..7696b3832c51e 100644 --- a/x-pack/plugins/index_management/common/constants/index_templates.ts +++ b/x-pack/plugins/index_management/common/constants/index_templates.ts @@ -6,7 +6,7 @@ /** * Up until the end of the 8.x release cycle we need to support both - * V1 and V2 index template formats. This constant keeps track of whether - * we create V1 or V2 index template format in the UI. + * legacy and composable index template formats. This constant keeps track of whether + * we create legacy index template format by default in the UI. */ -export const DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT = 1; +export const CREATE_LEGACY_TEMPLATE_BY_DEFAULT = true; diff --git a/x-pack/plugins/index_management/common/index.ts b/x-pack/plugins/index_management/common/index.ts index 459eda7552c85..3792e322ae40b 100644 --- a/x-pack/plugins/index_management/common/index.ts +++ b/x-pack/plugins/index_management/common/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { PLUGIN, API_BASE_PATH, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './constants'; +export { PLUGIN, API_BASE_PATH, CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from './constants'; export { getTemplateParameter } from './lib'; diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts index 33f7fbe45182e..16eb544c56a08 100644 --- a/x-pack/plugins/index_management/common/lib/index.ts +++ b/x-pack/plugins/index_management/common/lib/index.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ export { + deserializeLegacyTemplateList, deserializeTemplateList, - deserializeV1Template, - serializeV1Template, + deserializeLegacyTemplate, + serializeLegacyTemplate, } from './template_serialization'; export { getTemplateParameter } from './utils'; diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts index 33a83d1e9335b..249881f668d9f 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts @@ -5,60 +5,34 @@ */ import { TemplateDeserialized, - TemplateV1Serialized, - TemplateV2Serialized, + LegacyTemplateSerialized, + TemplateSerialized, TemplateListItem, } from '../types'; const hasEntries = (data: object = {}) => Object.entries(data).length > 0; -export function serializeV1Template(template: TemplateDeserialized): TemplateV1Serialized { - const { - name, - version, - order, - indexPatterns, - template: { settings, aliases, mappings } = {} as TemplateDeserialized['template'], - } = template; +export function serializeTemplate(templateDeserialized: TemplateDeserialized): TemplateSerialized { + const { version, priority, indexPatterns, template, composedOf } = templateDeserialized; - const serializedTemplate: TemplateV1Serialized = { - name, + return { version, - order, + priority, + template, index_patterns: indexPatterns, - settings, - aliases, - mappings, - }; - - return serializedTemplate; -} - -export function serializeV2Template(template: TemplateDeserialized): TemplateV2Serialized { - const { aliases, mappings, settings, ...templateV1serialized } = serializeV1Template(template); - - return { - ...templateV1serialized, - template: { - aliases, - mappings, - settings, - }, - priority: template.priority, - composed_of: template.composedOf, + composed_of: composedOf, }; } -export function deserializeV2Template( - templateEs: TemplateV2Serialized, +export function deserializeTemplate( + templateEs: TemplateSerialized & { name: string }, managedTemplatePrefix?: string ): TemplateDeserialized { const { name, version, - order, index_patterns: indexPatterns, - template, + template = {}, priority, composed_of: composedOf, } = templateEs; @@ -67,49 +41,92 @@ export function deserializeV2Template( const deserializedTemplate: TemplateDeserialized = { name, version, - order, + priority, indexPatterns: indexPatterns.sort(), template, - ilmPolicy: settings && settings.index && settings.index.lifecycle, - isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)), - priority, + ilmPolicy: settings?.index?.lifecycle, composedOf, _kbnMeta: { - formatVersion: 2, + isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)), }, }; return deserializedTemplate; } -export function deserializeV1Template( - templateEs: TemplateV1Serialized, +export function deserializeTemplateList( + indexTemplates: Array<{ name: string; index_template: TemplateSerialized }>, + managedTemplatePrefix?: string +): TemplateListItem[] { + return indexTemplates.map(({ name, index_template: templateSerialized }) => { + const { + template: { mappings, settings, aliases }, + ...deserializedTemplate + } = deserializeTemplate({ name, ...templateSerialized }, managedTemplatePrefix); + + return { + ...deserializedTemplate, + hasSettings: hasEntries(settings), + hasAliases: hasEntries(aliases), + hasMappings: hasEntries(mappings), + }; + }); +} + +/** + * ------------------------------------------ + * --------- LEGACY INDEX TEMPLATES --------- + * ------------------------------------------ + */ + +export function serializeLegacyTemplate(template: TemplateDeserialized): LegacyTemplateSerialized { + const { + version, + order, + indexPatterns, + template: { settings, aliases, mappings }, + } = template; + + return { + version, + order, + index_patterns: indexPatterns, + settings, + aliases, + mappings, + }; +} + +export function deserializeLegacyTemplate( + templateEs: LegacyTemplateSerialized & { name: string }, managedTemplatePrefix?: string ): TemplateDeserialized { const { settings, aliases, mappings, ...rest } = templateEs; - const deserializedTemplateV2 = deserializeV2Template( + const deserializedTemplate = deserializeTemplate( { ...rest, template: { aliases, settings, mappings } }, managedTemplatePrefix ); return { - ...deserializedTemplateV2, + ...deserializedTemplate, + order: templateEs.order, _kbnMeta: { - formatVersion: 1, + ...deserializedTemplate._kbnMeta, + isLegacy: true, }, }; } -export function deserializeTemplateList( - indexTemplatesByName: { [key: string]: Omit }, +export function deserializeLegacyTemplateList( + indexTemplatesByName: { [key: string]: LegacyTemplateSerialized }, managedTemplatePrefix?: string ): TemplateListItem[] { return Object.entries(indexTemplatesByName).map(([name, templateSerialized]) => { const { template: { mappings, settings, aliases }, ...deserializedTemplate - } = deserializeV1Template({ name, ...templateSerialized }, managedTemplatePrefix); + } = deserializeLegacyTemplate({ name, ...templateSerialized }, managedTemplatePrefix); return { ...deserializedTemplate, diff --git a/x-pack/plugins/index_management/common/lib/utils.test.ts b/x-pack/plugins/index_management/common/lib/utils.test.ts index 221d1b009cede..056101061a82b 100644 --- a/x-pack/plugins/index_management/common/lib/utils.test.ts +++ b/x-pack/plugins/index_management/common/lib/utils.test.ts @@ -3,12 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { TemplateV1Serialized, TemplateV2Serialized } from '../types'; -import { getTemplateVersion } from './utils'; +import { LegacyTemplateSerialized, TemplateSerialized } from '../types'; +import { isLegacyTemplate } from './utils'; describe('utils', () => { - describe('getTemplateVersion', () => { - test('should detect v1 template', () => { + describe('isLegacyTemplate', () => { + test('should detect legacy template', () => { const template = { name: 'my_template', index_patterns: ['logs*'], @@ -16,10 +16,10 @@ describe('utils', () => { properties: {}, }, }; - expect(getTemplateVersion(template as TemplateV1Serialized)).toBe(1); + expect(isLegacyTemplate(template as LegacyTemplateSerialized)).toBe(true); }); - test('should detect v2 template', () => { + test('should detect composable template', () => { const template = { name: 'my_template', index_patterns: ['logs*'], @@ -29,7 +29,7 @@ describe('utils', () => { }, }, }; - expect(getTemplateVersion(template as TemplateV2Serialized)).toBe(2); + expect(isLegacyTemplate(template as TemplateSerialized)).toBe(false); }); }); }); diff --git a/x-pack/plugins/index_management/common/lib/utils.ts b/x-pack/plugins/index_management/common/lib/utils.ts index eee35dc1ab467..5a7db8ef50ab4 100644 --- a/x-pack/plugins/index_management/common/lib/utils.ts +++ b/x-pack/plugins/index_management/common/lib/utils.ts @@ -4,26 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TemplateDeserialized, TemplateV1Serialized, TemplateV2Serialized } from '../types'; +import { TemplateDeserialized, LegacyTemplateSerialized, TemplateSerialized } from '../types'; /** - * Helper to get the format version of an index template. - * v1 will be supported up until 9.x but marked as deprecated from 7.8 - * v2 will be supported from 7.8 + * Helper to know if a template has the legacy format or not + * legacy format will be supported up until 9.x but marked as deprecated from 7.8 + * new (composable) format is supported from 7.8 */ -export const getTemplateVersion = ( - template: TemplateDeserialized | TemplateV1Serialized | TemplateV2Serialized -): 1 | 2 => { - return {}.hasOwnProperty.call(template, 'template') ? 2 : 1; +export const isLegacyTemplate = ( + template: TemplateDeserialized | LegacyTemplateSerialized | TemplateSerialized +): boolean => { + return {}.hasOwnProperty.call(template, 'template') ? false : true; }; export const getTemplateParameter = ( - template: TemplateV1Serialized | TemplateV2Serialized, + template: LegacyTemplateSerialized | TemplateSerialized, setting: 'aliases' | 'settings' | 'mappings' ) => { - const formatVersion = getTemplateVersion(template); - - return formatVersion === 1 - ? (template as TemplateV1Serialized)[setting] - : (template as TemplateV2Serialized).template[setting]; + return isLegacyTemplate(template) + ? (template as LegacyTemplateSerialized)[setting] + : (template as TemplateSerialized).template[setting]; }; diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts index c37088982f207..f113aa44d058f 100644 --- a/x-pack/plugins/index_management/common/types/templates.ts +++ b/x-pack/plugins/index_management/common/types/templates.ts @@ -8,28 +8,45 @@ import { IndexSettings } from './indices'; import { Aliases } from './aliases'; import { Mappings } from './mappings'; -// Template serialized (from Elasticsearch) -interface TemplateBaseSerialized { - name: string; +/** + * Index template format from Elasticsearch + */ +export interface TemplateSerialized { index_patterns: string[]; + template: { + settings?: IndexSettings; + aliases?: Aliases; + mappings?: Mappings; + }; + composed_of?: string[]; version?: number; - order?: number; -} - -export interface TemplateV1Serialized extends TemplateBaseSerialized { - settings?: IndexSettings; - aliases?: Aliases; - mappings?: Mappings; + priority?: number; } -export interface TemplateV2Serialized extends TemplateBaseSerialized { +/** + * TemplateDeserialized is the format the UI will be working with, + * regardless if we are loading the new format (composable) index template, + * or the legacy one. Serialization is done server side. + */ +export interface TemplateDeserialized { + name: string; + indexPatterns: string[]; template: { settings?: IndexSettings; aliases?: Aliases; mappings?: Mappings; }; + composedOf?: string[]; // Used on composable index template + version?: number; priority?: number; - composed_of?: string[]; + order?: number; // Used on legacy index template + ilmPolicy?: { + name: string; + }; + _kbnMeta: { + isManaged: boolean; + isLegacy?: boolean; + }; } /** @@ -42,42 +59,30 @@ export interface TemplateListItem { indexPatterns: string[]; version?: number; order?: number; + priority?: number; hasSettings: boolean; hasAliases: boolean; hasMappings: boolean; ilmPolicy?: { name: string; }; - isManaged: boolean; _kbnMeta: { - formatVersion: IndexTemplateFormatVersion; + isManaged: boolean; + isLegacy?: boolean; }; } /** - * TemplateDeserialized falls back to index template V2 format - * The UI will only be dealing with this interface, conversion from and to V1 format - * is done server side. + * ------------------------------------------ + * --------- LEGACY INDEX TEMPLATES --------- + * ------------------------------------------ */ -export interface TemplateDeserialized { - name: string; - indexPatterns: string[]; - isManaged: boolean; - template: { - settings?: IndexSettings; - aliases?: Aliases; - mappings?: Mappings; - }; - _kbnMeta: { - formatVersion: IndexTemplateFormatVersion; - }; + +export interface LegacyTemplateSerialized { + index_patterns: string[]; version?: number; - priority?: number; + settings?: IndexSettings; + aliases?: Aliases; + mappings?: Mappings; order?: number; - ilmPolicy?: { - name: string; - }; - composedOf?: string[]; } - -export type IndexTemplateFormatVersion = 1 | 2; diff --git a/x-pack/plugins/index_management/public/application/app.tsx b/x-pack/plugins/index_management/public/application/app.tsx index 83997dd6ece18..10bbe3ced64da 100644 --- a/x-pack/plugins/index_management/public/application/app.tsx +++ b/x-pack/plugins/index_management/public/application/app.tsx @@ -5,8 +5,9 @@ */ import React, { useEffect } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; -import { BASE_PATH, UIM_APP_LOAD } from '../../common/constants'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; +import { UIM_APP_LOAD } from '../../common/constants'; import { IndexManagementHome } from './sections/home'; import { TemplateCreate } from './sections/template_create'; import { TemplateClone } from './sections/template_clone'; @@ -14,24 +15,24 @@ import { TemplateEdit } from './sections/template_edit'; import { useServices } from './app_context'; -export const App = () => { +export const App = ({ history }: { history: ScopedHistory }) => { const { uiMetricService } = useServices(); useEffect(() => uiMetricService.trackMetric('loaded', UIM_APP_LOAD), [uiMetricService]); return ( - + - + ); }; // Export this so we can test it with a different router. export const AppWithoutRouter = () => ( - - - - - + + + + + ); diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 2bb618ad8efce..84938de416941 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -5,6 +5,7 @@ */ import React, { createContext, useContext } from 'react'; +import { ScopedHistory } from 'kibana/public'; import { CoreStart } from '../../../../../src/core/public'; import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public'; @@ -17,6 +18,7 @@ const AppContext = createContext(undefined); export interface AppDependencies { core: { fatalErrors: CoreStart['fatalErrors']; + getUrlForApp: CoreStart['application']['getUrlForApp']; }; plugins: { usageCollection: UsageCollectionSetup; @@ -27,6 +29,7 @@ export interface AppDependencies { httpService: HttpService; notificationService: NotificationService; }; + history: ScopedHistory; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index e708bf1b4de66..581624e312206 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -370,7 +370,7 @@ describe.skip('Mappings editor: text datatype', () => { const subSelectOptions = indexAnalyzerSelects .at(1) .find('option') - .map(wrapper => wrapper.text()); + .map((wrapper) => wrapper.text()); expect(subSelectOptions).toEqual(customAnalyzers); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 28fe2bfdc0711..bef2d5c79be99 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -40,7 +40,7 @@ jest.mock('@elastic/eui', () => ({ { + onChange={(e) => { props.onChange(e.target.value); }} /> @@ -108,7 +108,7 @@ const createActions = (testBed: TestBed) => { ): Promise => { const fields = find( fieldName ? (`${fieldName}.fieldsList.fieldsListItem` as TestSubjects) : 'fieldsListItem' - ).map(wrapper => wrapper); // convert to Array for our for of loop below + ).map((wrapper) => wrapper); // convert to Array for our for of loop below for (const field of fields) { const { hasChildren, testSubjectField } = await expandField(field); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 07f72d7cbf54a..0743211a2b7bf 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -279,7 +279,7 @@ describe.skip('Mappings editor: core', () => { * Mapped fields */ // Test that root-level mappings "properties" are rendered as root-level "DOM tree items" - const fields = find('fieldsListItem.fieldName').map(item => item.text()); + const fields = find('fieldsListItem.fieldName').map((item) => item.text()); expect(fields).toEqual(Object.keys(defaultMappings.properties).sort()); /** diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index 99c6fc37c4219..19eec0f0a9f9d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -24,7 +24,7 @@ interface Props { const stringifyJson = (json: GenericObject) => Object.keys(json).length ? JSON.stringify(json, null, 2) : '{\n\n}'; -const formSerializer: SerializerFunc = formData => { +const formSerializer: SerializerFunc = (formData) => { const { dynamicMapping: { enabled: dynamicMappingsEnabled, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx index 9d777cdccf83d..c06340fd9ae14 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx @@ -26,7 +26,7 @@ const fieldPathComboBoxConfig = { type: FIELD_TYPES.COMBO_BOX, defaultValue: [], serializer: (options: ComboBoxOption[]): string[] => options.map(({ label }) => label), - deserializer: (values: string[]): ComboBoxOption[] => values.map(value => ({ label: value })), + deserializer: (values: string[]): ComboBoxOption[] => values.map((value) => ({ label: value })), }; export const configurationFormSchema: FormSchema = { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx index c1a2b195a3f57..05d871ccfac71 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx @@ -52,7 +52,7 @@ export const DynamicMappingSection = () => ( } > - {formData => { + {(formData) => { const { 'dynamicMapping.enabled': enabled, 'dynamicMapping.date_detection': dateDetection, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx index 4278598dfc7c1..d195f1abfc444 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx @@ -78,7 +78,7 @@ export const SourceFieldSection = () => { } )} selectedOptions={value as ComboBoxOption[]} - onChange={newValue => { + onChange={(newValue) => { setValue(newValue); }} onCreateOption={(searchValue: string) => { @@ -109,7 +109,7 @@ export const SourceFieldSection = () => { } )} selectedOptions={value as ComboBoxOption[]} - onChange={newValue => { + onChange={(newValue) => { setValue(newValue); }} onCreateOption={(searchValue: string) => { @@ -154,7 +154,7 @@ export const SourceFieldSection = () => { } > - {formData => { + {(formData) => { const { 'sourceField.enabled': enabled } = formData; if (enabled === undefined) { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields_header.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields_header.tsx index a4e746aa4037d..56c01510376be 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields_header.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields_header.tsx @@ -46,7 +46,7 @@ export const DocumentFieldsHeader = React.memo(({ searchValue, onSearchChange }: } )} value={searchValue} - onChange={e => { + onChange={(e) => { // Temporary fix until EUI fixes the contract // See my comment https://github.com/elastic/eui/pull/2723/files#r366725059 if (typeof e === 'string') { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx index 569af5d21cdb0..dc52a362008c6 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx @@ -50,7 +50,7 @@ const getCustomAnalyzers = (indexSettings: IndexSettings): SelectOption[] | unde // We wrap inside a try catch as the index settings are written in JSON // and who knows what the user has entered. try { - return Object.keys(settings.analysis!.analyzer).map(value => ({ value, text: value })); + return Object.keys(settings.analysis!.analyzer).map((value) => ({ value, text: value })); } catch { return undefined; } @@ -156,7 +156,7 @@ export const AnalyzerParameter = ({ return ( - {field => ( + {(field) => (
{ - const subscription = form.subscribe(updateData => { + const subscription = form.subscribe((updateData) => { const formData = updateData.data.raw; const value = formData.sub ? formData.sub : formData.main; onChange(value); @@ -102,7 +102,7 @@ export const AnalyzerParameterSelects = ({ - {field => renderSelect(field, options)} + {(field) => renderSelect(field, options)} {subOptions && ( @@ -115,7 +115,7 @@ export const AnalyzerParameterSelects = ({ label: subOptions.label, }} > - {field => renderSelect(field, subOptions.options)} + {(field) => renderSelect(field, subOptions.options)} )} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/dynamic_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/dynamic_parameter.tsx index 975a6cd9bba4f..1882802b27487 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/dynamic_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/dynamic_parameter.tsx @@ -79,7 +79,7 @@ export const DynamicParameter = ({ defaultToggleValue }: Props) => { formFieldPath="dynamic_toggle" defaultToggleValue={defaultToggleValue} > - {isOn => { + {(isOn) => { return isOn === false ? ( { const defaultValueArray = - defaultValue !== undefined ? defaultValue.split('||').map(value => ({ label: value })) : []; - const defaultValuesInOptions = defaultValueArray.filter(defaultFormat => + defaultValue !== undefined ? defaultValue.split('||').map((value) => ({ label: value })) : []; + const defaultValuesInOptions = defaultValueArray.filter((defaultFormat) => ALL_DATE_FORMAT_OPTIONS.includes(defaultFormat) ); @@ -57,7 +57,7 @@ export const FormatParameter = ({ defaultValue, defaultToggleValue }: Props) => defaultToggleValue={defaultToggleValue} > - {formatField => { + {(formatField) => { return ( )} options={comboBoxOptions} selectedOptions={formatField.value as ComboBoxOption[]} - onChange={value => { + onChange={(value) => { formatField.setValue(value); }} onCreateOption={(searchValue: string) => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx index 3e91e97eef618..59ed0f3a0a2e5 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx @@ -53,8 +53,6 @@ export const IndexParameter = ({ }, }} /> - ) : ( - undefined - )} + ) : undefined} ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx index 44c19c12db88b..ae5ffaae3552b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx @@ -70,7 +70,7 @@ export const PathParameter = ({ field, allFields }: Props) => { deserializer: getDeserializer(allFields), }} > - {pathField => { + {(pathField) => { const error = pathField.getErrorsMessages(); const isInvalid = error ? Boolean(error.length) : false; @@ -123,7 +123,7 @@ export const PathParameter = ({ field, allFields }: Props) => { singleSelection={{ asPlainText: true }} options={suggestedFields} selectedOptions={pathField.value as AliasOption[]} - onChange={value => pathField.setValue(value)} + onChange={(value) => pathField.setValue(value)} isClearable={false} fullWidth /> diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/subtype_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/subtype_parameter.tsx index f955c68a6c8d5..cfa8b60653d4c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/subtype_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/subtype_parameter.tsx @@ -47,8 +47,8 @@ export const SubTypeParameter = ({ // Field sub type (if any) const subTypeOptions = typeDefinition - .subTypes!.types.map(_subType => TYPE_DEFINITION[_subType]) - .map(_subType => ({ value: _subType.value, label: _subType.label })); + .subTypes!.types.map((_subType) => TYPE_DEFINITION[_subType]) + .map((_subType) => ({ value: _subType.value, label: _subType.label })); const defaultValueSubType = typeDefinition.subTypes!.types.includes(defaultValueType as SubType) ? defaultValueType // we use the default value provided @@ -64,7 +64,7 @@ export const SubTypeParameter = ({ label: typeDefinition.subTypes!.label, }} > - {subTypeField => { + {(subTypeField) => { return ( { defaultToggleValue={defaultToggleValue} > - {formData => ( + {(formData) => ( <> ( path="type" config={getFieldConfig('type')}> - {typeField => { + {(typeField) => { const error = typeField.getErrorsMessages(); const isInvalid = error ? Boolean(error.length) : false; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx index 718d00ea461c0..230e6615bc4a4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx @@ -51,7 +51,7 @@ export const CreateField = React.memo(function CreateFieldComponent({ }); useEffect(() => { - const subscription = form.subscribe(updatedFieldForm => { + const subscription = form.subscribe((updatedFieldForm) => { dispatch({ type: 'fieldForm.update', value: updatedFieldForm }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/delete_field_provider.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/delete_field_provider.tsx index 64ed3a6f87117..80e3e9bec605a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/delete_field_provider.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/delete_field_provider.tsx @@ -54,9 +54,9 @@ export const DeleteFieldProvider = ({ children }: Props) => { ); } - const deleteField: DeleteFieldFunc = field => { + const deleteField: DeleteFieldFunc = (field) => { const aliases = getAllDescendantAliases(field, fields) - .map(id => byId[id].path.join(' > ')) + .map((id) => byId[id].path.join(' > ')) .sort(); const hasAliases = Boolean(aliases.length); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx index 33c4a21775d69..e8e41955a5e80 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx @@ -64,7 +64,7 @@ export const EditField = React.memo(({ form, field, allFields, exitEdit }: Props return ( - {updateField => ( + {(updateField) => ( { }); useEffect(() => { - const subscription = form.subscribe(updatedFieldForm => { + const subscription = form.subscribe((updatedFieldForm) => { dispatch({ type: 'fieldForm.update', value: updatedFieldForm }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx index 1c079c8d5cf87..c0e68b082c310 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx @@ -100,7 +100,7 @@ export const EditFieldFormRow = React.memo( defaultValue: initialVisibleState, }} > - {field => { + {(field) => { return ( - {formData => { + {(formData) => { setIsContentVisible(formData[formFieldPath]); return renderContent(); }} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/update_field_provider.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/update_field_provider.tsx index 88e08bc7098cb..e31d12689e7e0 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/update_field_provider.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/update_field_provider.tsx @@ -62,7 +62,7 @@ export const UpdateFieldProvider = ({ children }: Props) => { setState({ isModalOpen: false }); }; - const updateField: UpdateFieldFunc = field => { + const updateField: UpdateFieldFunc = (field) => { const previousField = byId[field.id]; const willDeleteChildFields = (oldType: DataType, newType: DataType): boolean => { @@ -102,7 +102,7 @@ export const UpdateFieldProvider = ({ children }: Props) => { if (requiresConfirmation) { aliasesToDelete = aliasesOnFieldAndDescendants.filter( // We will only delete aliases that points to possible children, *NOT* the field itself - id => aliasesOnField.includes(id) === false + (id) => aliasesOnField.includes(id) === false ); } } @@ -112,7 +112,7 @@ export const UpdateFieldProvider = ({ children }: Props) => { isModalOpen: true, field, aliases: Boolean(aliasesToDelete.length) - ? aliasesToDelete.map(id => byId[id].path.join(' > ')).sort() + ? aliasesToDelete.map((id) => byId[id].path.join(' > ')).sort() : undefined, }); return; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx index 89af480d79a20..3d78205934eea 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx @@ -47,7 +47,7 @@ export const NumericType = ({ field }: Props) => { {/* scaling_factor */} - {formData => + {(formData) => formData.subType === 'scaled_float' ? ( { - {formData => + {(formData) => formData.subType === 'date_range' ? ( { - {formData => + {(formData) => formData.subType === 'date_range' ? ( ) : null diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx index 73032ad31461e..c4ed11097b609 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx @@ -182,7 +182,7 @@ export const TextType = React.memo(({ field }: Props) => { defaultToggleValue={getDefaultToggleValue('position_increment_gap', field.source)} > - {formData => { + {(formData) => { return ( <> - {deleteField => ( + {(deleteField) => (
@@ -172,7 +172,7 @@ export class FeatureProperties extends React.Component { return (
{getColumnName(col)}   @@ -69,7 +69,7 @@ export const Datatable = ({ datatable, perPage, paginate, showHeader }) => (
{getFormattedValue(row[getColumnName(col)], getColumnType(col))}
(this._node = node)} + ref={(node) => (this._node = node)} > {rows}
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js index 10c3711392fb3..c6db9cd96a429 100644 --- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js +++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js @@ -55,7 +55,7 @@ describe('FeatureProperties', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -74,7 +74,7 @@ describe('FeatureProperties', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -93,7 +93,7 @@ describe('FeatureProperties', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js index 8a1b556d21c1f..e5b97947602b0 100644 --- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js +++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js @@ -32,7 +32,7 @@ export class FeaturesTooltip extends React.Component { return null; } - _setCurrentFeature = feature => { + _setCurrentFeature = (feature) => { this.setState({ currentFeature: feature }); }; diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js index 9d4b8bf356dca..6a249ad7b3592 100644 --- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js +++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js @@ -64,7 +64,7 @@ export class TooltipHeader extends Component { countByLayerId.forEach((count, layerId) => { layers.push(this.props.findLayerById(layerId)); }); - const layerNamePromises = layers.map(layer => { + const layerNamePromises = layers.map((layer) => { return layer.getDisplayName(); }); const layerNames = await Promise.all(layerNamePromises); @@ -88,12 +88,12 @@ export class TooltipHeader extends Component { } }; - _onPageChange = pageNumber => { + _onPageChange = (pageNumber) => { this.setState({ pageNumber }); this.props.setCurrentFeature(this.state.filteredFeatures[pageNumber]); }; - _onLayerChange = e => { + _onLayerChange = (e) => { const newLayerId = e.target.value; if (this.state.selectedLayerId === newLayerId) { return; @@ -102,7 +102,7 @@ export class TooltipHeader extends Component { const filteredFeatures = newLayerId === ALL_LAYERS ? this.props.features - : this.props.features.filter(feature => { + : this.props.features.filter((feature) => { return feature.layerId === newLayerId; }); diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js index 329e52b3519cf..342817f8171f2 100644 --- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js +++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js @@ -23,7 +23,7 @@ class MockLayer { const defaultProps = { onClose: () => {}, isLocked: false, - findLayerById: id => { + findLayerById: (id) => { return new MockLayer(id); }, setCurrentFeature: () => {}, @@ -42,7 +42,7 @@ describe('TooltipHeader', () => { const component = shallow(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -56,7 +56,7 @@ describe('TooltipHeader', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -83,7 +83,7 @@ describe('TooltipHeader', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -97,7 +97,7 @@ describe('TooltipHeader', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -128,7 +128,7 @@ describe('TooltipHeader', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -142,7 +142,7 @@ describe('TooltipHeader', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index a69e06458a6a0..ac28a2d5d5a6d 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -57,7 +57,7 @@ export class DrawControl extends React.Component { } }, 256); - _onDraw = e => { + _onDraw = (e) => { if (!e.features.length) { return; } diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts b/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts index 30e3b9b46916b..b9d446d390ffb 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts +++ b/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts @@ -24,7 +24,7 @@ export async function getInitialView( return await new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition( // success callback - pos => { + (pos) => { resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude, diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/plugins/maps/public/connected_components/map/mb/index.js index a617ae92dea15..189d6bc1f0a43 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/index.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/index.js @@ -46,10 +46,10 @@ function mapStateToProps(state = {}) { function mapDispatchToProps(dispatch) { return { - extentChanged: e => { + extentChanged: (e) => { dispatch(mapExtentChanged(e)); }, - onMapReady: e => { + onMapReady: (e) => { dispatch(clearGoto()); dispatch(mapExtentChanged(e)); dispatch(mapReady()); diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js b/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js index 4774cdc556c24..376010f0df9ba 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js @@ -18,14 +18,14 @@ class MockMbMap { } moveLayer(mbLayerId, nextMbLayerId) { - const indexOfLayerToMove = this._style.layers.findIndex(layer => { + const indexOfLayerToMove = this._style.layers.findIndex((layer) => { return layer.id === mbLayerId; }); const layerToMove = this._style.layers[indexOfLayerToMove]; this._style.layers.splice(indexOfLayerToMove, 1); - const indexOfNextLayer = this._style.layers.findIndex(layer => { + const indexOfNextLayer = this._style.layers.findIndex((layer) => { return layer.id === nextMbLayerId; }); @@ -37,7 +37,7 @@ class MockMbMap { } removeLayer(layerId) { - const layerToRemove = this._style.layers.findIndex(layer => { + const layerToRemove = this._style.layers.findIndex((layer) => { return layer.id === layerId; }); this._style.layers.splice(layerToRemove, 1); @@ -65,13 +65,13 @@ class MockLayer { } ownsMbLayerId(mbLayerId) { - return this._mbLayerIdsToSource.some(mbLayerToSource => { + return this._mbLayerIdsToSource.some((mbLayerToSource) => { return mbLayerToSource.id === mbLayerId; }); } ownsMbSourceId(mbSourceId) { - return this._mbSourceIds.some(id => mbSourceId === id); + return this._mbSourceIds.some((id) => mbSourceId === id); } } @@ -81,8 +81,8 @@ function getMockStyle(orderedMockLayerList) { layers: [], }; - orderedMockLayerList.forEach(mockLayer => { - mockLayer.getMbSourceIds().forEach(mbSourceId => { + orderedMockLayerList.forEach((mockLayer) => { + mockLayer.getMbSourceIds().forEach((mbSourceId) => { mockStyle.sources[mbSourceId] = {}; }); mockLayer.getMbLayersIdsToSource().forEach(({ id, source }) => { diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js index 329d2b7fd2985..7c86d729577e2 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js @@ -47,7 +47,7 @@ export class TooltipControl extends React.Component { }; _getLayerByMbLayerId(mbLayerId) { - return this.props.layerList.find(layer => { + return this.props.layerList.find((layer) => { const mbLayerIds = layer.getMbLayerIds(); return mbLayerIds.indexOf(mbLayerId) > -1; }); @@ -81,7 +81,7 @@ export class TooltipControl extends React.Component { return uniqueFeatures; } - _lockTooltip = e => { + _lockTooltip = (e) => { if (this.props.isDrawingFilter) { // ignore click events when in draw mode return; @@ -105,7 +105,7 @@ export class TooltipControl extends React.Component { }); }; - _updateHoverTooltipState = _.debounce(e => { + _updateHoverTooltipState = _.debounce((e) => { if (this.props.isDrawingFilter || this.props.hasLockedTooltips) { // ignore hover events when in draw mode or when there are locked tooltips return; @@ -144,7 +144,7 @@ export class TooltipControl extends React.Component { //For example: //a vector or heatmap layer will not add a source and layer to the mapbox-map, until that data is available. //during that data-fetch window, the app should not query for layers that do not exist. - return mbLayerIds.filter(mbLayerId => { + return mbLayerIds.filter((mbLayerId) => { return !!this.props.mbMap.getLayer(mbLayerId); }); } diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js index 620d7cb9ff756..31964c3395417 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js @@ -37,7 +37,7 @@ const mockMBMap = { on: (eventName, callback) => { mockMbMapHandlers[eventName] = callback; }, - off: eventName => { + off: (eventName) => { delete mockMbMapHandlers[eventName]; }, getLayer: () => {}, diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js index 867c779bc4dba..03c2aeb2edd0a 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js @@ -97,13 +97,13 @@ export class TooltipPopover extends Component { return await tooltipLayer.getSource().getPreIndexedShape(targetFeature.properties); }; - _findLayerById = layerId => { - return this.props.layerList.find(layer => { + _findLayerById = (layerId) => { + return this.props.layerList.find((layer) => { return layer.getId() === layerId; }); }; - _getLayerName = async layerId => { + _getLayerName = async (layerId) => { const layer = this._findLayerById(layerId); if (!layer) { return null; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js index bcef03c205b2b..205ca7337277d 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js @@ -23,7 +23,7 @@ const layerId = 'tfi3f'; const mockMbMapHandlers = {}; const mockMBMap = { - project: lonLatArray => { + project: (lonLatArray) => { const lonDistanceFromCenter = Math.abs(lonLatArray[0] - mapCenter[0]); const latDistanceFromCenter = Math.abs(lonLatArray[1] - mapCenter[1]); return { @@ -34,7 +34,7 @@ const mockMBMap = { on: (eventName, callback) => { mockMbMapHandlers[eventName] = callback; }, - off: eventName => { + off: (eventName) => { delete mockMbMapHandlers[eventName]; }, getBounds: () => { @@ -95,7 +95,7 @@ describe('TooltipPopover', () => { const component = shallow( { + renderTooltipContent={(props) => { return
Custom tooltip content
; }} /> diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/plugins/maps/public/connected_components/map/mb/utils.js index adf109a087d27..a5934038f83df 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/utils.js @@ -11,20 +11,20 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLa const mbStyle = mbMap.getStyle(); const mbLayerIdsToRemove = []; - mbStyle.layers.forEach(mbLayer => { + mbStyle.layers.forEach((mbLayer) => { // ignore mapbox layers from spatial filter layer if (spatialFilterLayer.ownsMbLayerId(mbLayer.id)) { return; } - const layer = layerList.find(layer => { + const layer = layerList.find((layer) => { return layer.ownsMbLayerId(mbLayer.id); }); if (!layer) { mbLayerIdsToRemove.push(mbLayer.id); } }); - mbLayerIdsToRemove.forEach(mbLayerId => mbMap.removeLayer(mbLayerId)); + mbLayerIdsToRemove.forEach((mbLayerId) => mbMap.removeLayer(mbLayerId)); const mbSourcesToRemove = []; for (const mbSourceId in mbStyle.sources) { @@ -34,7 +34,7 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLa return; } - const layer = layerList.find(layer => { + const layer = layerList.find((layer) => { return layer.ownsMbSourceId(mbSourceId); }); if (!layer) { @@ -42,7 +42,7 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLa } } } - mbSourcesToRemove.forEach(mbSourceId => mbMap.removeSource(mbSourceId)); + mbSourcesToRemove.forEach((mbSourceId) => mbMap.removeSource(mbSourceId)); } export function moveLayerToTop(mbMap, layer) { @@ -52,7 +52,7 @@ export function moveLayerToTop(mbMap, layer) { return; } - layer.getMbLayerIds().forEach(mbLayerId => { + layer.getMbLayerIds().forEach((mbLayerId) => { const mbLayer = mbMap.getLayer(mbLayerId); if (mbLayer) { mbMap.moveLayer(mbLayerId); @@ -73,8 +73,8 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { const mbLayers = mbMap.getStyle().layers.slice(); const layerIds = []; - mbLayers.forEach(mbLayer => { - const layer = layerList.find(layer => layer.ownsMbLayerId(mbLayer.id)); + mbLayers.forEach((mbLayer) => { + const layer = layerList.find((layer) => layer.ownsMbLayerId(mbLayer.id)); if (layer) { layerIds.push(layer.getId()); } @@ -82,15 +82,15 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { const currentLayerOrderLayerIds = _.uniq(layerIds); - const newLayerOrderLayerIdsUnfiltered = layerList.map(l => l.getId()); - const newLayerOrderLayerIds = newLayerOrderLayerIdsUnfiltered.filter(layerId => + const newLayerOrderLayerIdsUnfiltered = layerList.map((l) => l.getId()); + const newLayerOrderLayerIds = newLayerOrderLayerIdsUnfiltered.filter((layerId) => currentLayerOrderLayerIds.includes(layerId) ); let netPos = 0; let netNeg = 0; const movementArr = currentLayerOrderLayerIds.reduce((accu, id, idx) => { - const movement = newLayerOrderLayerIds.findIndex(newOId => newOId === id) - idx; + const movement = newLayerOrderLayerIds.findIndex((newOId) => newOId === id) - idx; movement > 0 ? netPos++ : movement < 0 && netNeg++; accu.push({ id, movement }); return accu; @@ -99,9 +99,9 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { return; } const movedLayerId = - (netPos >= netNeg && movementArr.find(l => l.movement < 0).id) || - (netPos < netNeg && movementArr.find(l => l.movement > 0).id); - const nextLayerIdx = newLayerOrderLayerIds.findIndex(layerId => layerId === movedLayerId) + 1; + (netPos >= netNeg && movementArr.find((l) => l.movement < 0).id) || + (netPos < netNeg && movementArr.find((l) => l.movement > 0).id); + const nextLayerIdx = newLayerOrderLayerIds.findIndex((layerId) => layerId === movedLayerId) + 1; let nextMbLayerId; if (nextLayerIdx === newLayerOrderLayerIds.length) { @@ -109,13 +109,13 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { } else { const foundLayer = mbLayers.find(({ id: mbLayerId }) => { const layerId = newLayerOrderLayerIds[nextLayerIdx]; - const layer = layerList.find(layer => layer.getId() === layerId); + const layer = layerList.find((layer) => layer.getId() === layerId); return layer.ownsMbLayerId(mbLayerId); }); nextMbLayerId = foundLayer.id; } - const movedLayer = layerList.find(layer => layer.getId() === movedLayerId); + const movedLayer = layerList.find((layer) => layer.getId() === movedLayerId); mbLayers.forEach(({ id: mbLayerId }) => { if (movedLayer.ownsMbLayerId(mbLayerId)) { mbMap.moveLayer(mbLayerId, nextMbLayerId); @@ -156,11 +156,11 @@ export async function loadSpriteSheetImageData(imgUrl) { if (isCrossOriginUrl(imgUrl)) { image.crossOrigin = 'Anonymous'; } - image.onload = el => { + image.onload = (el) => { const imgData = getImageData(el.currentTarget); resolve(imgData); }; - image.onerror = e => { + image.onerror = (e) => { reject(e); }; image.src = imgUrl; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js index 800daec76989c..42235bfd5442e 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js @@ -113,16 +113,13 @@ export class MBMapContainer extends React.Component { async _createMbMapInstance() { const initialView = await getInitialView(this.props.goto, this.props.settings); - return new Promise(resolve => { + return new Promise((resolve) => { const mbStyle = { version: 8, sources: {}, layers: [], + glyphs: getGlyphUrl(), }; - const glyphUrl = getGlyphUrl(); - if (glyphUrl) { - mbStyle.glyphs = glyphUrl; - } const options = { attributionControl: false, @@ -151,7 +148,7 @@ export class MBMapContainer extends React.Component { } let emptyImage; - mbMap.on('styleimagemissing', e => { + mbMap.on('styleimagemissing', (e) => { if (emptyImage) { mbMap.addImage(e.id, emptyImage); } @@ -201,7 +198,7 @@ export class MBMapContainer extends React.Component { ); // Attach event only if view control is visible, which shows lat/lon if (!this.props.hideViewControl) { - const throttledSetMouseCoordinates = _.throttle(e => { + const throttledSetMouseCoordinates = _.throttle((e) => { this.props.setMouseCoordinates({ lat: e.lngLat.lat, lon: e.lngLat.lng, @@ -267,7 +264,7 @@ export class MBMapContainer extends React.Component { this.props.layerList, this.props.spatialFiltersLayer ); - this.props.layerList.forEach(layer => layer.syncLayerWithMB(this.state.mbMap)); + this.props.layerList.forEach((layer) => layer.syncLayerWithMB(this.state.mbMap)); syncLayerOrderForSingleLayer(this.state.mbMap, this.props.layerList); moveLayerToTop(this.state.mbMap, this.props.spatialFiltersLayer); }; diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts index 551fefb0ffcab..51bf0a519e380 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts @@ -20,7 +20,7 @@ function mapStateToProps(state: MapStoreState) { function mapDispatchToProps(dispatch: Dispatch) { return { fitToBounds: () => { - dispatch(fitToDataBounds()); + dispatch(fitToDataBounds()); }, }; } diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.js index 2c10728f78e5c..87b636da543fc 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.js +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.js @@ -49,15 +49,15 @@ export class SetViewControl extends Component { this.props.openSetView(); }; - _onLatChange = evt => { + _onLatChange = (evt) => { this._onChange('lat', evt); }; - _onLonChange = evt => { + _onLonChange = (evt) => { this._onChange('lon', evt); }; - _onZoomChange = evt => { + _onZoomChange = (evt) => { this._onChange('zoom', evt); }; diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.js index 7ed6f07a168ea..5812604a91b87 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.js @@ -17,7 +17,7 @@ function mapStateToProps(state = {}) { function mapDispatchToProps(dispatch) { return { - initiateDraw: options => { + initiateDraw: (options) => { dispatch(updateDrawState(options)); }, cancelDraw: () => { diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js index e7c125abe70c7..a06def086b861 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js @@ -52,7 +52,7 @@ export class ToolsControl extends Component { }; _togglePopover = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; @@ -61,7 +61,7 @@ export class ToolsControl extends Component { this.setState({ isPopoverOpen: false }); }; - _initiateShapeDraw = options => { + _initiateShapeDraw = (options) => { this.props.initiateDraw({ drawType: DRAW_TYPE.POLYGON, ...options, @@ -69,7 +69,7 @@ export class ToolsControl extends Component { this._closePopover(); }; - _initiateBoundsDraw = options => { + _initiateBoundsDraw = (options) => { this.props.initiateDraw({ drawType: DRAW_TYPE.BOUNDS, ...options, @@ -77,7 +77,7 @@ export class ToolsControl extends Component { this._closePopover(); }; - _initiateDistanceDraw = options => { + _initiateDistanceDraw = (options) => { this.props.initiateDraw({ drawType: DRAW_TYPE.DISTANCE, ...options, diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js index 8f11d1b23376c..0e5048a1e9190 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js @@ -28,7 +28,7 @@ export class AttributionControl extends React.Component { } _loadAttributions = async () => { - const attributionPromises = this.props.layerList.map(async layer => { + const attributionPromises = this.props.layerList.map(async (layer) => { try { return await layer.getAttributions(); } catch (error) { @@ -44,7 +44,7 @@ export class AttributionControl extends React.Component { for (let i = 0; i < attributions.length; i++) { for (let j = 0; j < attributions[i].length; j++) { const testAttr = attributions[i][j]; - const attr = uniqueAttributions.find(added => { + const attr = uniqueAttributions.find((added) => { return added.url === testAttr.url && added.label === testAttr.label; }); if (!attr) { diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.test.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.test.js index 7ec64a94e8f47..2e85e8ab792d3 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.test.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.test.js @@ -24,7 +24,7 @@ describe('AttributionControl', () => { const component = shallowWithIntl(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.js index 68e689d9c789e..fa7ddc4d31eaa 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.js @@ -11,7 +11,7 @@ import { getLayerList } from '../../../../selectors/map_selectors'; import { getIsReadOnly } from '../../../../selectors/ui_selectors'; const mapDispatchToProps = { - updateLayerOrder: newOrder => updateLayerOrder(newOrder), + updateLayerOrder: (newOrder) => updateLayerOrder(newOrder), }; function mapStateToProps(state = {}) { diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/view.test.js.snap index f1cb1a8864753..f711549d87316 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/view.test.js.snap +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/view.test.js.snap @@ -19,6 +19,7 @@ exports[`TOCEntry is rendered 1`] = ` "getId": [Function], "hasErrors": [Function], "hasLegendDetails": [Function], + "isPreviewLayer": [Function], "isVisible": [Function], "renderLegendDetails": [Function], "showAtZoomLevel": [Function], @@ -81,6 +82,7 @@ exports[`TOCEntry props Should shade background when not selected layer 1`] = ` "getId": [Function], "hasErrors": [Function], "hasLegendDetails": [Function], + "isPreviewLayer": [Function], "isVisible": [Function], "renderLegendDetails": [Function], "showAtZoomLevel": [Function], @@ -143,6 +145,7 @@ exports[`TOCEntry props Should shade background when selected layer 1`] = ` "getId": [Function], "hasErrors": [Function], "hasLegendDetails": [Function], + "isPreviewLayer": [Function], "isVisible": [Function], "renderLegendDetails": [Function], "showAtZoomLevel": [Function], @@ -205,6 +208,7 @@ exports[`TOCEntry props isReadOnly 1`] = ` "getId": [Function], "hasErrors": [Function], "hasLegendDetails": [Function], + "isPreviewLayer": [Function], "isVisible": [Function], "renderLegendDetails": [Function], "showAtZoomLevel": [Function], @@ -250,6 +254,7 @@ exports[`TOCEntry props should display layer details when isLegendDetailsOpen is "getId": [Function], "hasErrors": [Function], "hasLegendDetails": [Function], + "isPreviewLayer": [Function], "isVisible": [Function], "renderLegendDetails": [Function], "showAtZoomLevel": [Function], diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js index da4107e747ce6..324db648b02e3 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js @@ -19,7 +19,6 @@ import { } from '../../../../../selectors/ui_selectors'; import { setSelectedLayer, - removeTransientLayer, updateFlyout, hideTOCDetails, showTOCDetails, @@ -40,15 +39,14 @@ function mapStateToProps(state = {}, ownProps) { function mapDispatchToProps(dispatch) { return { - openLayerPanel: async layerId => { - await dispatch(removeTransientLayer()); + openLayerPanel: async (layerId) => { await dispatch(setSelectedLayer(layerId)); dispatch(updateFlyout(FLYOUT_STATE.LAYER_PANEL)); }, - hideTOCDetails: layerId => { + hideTOCDetails: (layerId) => { dispatch(hideTOCDetails(layerId)); }, - showTOCDetails: layerId => { + showTOCDetails: (layerId) => { dispatch(showTOCDetails(layerId)); }, }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts index 5a81ccf41d87c..17a6dbf02b878 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts @@ -28,16 +28,16 @@ function mapStateToProps(state: MapStoreState) { function mapDispatchToProps(dispatch: Dispatch) { return { cloneLayer: (layerId: string) => { - dispatch(cloneLayer(layerId)); + dispatch(cloneLayer(layerId)); }, fitToBounds: (layerId: string) => { - dispatch(fitToLayerExtent(layerId)); + dispatch(fitToLayerExtent(layerId)); }, removeLayer: (layerId: string) => { - dispatch(removeLayer(layerId)); + dispatch(removeLayer(layerId)); }, toggleVisible: (layerId: string) => { - dispatch(toggleLayerVisible(layerId)); + dispatch(toggleLayerVisible(layerId)); }, }; } diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx index 5eaba5330a3a7..c7ed5ec74ac7a 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx @@ -79,7 +79,7 @@ describe('TOCEntryActionsPopover', () => { const component = shallowWithIntl(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -92,7 +92,7 @@ describe('TOCEntryActionsPopover', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -104,7 +104,7 @@ describe('TOCEntryActionsPopover', () => { const component = shallowWithIntl(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index 344e96e511f2e..5baac0a474ffa 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -55,7 +55,7 @@ export class TOCEntryActionsPopover extends Component { } _togglePopover = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js index c0ce24fef9cd8..b17078ae37113 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js @@ -239,7 +239,8 @@ export class TOCEntry extends React.Component { 'mapTocEntry-isDragging': this.props.isDragging, 'mapTocEntry-isDraggingOver': this.props.isDraggingOver, 'mapTocEntry-isSelected': - this.props.selectedLayer && this.props.selectedLayer.getId() === this.props.layer.getId(), + this.props.layer.isPreviewLayer() || + (this.props.selectedLayer && this.props.selectedLayer.getId() === this.props.layer.getId()), }); return ( diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js index fa200660d8e4c..543be9395d0bc 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js @@ -21,6 +21,9 @@ const mockLayer = { getDisplayName: () => { return 'layer 1'; }, + isPreviewLayer: () => { + return false; + }, isVisible: () => { return true; }, @@ -51,7 +54,7 @@ describe('TOCEntry', () => { const component = shallowWithIntl(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -63,7 +66,7 @@ describe('TOCEntry', () => { const component = shallowWithIntl(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -74,7 +77,7 @@ describe('TOCEntry', () => { const component = shallowWithIntl(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -85,7 +88,7 @@ describe('TOCEntry', () => { const component = shallowWithIntl(); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); @@ -102,7 +105,7 @@ describe('TOCEntry', () => { ); // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); + await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.js index 49ccd503793e4..7414d810a8654 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.js @@ -28,7 +28,7 @@ export class LayerTOC extends React.Component { } // Layer list is displayed in reverse order so index needs to reversed to get back to original reference. - const reverseIndex = index => { + const reverseIndex = (index) => { return this.props.layerList.length - index - 1; }; @@ -49,7 +49,7 @@ export class LayerTOC extends React.Component { const reverseLayerList = [...this.props.layerList].reverse(); if (this.props.isReadOnly) { - return reverseLayerList.map(layer => { + return reverseLayerList.map((layer) => { return ; }); } diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/view.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/view.js index 180dc2e3933c3..9596a4df2cf17 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/view.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/view.js @@ -60,10 +60,10 @@ export function LayerControl({ isFlyoutOpen, }) { if (!isLayerTOCOpen) { - const hasErrors = layerList.some(layer => { + const hasErrors = layerList.some((layer) => { return layer.hasErrors(); }); - const isLoading = layerList.some(layer => { + const isLoading = layerList.some((layer) => { return layer.isLayerLoading(); }); diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js index 419b169138ef1..b89fda29554ee 100644 --- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js +++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js @@ -438,10 +438,10 @@ export function clamp(val, min, max) { export function extractFeaturesFromFilters(filters) { const features = []; filters - .filter(filter => { + .filter((filter) => { return filter.meta.key && filter.meta.type === SPATIAL_FILTER_TYPE; }) - .forEach(filter => { + .forEach((filter) => { let geometry; if (filter.geo_distance && filter.geo_distance[filter.meta.key]) { const distanceSplit = filter.geo_distance.distance.split('km'); diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js index c0baf5e4bcb6e..05f6a9eec5bd5 100644 --- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js +++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js @@ -24,7 +24,7 @@ import { indexPatterns } from '../../../../src/plugins/data/public'; const geoFieldName = 'location'; -const flattenHitMock = hit => { +const flattenHitMock = (hit) => { const properties = {}; for (const fieldName in hit._source) { if (hit._source.hasOwnProperty(fieldName)) { @@ -173,7 +173,7 @@ describe('hitsToGeoJson', () => { describe('dot in geoFieldName', () => { const indexPatternMock = { fields: { - getByName: name => { + getByName: (name) => { const fields = { ['my.location']: { type: 'geo_point', diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 01cbece32c64c..2da3205730638 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -55,6 +55,7 @@ import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_se import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; import { getUiActions, getCoreI18n } from '../kibana_services'; +import { LayerDescriptor } from '../../common/descriptor_types'; import { MapEmbeddableInput, MapEmbeddableConfig } from './types'; export { MapEmbeddableInput, MapEmbeddableConfig }; @@ -69,7 +70,7 @@ export class MapEmbeddable extends Embeddable this.onContainerStateChanged(input)); + this._subscription = this.getInput$().subscribe((input) => this.onContainerStateChanged(input)); } setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => { @@ -147,9 +148,9 @@ export class MapEmbeddable extends Embeddable( setQuery({ - filters: filters.filter(filter => !filter.meta.disabled), + filters: filters.filter((filter) => !filter.meta.disabled), query, timeFilters: timeRange, refresh, @@ -218,9 +219,9 @@ export class MapEmbeddable extends Embeddable(replaceLayerList(this._layerList)); if (this.input.hiddenLayers) { - this._store.dispatch(setHiddenLayers(this.input.hiddenLayers)); + this._store.dispatch(setHiddenLayers(this.input.hiddenLayers)); } this._dispatchSetQuery(this.input); this._dispatchSetRefreshConfig(this.input); @@ -248,9 +249,9 @@ export class MapEmbeddable extends Embeddable(replaceLayerList(this._layerList)); } addFilters = (filters: Filter[]) => { diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts index f33885c2a2462..c6103bcc73fc5 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -15,6 +15,7 @@ import { } from '../../../../../src/plugins/embeddable/public'; import '../index.scss'; import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; +import { LayerDescriptor } from '../../common/descriptor_types'; import { MapStore, MapStoreState } from '../reducers/store'; import { MapEmbeddableConfig, MapEmbeddableInput } from './types'; import { MapEmbeddableOutput } from './map_embeddable'; @@ -38,9 +39,12 @@ let getIndexPatternService: () => { let getHttp: () => any; let getMapsCapabilities: () => any; let createMapStore: () => MapStore; -let addLayerWithoutDataSync: (layerDescriptor: unknown) => AnyAction; +let addLayerWithoutDataSync: (layerDescriptor: LayerDescriptor) => AnyAction; let getQueryableUniqueIndexPatternIds: (state: MapStoreState) => string[]; -let getInitialLayers: (layerListJSON?: string, initialLayers?: unknown[]) => unknown[]; +let getInitialLayers: ( + layerListJSON?: string, + initialLayers?: LayerDescriptor[] +) => LayerDescriptor[]; let mergeInputWithSavedMap: any; async function waitForMapDependencies(): Promise { @@ -48,7 +52,7 @@ async function waitForMapDependencies(): Promise { return whenModulesLoadedPromise; } - whenModulesLoadedPromise = new Promise(async resolve => { + whenModulesLoadedPromise = new Promise(async (resolve) => { ({ // @ts-ignore getMapsSavedObjectLoader, @@ -94,12 +98,12 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { }); } - async _getIndexPatterns(layerList: unknown[]): Promise { + async _getIndexPatterns(layerList: LayerDescriptor[]): Promise { // Need to extract layerList from store to get queryable index pattern ids const store = createMapStore(); let queryableIndexPatternIds: string[]; try { - layerList.forEach((layerDescriptor: unknown) => { + layerList.forEach((layerDescriptor: LayerDescriptor) => { store.dispatch(addLayerWithoutDataSync(layerDescriptor)); }); queryableIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState()); @@ -111,7 +115,7 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { ); } - const promises = queryableIndexPatternIds.map(async indexPatternId => { + const promises = queryableIndexPatternIds.map(async (indexPatternId) => { try { // @ts-ignore return await getIndexPatternService().get(indexPatternId); diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index d287c431e50c2..fc614a4a6040c 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -9,14 +9,14 @@ import { MapSettings } from '../reducers/map'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { EmbeddableInput } from '../../../../../src/plugins/embeddable/public/lib/embeddables'; import { Filter, Query, RefreshInterval, TimeRange } from '../../../../../src/plugins/data/common'; -import { MapCenterAndZoom } from '../../common/descriptor_types'; +import { LayerDescriptor, MapCenterAndZoom } from '../../common/descriptor_types'; export interface MapEmbeddableConfig { editUrl?: string; indexPatterns: IIndexPattern[]; editable: boolean; title?: string; - layerList: unknown[]; + layerList: LayerDescriptor[]; settings?: MapSettings; } diff --git a/x-pack/plugins/maps/public/index_pattern_util.js b/x-pack/plugins/maps/public/index_pattern_util.js index bbea4a9e3ab2a..d695d1087a38d 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.js +++ b/x-pack/plugins/maps/public/index_pattern_util.js @@ -10,7 +10,7 @@ import { ES_GEO_FIELD_TYPE } from '../common/constants'; export async function getIndexPatternsFromIds(indexPatternIds = []) { const promises = []; - indexPatternIds.forEach(id => { + indexPatternIds.forEach((id) => { const indexPatternPromise = getIndexPatternService().get(id); if (indexPatternPromise) { promises.push(indexPatternPromise); @@ -21,7 +21,7 @@ export async function getIndexPatternsFromIds(indexPatternIds = []) { } export function getTermsFields(fields) { - return fields.filter(field => { + return fields.filter((field) => { return ( field.aggregatable && !indexPatterns.isNestedField(field) && @@ -48,7 +48,7 @@ export function supportsGeoTileAgg(field) { // Returns filtered fields list containing only fields that exist in _source. export function getSourceFields(fields) { - return fields.filter(field => { + return fields.filter((field) => { // Multi fields are not stored in _source and only exist in index. const isMultiField = field.subType && field.subType.multi; return !isMultiField && !indexPatterns.isNestedField(field); diff --git a/x-pack/plugins/maps/public/inspector/views/map_details.js b/x-pack/plugins/maps/public/inspector/views/map_details.js index e84cf51ed34c1..29553c75592ca 100644 --- a/x-pack/plugins/maps/public/inspector/views/map_details.js +++ b/x-pack/plugins/maps/public/inspector/views/map_details.js @@ -43,7 +43,7 @@ class MapDetails extends Component { selectedTabId: DETAILS_TAB_ID, }; - onSelectedTabChanged = id => { + onSelectedTabChanged = (id) => { this.setState({ selectedTabId: id, }); diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js index 4e874d45bf128..ba7be7a3b2464 100644 --- a/x-pack/plugins/maps/public/kibana_services.js +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -10,51 +10,52 @@ export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; const { getRequestInspectorStats, getResponseInspectorStats } = search; let indexPatternService; -export const setIndexPatternService = dataIndexPatterns => +export const setIndexPatternService = (dataIndexPatterns) => (indexPatternService = dataIndexPatterns); export const getIndexPatternService = () => indexPatternService; let autocompleteService; -export const setAutocompleteService = dataAutoComplete => (autocompleteService = dataAutoComplete); +export const setAutocompleteService = (dataAutoComplete) => + (autocompleteService = dataAutoComplete); export const getAutocompleteService = () => autocompleteService; let licenseId; -export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); +export const setLicenseId = (latestLicenseId) => (licenseId = latestLicenseId); export const getLicenseId = () => { return licenseId; }; let inspector; -export const setInspector = newInspector => (inspector = newInspector); +export const setInspector = (newInspector) => (inspector = newInspector); export const getInspector = () => { return inspector; }; let fileUploadPlugin; -export const setFileUpload = fileUpload => (fileUploadPlugin = fileUpload); +export const setFileUpload = (fileUpload) => (fileUploadPlugin = fileUpload); export const getFileUploadComponent = () => { return fileUploadPlugin.JsonUploadAndParse; }; let uiSettings; -export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); +export const setUiSettings = (coreUiSettings) => (uiSettings = coreUiSettings); export const getUiSettings = () => uiSettings; let indexPatternSelectComponent; -export const setIndexPatternSelect = indexPatternSelect => +export const setIndexPatternSelect = (indexPatternSelect) => (indexPatternSelectComponent = indexPatternSelect); export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; let coreHttp; -export const setHttp = http => (coreHttp = http); +export const setHttp = (http) => (coreHttp = http); export const getHttp = () => coreHttp; let dataTimeFilter; -export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); +export const setTimeFilter = (timeFilter) => (dataTimeFilter = timeFilter); export const getTimeFilter = () => dataTimeFilter; let toast; -export const setToasts = notificationToast => (toast = notificationToast); +export const setToasts = (notificationToast) => (toast = notificationToast); export const getToasts = () => toast; export async function fetchSearchSourceAndRecordWithInspector({ @@ -72,7 +73,7 @@ export async function fetchSearchSourceAndRecordWithInspector({ let resp; try { inspectorRequest.stats(getRequestInspectorStats(searchSource)); - searchSource.getSearchRequestBody().then(body => { + searchSource.getSearchRequestBody().then((body) => { inspectorRequest.json(body); }); resp = await searchSource.fetch({ abortSignal }); @@ -86,62 +87,62 @@ export async function fetchSearchSourceAndRecordWithInspector({ } let savedObjectsClient; -export const setSavedObjectsClient = coreSavedObjectsClient => +export const setSavedObjectsClient = (coreSavedObjectsClient) => (savedObjectsClient = coreSavedObjectsClient); export const getSavedObjectsClient = () => savedObjectsClient; let chrome; -export const setCoreChrome = coreChrome => (chrome = coreChrome); +export const setCoreChrome = (coreChrome) => (chrome = coreChrome); export const getCoreChrome = () => chrome; let mapsCapabilities; -export const setMapsCapabilities = coreAppMapsCapabilities => +export const setMapsCapabilities = (coreAppMapsCapabilities) => (mapsCapabilities = coreAppMapsCapabilities); export const getMapsCapabilities = () => mapsCapabilities; let visualizations; -export const setVisualizations = visPlugin => (visualizations = visPlugin); +export const setVisualizations = (visPlugin) => (visualizations = visPlugin); export const getVisualizations = () => visualizations; let docLinks; -export const setDocLinks = coreDocLinks => (docLinks = coreDocLinks); +export const setDocLinks = (coreDocLinks) => (docLinks = coreDocLinks); export const getDocLinks = () => docLinks; let overlays; -export const setCoreOverlays = coreOverlays => (overlays = coreOverlays); +export const setCoreOverlays = (coreOverlays) => (overlays = coreOverlays); export const getCoreOverlays = () => overlays; let data; -export const setData = dataPlugin => (data = dataPlugin); +export const setData = (dataPlugin) => (data = dataPlugin); export const getData = () => data; let uiActions; -export const setUiActions = pluginUiActions => (uiActions = pluginUiActions); +export const setUiActions = (pluginUiActions) => (uiActions = pluginUiActions); export const getUiActions = () => uiActions; let core; -export const setCore = kibanaCore => (core = kibanaCore); +export const setCore = (kibanaCore) => (core = kibanaCore); export const getCore = () => core; let navigation; -export const setNavigation = pluginNavigation => (navigation = pluginNavigation); +export const setNavigation = (pluginNavigation) => (navigation = pluginNavigation); export const getNavigation = () => navigation; let coreI18n; -export const setCoreI18n = kibanaCoreI18n => (coreI18n = kibanaCoreI18n); +export const setCoreI18n = (kibanaCoreI18n) => (coreI18n = kibanaCoreI18n); export const getCoreI18n = () => coreI18n; let dataSearchService; -export const setSearchService = searchService => (dataSearchService = searchService); +export const setSearchService = (searchService) => (dataSearchService = searchService); export const getSearchService = () => dataSearchService; let kibanaVersion; -export const setKibanaVersion = version => (kibanaVersion = version); +export const setKibanaVersion = (version) => (kibanaVersion = version); export const getKibanaVersion = () => kibanaVersion; // xpack.maps.* kibana.yml settings from this plugin let mapAppConfig; -export const setMapAppConfig = config => (mapAppConfig = config); +export const setMapAppConfig = (config) => (mapAppConfig = config); export const getMapAppConfig = () => mapAppConfig; export const getEnabled = () => getMapAppConfig().enabled; @@ -152,7 +153,7 @@ export const getEnableVectorTiles = () => getMapAppConfig().enableVectorTiles; // map.* kibana.yml settings from maps_legacy plugin that are shared between OSS map visualizations and maps app let kibanaCommonConfig; -export const setKibanaCommonConfig = config => (kibanaCommonConfig = config); +export const setKibanaCommonConfig = (config) => (kibanaCommonConfig = config); export const getKibanaCommonConfig = () => kibanaCommonConfig; export const getIsEmsEnabled = () => getKibanaCommonConfig().includeElasticMapsService; diff --git a/x-pack/plugins/maps/public/meta.js b/x-pack/plugins/maps/public/meta.js index 3ffd0578796ce..46c5e5cda3617 100644 --- a/x-pack/plugins/maps/public/meta.js +++ b/x-pack/plugins/maps/public/meta.js @@ -5,15 +5,7 @@ */ import { - GIS_API_PATH, - EMS_FILES_CATALOGUE_PATH, - EMS_TILES_CATALOGUE_PATH, - EMS_GLYPHS_PATH, - EMS_APP_NAME, -} from '../common/constants'; -import { i18n } from '@kbn/i18n'; -import { EMSClient } from '@elastic/ems-client'; -import { + getHttp, getLicenseId, getIsEmsEnabled, getRegionmapLayers, @@ -25,6 +17,17 @@ import { getProxyElasticMapsServiceInMaps, getKibanaVersion, } from './kibana_services'; +import { + GIS_API_PATH, + EMS_FILES_CATALOGUE_PATH, + EMS_TILES_CATALOGUE_PATH, + EMS_GLYPHS_PATH, + EMS_APP_NAME, + FONTS_API_PATH, +} from '../common/constants'; +import { i18n } from '@kbn/i18n'; +import { EMSClient } from '@elastic/ems-client'; + import fetch from 'node-fetch'; const GIS_API_RELATIVE = `../${GIS_API_PATH}`; @@ -95,7 +98,7 @@ export function getEMSClient() { export function getGlyphUrl() { if (!getIsEmsEnabled()) { - return ''; + return getHttp().basePath.prepend(`/${FONTS_API_PATH}/{fontstack}/{range}`); } return getProxyElasticMapsServiceInMaps() ? relativeToAbsolute(`../${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}/${EMS_GLYPHS_PATH}`) + diff --git a/x-pack/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js index 24dc65e9fc71c..5c04a57c00058 100644 --- a/x-pack/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/meta.test.js @@ -5,7 +5,7 @@ */ import { EMSClient } from '@elastic/ems-client'; -import { getEMSClient } from './meta'; +import { getEMSClient, getGlyphUrl } from './meta'; jest.mock('@elastic/ems-client'); @@ -22,10 +22,56 @@ describe('default use without proxy', () => { require('./kibana_services').getEmsLandingPageUrl = () => 'http://test.com'; }); - it('should construct EMSClient with absolute file and tile API urls', async () => { + test('should construct EMSClient with absolute file and tile API urls', async () => { getEMSClient(); const mockEmsClientCall = EMSClient.mock.calls[0]; expect(mockEmsClientCall[0].fileApiUrl.startsWith('https://file-api')).toBe(true); expect(mockEmsClientCall[0].tileApiUrl.startsWith('https://tile-api')).toBe(true); }); }); + +describe('getGlyphUrl', () => { + describe('EMS enabled', () => { + const EMS_FONTS_URL_MOCK = 'ems/fonts'; + beforeAll(() => { + require('./kibana_services').getIsEmsEnabled = () => true; + require('./kibana_services').getEmsFontLibraryUrl = () => EMS_FONTS_URL_MOCK; + }); + + describe('EMS proxy enabled', () => { + beforeAll(() => { + require('./kibana_services').getProxyElasticMapsServiceInMaps = () => true; + }); + + test('should return proxied EMS fonts URL', async () => { + expect(getGlyphUrl()).toBe('http://localhost/api/maps/ems/tiles/fonts/{fontstack}/{range}'); + }); + }); + + describe('EMS proxy disabled', () => { + beforeAll(() => { + require('./kibana_services').getProxyElasticMapsServiceInMaps = () => false; + }); + + test('should return EMS fonts URL', async () => { + expect(getGlyphUrl()).toBe(EMS_FONTS_URL_MOCK); + }); + }); + }); + + describe('EMS disabled', () => { + beforeAll(() => { + const mockHttp = { + basePath: { + prepend: (path) => `abc${path}`, + }, + }; + require('./kibana_services').getHttp = () => mockHttp; + require('./kibana_services').getIsEmsEnabled = () => false; + }); + + test('should return kibana fonts URL', async () => { + expect(getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/reducers/map.d.ts b/x-pack/plugins/maps/public/reducers/map.d.ts index 8fc655b2c837a..33794fcf8657d 100644 --- a/x-pack/plugins/maps/public/reducers/map.d.ts +++ b/x-pack/plugins/maps/public/reducers/map.d.ts @@ -66,7 +66,6 @@ export type MapState = { openTooltips: TooltipState[]; mapState: MapContext; selectedLayerId: string | null; - __transientLayerId: string | null; layerList: LayerDescriptor[]; waitingForMapReadyLayerList: LayerDescriptor[]; settings: MapSettings; diff --git a/x-pack/plugins/maps/public/reducers/map.js b/x-pack/plugins/maps/public/reducers/map.js index d0f4b07a2d90d..317c11eb7680c 100644 --- a/x-pack/plugins/maps/public/reducers/map.js +++ b/x-pack/plugins/maps/public/reducers/map.js @@ -6,7 +6,6 @@ import { SET_SELECTED_LAYER, - SET_TRANSIENT_LAYER, UPDATE_LAYER_ORDER, LAYER_DATA_LOAD_STARTED, LAYER_DATA_LOAD_ENDED, @@ -54,7 +53,7 @@ import { import { getDefaultMapSettings } from './default_map_settings'; import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from './util'; -import { SOURCE_DATA_ID_ORIGIN } from '../../common/constants'; +import { SOURCE_DATA_REQUEST_ID } from '../../common/constants'; const getLayerIndex = (list, layerId) => list.findIndex(({ id }) => layerId === id); @@ -126,7 +125,6 @@ export const DEFAULT_MAP_STATE = { hideViewControl: false, }, selectedLayerId: null, - __transientLayerId: null, layerList: [], waitingForMapReadyLayerList: [], settings: getDefaultMapSettings(), @@ -283,15 +281,12 @@ export function map(state = DEFAULT_MAP_STATE, action) { }, }; case SET_SELECTED_LAYER: - const selectedMatch = state.layerList.find(layer => layer.id === action.selectedLayerId); + const selectedMatch = state.layerList.find((layer) => layer.id === action.selectedLayerId); return { ...state, selectedLayerId: selectedMatch ? action.selectedLayerId : null }; - case SET_TRANSIENT_LAYER: - const transientMatch = state.layerList.find(layer => layer.id === action.transientLayerId); - return { ...state, __transientLayerId: transientMatch ? action.transientLayerId : null }; case UPDATE_LAYER_ORDER: return { ...state, - layerList: action.newLayerOrder.map(layerNumber => state.layerList[layerNumber]), + layerList: action.newLayerOrder.map((layerNumber) => state.layerList[layerNumber]), }; case UPDATE_LAYER_PROP: return updateLayerInList(state, action.id, action.propName, action.newValue); @@ -299,12 +294,12 @@ export function map(state = DEFAULT_MAP_STATE, action) { return updateLayerSourceDescriptorProp(state, action.layerId, action.propName, action.value); case SET_JOINS: const layerDescriptor = state.layerList.find( - descriptor => descriptor.id === action.layer.getId() + (descriptor) => descriptor.id === action.layer.getId() ); if (layerDescriptor) { const newLayerDescriptor = { ...layerDescriptor, joins: action.joins.slice() }; const index = state.layerList.findIndex( - descriptor => descriptor.id === action.layer.getId() + (descriptor) => descriptor.id === action.layer.getId() ); const newLayerList = state.layerList.slice(); newLayerList[index] = newLayerDescriptor; @@ -403,7 +398,7 @@ export function map(state = DEFAULT_MAP_STATE, action) { case SET_WAITING_FOR_READY_HIDDEN_LAYERS: return { ...state, - waitingForMapReadyLayerList: state.waitingForMapReadyLayerList.map(layer => ({ + waitingForMapReadyLayerList: state.waitingForMapReadyLayerList.map((layer) => ({ ...layer, visible: !action.hiddenLayerIds.includes(layer.id), })), @@ -418,7 +413,7 @@ function findDataRequest(layerDescriptor, dataRequestAction) { return; } - return layerDescriptor.__dataRequests.find(dataRequest => { + return layerDescriptor.__dataRequests.find((dataRequest) => { return dataRequest.dataId === dataRequestAction.dataId; }); } @@ -447,8 +442,8 @@ function updateSourceDataRequest(state, action) { if (!layerDescriptor) { return state; } - const dataRequest = layerDescriptor.__dataRequests.find(dataRequest => { - return dataRequest.dataId === SOURCE_DATA_ID_ORIGIN; + const dataRequest = layerDescriptor.__dataRequests.find((dataRequest) => { + return dataRequest.dataId === SOURCE_DATA_REQUEST_ID; }); if (!dataRequest) { return state; @@ -517,7 +512,7 @@ function getValidDataRequest(state, action, checkRequestToken = true) { } function findLayerById(state, id) { - return state.layerList.find(layer => layer.id === id); + return state.layerList.find((layer) => layer.id === id); } function trackCurrentLayerState(state, layerId) { diff --git a/x-pack/plugins/maps/public/reducers/non_serializable_instances.js b/x-pack/plugins/maps/public/reducers/non_serializable_instances.js index bbefe09bb6e43..e567a62c0c7b6 100644 --- a/x-pack/plugins/maps/public/reducers/non_serializable_instances.js +++ b/x-pack/plugins/maps/public/reducers/non_serializable_instances.js @@ -76,14 +76,14 @@ export const registerCancelCallback = (requestToken, callback) => { }; }; -export const unregisterCancelCallback = requestToken => { +export const unregisterCancelCallback = (requestToken) => { return { type: UNREGISTER_CANCEL_CALLBACK, requestToken, }; }; -export const cancelRequest = requestToken => { +export const cancelRequest = (requestToken) => { return (dispatch, getState) => { if (!requestToken) { return; diff --git a/x-pack/plugins/maps/public/reducers/ui.ts b/x-pack/plugins/maps/public/reducers/ui.ts index 5f532fea018ea..ff521c92568b3 100644 --- a/x-pack/plugins/maps/public/reducers/ui.ts +++ b/x-pack/plugins/maps/public/reducers/ui.ts @@ -81,7 +81,7 @@ export function ui(state: MapUiState = DEFAULT_MAP_UI_STATE, action: any) { case HIDE_TOC_DETAILS: return { ...state, - openTOCDetails: state.openTOCDetails.filter(layerId => { + openTOCDetails: state.openTOCDetails.filter((layerId) => { return layerId !== action.layerId; }), }; diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index b0e1400d513fe..467f1074e88e7 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -26,7 +26,7 @@ import { getSourceByType } from '../classes/sources/source_registry'; import { GeojsonFileSource } from '../classes/sources/client_file_source'; import { LAYER_TYPE, - SOURCE_DATA_ID_ORIGIN, + SOURCE_DATA_REQUEST_ID, STYLE_TYPE, VECTOR_STYLES, SPATIAL_FILTERS_LAYER_ID, @@ -66,7 +66,7 @@ function createLayerInstance( const joins: IJoin[] = []; const vectorLayerDescriptor = layerDescriptor as VectorLayerDescriptor; if (vectorLayerDescriptor.joins) { - vectorLayerDescriptor.joins.forEach(joinDescriptor => { + vectorLayerDescriptor.joins.forEach((joinDescriptor) => { const join = new InnerJoin(joinDescriptor, source); joins.push(join); }); @@ -137,9 +137,6 @@ export const getSelectedLayerId = ({ map }: MapStoreState): string | null => { return !map.selectedLayerId || !map.layerList ? null : map.selectedLayerId; }; -export const getTransientLayerId = ({ map }: MapStoreState): string | null => - map.__transientLayerId; - export const getLayerListRaw = ({ map }: MapStoreState): LayerDescriptor[] => map.layerList ? map.layerList : []; @@ -183,7 +180,7 @@ export const getQuery = ({ map }: MapStoreState): MapQuery | undefined => map.ma export const getFilters = ({ map }: MapStoreState): Filter[] => map.mapState.filters; export const isUsingSearch = (state: MapStoreState): boolean => { - const filters = getFilters(state).filter(filter => !filter.meta.disabled); + const filters = getFilters(state).filter((filter) => !filter.meta.disabled); const queryString = _.get(getQuery(state), 'query', ''); return !!filters.length || !!queryString.length; }; @@ -212,7 +209,7 @@ export const getRefreshTimerLastTriggeredAt = ({ map }: MapStoreState): string | function getLayerDescriptor(state: MapStoreState, layerId: string) { const layerListRaw = getLayerListRaw(state); - return layerListRaw.find(layer => layer.id === layerId); + return layerListRaw.find((layer) => layer.id === layerId); } export function getDataRequestDescriptor(state: MapStoreState, layerId: string, dataId: string) { @@ -266,7 +263,7 @@ export const getSpatialFiltersLayer = createSelector( alpha: settings.spatialFiltersAlpa, __dataRequests: [ { - dataId: SOURCE_DATA_ID_ORIGIN, + dataId: SOURCE_DATA_REQUEST_ID, data: featureCollection, }, ], @@ -294,20 +291,20 @@ export const getLayerList = createSelector( getLayerListRaw, getInspectorAdapters, (layerDescriptorList, inspectorAdapters) => { - return layerDescriptorList.map(layerDescriptor => + return layerDescriptorList.map((layerDescriptor) => createLayerInstance(layerDescriptor, inspectorAdapters) ); } ); -export function getLayerById(layerId: string, state: MapStoreState): ILayer | undefined { - return getLayerList(state).find(layer => { +export function getLayerById(layerId: string | null, state: MapStoreState): ILayer | undefined { + return getLayerList(state).find((layer) => { return layerId === layer.getId(); }); } -export const getFittableLayers = createSelector(getLayerList, layerList => { - return layerList.filter(layer => { +export const getFittableLayers = createSelector(getLayerList, (layerList) => { + return layerList.filter((layer) => { // These are the only layer-types that implement bounding-box retrieval reliably // This will _not_ work if Maps will allow register custom layer types const isFittable = @@ -319,33 +316,46 @@ export const getFittableLayers = createSelector(getLayerList, layerList => { }); }); -export const getHiddenLayerIds = createSelector(getLayerListRaw, layers => - layers.filter(layer => !layer.visible).map(layer => layer.id) +export const getHiddenLayerIds = createSelector(getLayerListRaw, (layers) => + layers.filter((layer) => !layer.visible).map((layer) => layer.id) ); export const getSelectedLayer = createSelector( getSelectedLayerId, getLayerList, (selectedLayerId, layerList) => { - return layerList.find(layer => layer.getId() === selectedLayerId); + return layerList.find((layer) => layer.getId() === selectedLayerId); } ); -export const getMapColors = createSelector( - getTransientLayerId, - getLayerListRaw, - (transientLayerId, layerList) => - layerList.reduce((accu: string[], layer: LayerDescriptor) => { - if (layer.id === transientLayerId) { - return accu; - } - const color: string | undefined = _.get(layer, 'style.properties.fillColor.options.color'); +export const hasPreviewLayers = createSelector(getLayerList, (layerList) => { + return layerList.some((layer) => { + return layer.isPreviewLayer(); + }); +}); + +export const isLoadingPreviewLayers = createSelector(getLayerList, (layerList) => { + return layerList.some((layer) => { + return layer.isPreviewLayer() && layer.isLayerLoading(); + }); +}); + +export const getMapColors = createSelector(getLayerListRaw, (layerList) => + layerList + .filter((layerDescriptor) => { + return !layerDescriptor.__isPreviewLayer; + }) + .reduce((accu: string[], layerDescriptor: LayerDescriptor) => { + const color: string | undefined = _.get( + layerDescriptor, + 'style.properties.fillColor.options.color' + ); if (color) accu.push(color); return accu; }, []) ); -export const getSelectedLayerJoinDescriptors = createSelector(getSelectedLayer, selectedLayer => { +export const getSelectedLayerJoinDescriptors = createSelector(getSelectedLayer, (selectedLayer) => { if (!selectedLayer || !('getJoins' in selectedLayer)) { return []; } @@ -356,41 +366,37 @@ export const getSelectedLayerJoinDescriptors = createSelector(getSelectedLayer, }); // Get list of unique index patterns used by all layers -export const getUniqueIndexPatternIds = createSelector(getLayerList, layerList => { +export const getUniqueIndexPatternIds = createSelector(getLayerList, (layerList) => { const indexPatternIds: string[] = []; - layerList.forEach(layer => { + layerList.forEach((layer) => { indexPatternIds.push(...layer.getIndexPatternIds()); }); return _.uniq(indexPatternIds).sort(); }); // Get list of unique index patterns, excluding index patterns from layers that disable applyGlobalQuery -export const getQueryableUniqueIndexPatternIds = createSelector(getLayerList, layerList => { +export const getQueryableUniqueIndexPatternIds = createSelector(getLayerList, (layerList) => { const indexPatternIds: string[] = []; - layerList.forEach(layer => { + layerList.forEach((layer) => { indexPatternIds.push(...layer.getQueryableIndexPatternIds()); }); return _.uniq(indexPatternIds); }); -export const hasDirtyState = createSelector( - getLayerListRaw, - getTransientLayerId, - (layerListRaw, transientLayerId) => { - if (transientLayerId) { +export const hasDirtyState = createSelector(getLayerListRaw, (layerListRaw) => { + return layerListRaw.some((layerDescriptor) => { + if (layerDescriptor.__isPreviewLayer) { return true; } - return layerListRaw.some(layerDescriptor => { - const trackedState = layerDescriptor[TRACKED_LAYER_DESCRIPTOR]; - if (!trackedState) { - return false; - } - const currentState = copyPersistentState(layerDescriptor); - return !_.isEqual(currentState, trackedState); - }); - } -); + const trackedState = layerDescriptor[TRACKED_LAYER_DESCRIPTOR]; + if (!trackedState) { + return false; + } + const currentState = copyPersistentState(layerDescriptor); + return !_.isEqual(currentState, trackedState); + }); +}); export const areLayersLoaded = createSelector( getLayerList, diff --git a/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf b/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf new file mode 100644 index 0000000000000..ab811ae10a2e7 Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf b/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf new file mode 100644 index 0000000000000..7cda8da1d0388 Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf b/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf new file mode 100644 index 0000000000000..6e108e53a26f8 Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf b/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf new file mode 100644 index 0000000000000..a3efbb9361d4d Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/8192-8447.pbf b/x-pack/plugins/maps/server/fonts/open_sans/8192-8447.pbf new file mode 100644 index 0000000000000..e053cb51c438c Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/8192-8447.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/license.txt b/x-pack/plugins/maps/server/fonts/open_sans/license.txt new file mode 100644 index 0000000000000..7783de532a331 --- /dev/null +++ b/x-pack/plugins/maps/server/fonts/open_sans/license.txt @@ -0,0 +1,53 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/x-pack/plugins/maps/server/lib/get_index_pattern_settings.js b/x-pack/plugins/maps/server/lib/get_index_pattern_settings.js index 066996ef8a6b9..27b6ff0fc6a51 100644 --- a/x-pack/plugins/maps/server/lib/get_index_pattern_settings.js +++ b/x-pack/plugins/maps/server/lib/get_index_pattern_settings.js @@ -10,7 +10,7 @@ import { DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_INNER_RESULT_WINDOW } from '../. export function getIndexPatternSettings(indicesSettingsResp) { let maxResultWindow = Infinity; let maxInnerResultWindow = Infinity; - Object.values(indicesSettingsResp).forEach(indexSettings => { + Object.values(indicesSettingsResp).forEach((indexSettings) => { const indexMaxResultWindow = _.get( indexSettings, 'settings.index.max_result_window', diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index 6c62ceb347c85..463d3f3b3939d 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -36,7 +36,7 @@ interface ILayerTypeCount { } function getUniqueLayerCounts(layerCountsList: ILayerTypeCount[], mapsCount: number) { - const uniqueLayerTypes = _.uniq(_.flatten(layerCountsList.map(lTypes => Object.keys(lTypes)))); + const uniqueLayerTypes = _.uniq(_.flatten(layerCountsList.map((lTypes) => Object.keys(lTypes)))); return uniqueLayerTypes.reduce((accu: IStats, type: string) => { const typeCounts = layerCountsList.reduce( @@ -59,24 +59,24 @@ function getUniqueLayerCounts(layerCountsList: ILayerTypeCount[], mapsCount: num } function getIndexPatternsWithGeoFieldCount(indexPatterns: IIndexPattern[]) { - const fieldLists = indexPatterns.map(indexPattern => + const fieldLists = indexPatterns.map((indexPattern) => indexPattern.attributes && indexPattern.attributes.fields ? JSON.parse(indexPattern.attributes.fields) : [] ); - const fieldListsWithGeoFields = fieldLists.filter(fields => + const fieldListsWithGeoFields = fieldLists.filter((fields) => fields.some( (field: IFieldType) => field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE ) ); - const fieldListsWithGeoPointFields = fieldLists.filter(fields => + const fieldListsWithGeoPointFields = fieldLists.filter((fields) => fields.some((field: IFieldType) => field.type === ES_GEO_FIELD_TYPE.GEO_POINT) ); - const fieldListsWithGeoShapeFields = fieldLists.filter(fields => + const fieldListsWithGeoShapeFields = fieldLists.filter((fields) => fields.some((field: IFieldType) => field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE) ); @@ -96,25 +96,25 @@ export function buildMapsTelemetry({ indexPatternSavedObjects: IIndexPattern[]; settings: SavedObjectAttribute; }): SavedObjectAttributes { - const layerLists = mapSavedObjects.map(savedMapObject => + const layerLists = mapSavedObjects.map((savedMapObject) => savedMapObject.attributes && savedMapObject.attributes.layerListJSON ? JSON.parse(savedMapObject.attributes.layerListJSON) : [] ); const mapsCount = layerLists.length; - const dataSourcesCount = layerLists.map(lList => { + const dataSourcesCount = layerLists.map((lList) => { // todo: not every source-descriptor has an id // @ts-ignore const sourceIdList = lList.map((layer: LayerDescriptor) => layer.sourceDescriptor.id); return _.uniq(sourceIdList).length; }); - const layersCount = layerLists.map(lList => lList.length); - const layerTypesCount = layerLists.map(lList => _.countBy(lList, 'type')); + const layersCount = layerLists.map((lList) => lList.length); + const layerTypesCount = layerLists.map((lList) => _.countBy(lList, 'type')); // Count of EMS Vector layers used - const emsLayersCount = layerLists.map(lList => + const emsLayersCount = layerLists.map((lList) => _(lList) .countBy((layer: LayerDescriptor) => { const isEmsFile = _.get(layer, 'sourceDescriptor.type') === SOURCE_TYPES.EMS_FILE; diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index de07472275c0c..ad66712eb3ad6 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -21,12 +21,15 @@ import { GIS_API_PATH, EMS_SPRITES_PATH, INDEX_SETTINGS_API_PATH, + FONTS_API_PATH, } from '../common/constants'; import { EMSClient } from '@elastic/ems-client'; import fetch from 'node-fetch'; import { i18n } from '@kbn/i18n'; import { getIndexPatternSettings } from './lib/get_index_pattern_settings'; import { schema } from '@kbn/config-schema'; +import fs from 'fs'; +import path from 'path'; const ROOT = `/${GIS_API_PATH}`; @@ -76,7 +79,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -87,7 +90,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } const fileLayers = await emsClient.getFileLayers(); - const layer = fileLayers.find(layer => layer.getId() === request.query.id); + const layer = fileLayers.find((layer) => layer.getId() === request.query.id); if (!layer) { return null; } @@ -108,7 +111,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_RASTER_TILE_PATH}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -124,7 +127,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } const tmsServices = await emsClient.getTMSServices(); - const tmsService = tmsServices.find(layer => layer.getId() === request.query.id); + const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; } @@ -144,7 +147,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_CATALOGUE_PATH}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -155,8 +158,8 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }; //rewrite the urls to the submanifest - const tileService = main.services.find(service => service.type === 'tms'); - const fileService = main.services.find(service => service.type === 'file'); + const tileService = main.services.find((service) => service.type === 'tms'); + const fileService = main.services.find((service) => service.type === 'file'); if (tileService) { proxiedManifest.services.push({ ...tileService, @@ -180,13 +183,13 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_FILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } const file = await emsClient.getDefaultFileManifest(); - const layers = file.layers.map(layer => { + const layers = file.layers.map((layer) => { const newLayer = { ...layer }; const id = encodeURIComponent(layer.layer_id); const newUrl = `${EMS_FILES_DEFAULT_JSON_PATH}?id=${id}`; @@ -210,19 +213,19 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } const tilesManifest = await emsClient.getDefaultTMSManifest(); - const newServices = tilesManifest.services.map(service => { + const newServices = tilesManifest.services.map((service) => { const newService = { ...service, }; newService.formats = []; - const rasterFormats = service.formats.filter(format => format.format === 'raster'); + const rasterFormats = service.formats.filter((format) => format.format === 'raster'); if (rasterFormats.length) { const newUrl = `${EMS_TILES_RASTER_STYLE_PATH}?id=${service.id}`; newService.formats.push({ @@ -230,7 +233,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { url: newUrl, }); } - const vectorFormats = service.formats.filter(format => format.format === 'vector'); + const vectorFormats = service.formats.filter((format) => format.format === 'vector'); if (vectorFormats.length) { const newUrl = `${EMS_TILES_VECTOR_STYLE_PATH}?id=${service.id}`; newService.formats.push({ @@ -258,7 +261,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -269,7 +272,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } const tmsServices = await emsClient.getTMSServices(); - const tmsService = tmsServices.find(layer => layer.getId() === request.query.id); + const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; } @@ -294,7 +297,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -305,7 +308,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } const tmsServices = await emsClient.getTMSServices(); - const tmsService = tmsServices.find(layer => layer.getId() === request.query.id); + const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; } @@ -344,7 +347,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -355,7 +358,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } const tmsServices = await emsClient.getTMSServices(); - const tmsService = tmsServices.find(layer => layer.getId() === request.query.id); + const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; } @@ -386,7 +389,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -403,7 +406,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } const tmsServices = await emsClient.getTMSServices(); - const tmsService = tmsServices.find(layer => layer.getId() === request.query.id); + const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; } @@ -423,7 +426,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_GLYPHS_PATH}/{fontstack}/{range}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -444,7 +447,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -455,7 +458,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } const tmsServices = await emsClient.getTMSServices(); - const tmsService = tmsServices.find(layer => layer.getId() === request.params.id); + const tmsService = tmsServices.find((layer) => layer.getId() === request.params.id); if (!tmsService) { return null; } @@ -481,6 +484,39 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } ); + router.get( + { + path: `/${FONTS_API_PATH}/{fontstack}/{range}`, + validate: { + params: schema.object({ + fontstack: schema.string(), + range: schema.string(), + }), + }, + }, + (context, request, response) => { + return new Promise((resolve, reject) => { + const santizedRange = path.normalize(request.params.range); + const fontPath = path.join(__dirname, 'fonts', 'open_sans', `${santizedRange}.pbf`); + fs.readFile(fontPath, (error, data) => { + if (error) { + reject( + response.custom({ + statusCode: 404, + }) + ); + } else { + resolve( + response.ok({ + body: data, + }) + ); + } + }); + }); + } + ); + router.get( { path: `/${INDEX_SETTINGS_API_PATH}`, @@ -490,7 +526,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, response) => { + async (context, request, response) => { const { query } = request; if (!query.indexPatternTitle) { @@ -502,7 +538,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } try { - const resp = await con.core.elasticsearch.dataClient.callAsCurrentUser( + const resp = await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.getSettings', { index: query.indexPatternTitle, diff --git a/x-pack/plugins/maps/server/saved_objects/migrations.js b/x-pack/plugins/maps/server/saved_objects/migrations.js index 13b38353d6807..5f9576740db29 100644 --- a/x-pack/plugins/maps/server/saved_objects/migrations.js +++ b/x-pack/plugins/maps/server/saved_objects/migrations.js @@ -15,7 +15,7 @@ import { migrateJoinAggKey } from '../../common/migrations/join_agg_key'; export const migrations = { map: { - '7.2.0': doc => { + '7.2.0': (doc) => { const { attributes, references } = extractReferences(doc); return { @@ -24,7 +24,7 @@ export const migrations = { references, }; }, - '7.4.0': doc => { + '7.4.0': (doc) => { const attributes = emsRasterTileToEmsVectorTile(doc); return { @@ -32,7 +32,7 @@ export const migrations = { attributes, }; }, - '7.5.0': doc => { + '7.5.0': (doc) => { const attributes = topHitsTimeToSort(doc); return { @@ -40,7 +40,7 @@ export const migrations = { attributes, }; }, - '7.6.0': doc => { + '7.6.0': (doc) => { const attributesPhase1 = moveApplyGlobalQueryToSources(doc); const attributesPhase2 = addFieldMetaOptions({ attributes: attributesPhase1 }); @@ -49,7 +49,7 @@ export const migrations = { attributes: attributesPhase2, }; }, - '7.7.0': doc => { + '7.7.0': (doc) => { const attributesPhase1 = migrateSymbolStyleDescriptor(doc); const attributesPhase2 = migrateUseTopHitsToScalingType({ attributes: attributesPhase1 }); @@ -58,7 +58,7 @@ export const migrations = { attributes: attributesPhase2, }; }, - '7.8.0': doc => { + '7.8.0': (doc) => { const attributes = migrateJoinAggKey(doc); return { diff --git a/x-pack/plugins/ml/common/license/ml_license.ts b/x-pack/plugins/ml/common/license/ml_license.ts index 25b5b4992b227..e4367c9b921f4 100644 --- a/x-pack/plugins/ml/common/license/ml_license.ts +++ b/x-pack/plugins/ml/common/license/ml_license.ts @@ -31,7 +31,7 @@ export class MlLicense { license$: Observable, postInitFunctions?: Array<(lic: MlLicense) => void> ) { - this._licenseSubscription = license$.subscribe(async license => { + this._licenseSubscription = license$.subscribe(async (license) => { const { isEnabled: securityIsEnabled } = license.getFeature('security'); this._license = license; @@ -42,7 +42,7 @@ export class MlLicense { this._isFullLicense = isFullLicense(this._license); if (this._initialized === false && postInitFunctions !== undefined) { - postInitFunctions.forEach(f => f(this)); + postInitFunctions.forEach((f) => f(this)); } this._initialized = true; }); diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts index bc55c7549c589..3dbdb8bf3c002 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts @@ -50,6 +50,7 @@ export interface AnalysisConfig { latency?: number; multivariate_by_fields?: boolean; summary_count_field_name?: string; + per_partition_categorization?: PerPartitionCategorization; } export interface Detector { @@ -77,6 +78,7 @@ export interface DataDescription { export interface ModelPlotConfig { enabled: boolean; + annotations_enabled?: boolean; terms?: string; } @@ -86,3 +88,8 @@ export interface CustomRule { scope?: object; conditions: any[]; } + +export interface PerPartitionCategorization { + enabled: boolean; + stop_on_warn?: boolean; +} diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts index 572217ce16eee..9216430ab7830 100644 --- a/x-pack/plugins/ml/common/types/capabilities.ts +++ b/x-pack/plugins/ml/common/types/capabilities.ts @@ -73,11 +73,11 @@ export function getPluginPrivileges() { return { user: { ui: userMlCapabilitiesKeys, - api: userMlCapabilitiesKeys.map(k => `ml:${k}`), + api: userMlCapabilitiesKeys.map((k) => `ml:${k}`), }, admin: { ui: allMlCapabilities, - api: allMlCapabilities.map(k => `ml:${k}`), + api: allMlCapabilities.map((k) => `ml:${k}`), }, }; } diff --git a/x-pack/plugins/ml/common/types/categories.ts b/x-pack/plugins/ml/common/types/categories.ts index 5d4c3eab53ee8..b3655f274b362 100644 --- a/x-pack/plugins/ml/common/types/categories.ts +++ b/x-pack/plugins/ml/common/types/categories.ts @@ -16,6 +16,8 @@ export interface Category { max_matching_length: number; examples: string[]; grok_pattern: string; + partition_field_name?: string; // TODO: make non-optional once fields have been added to the results + partition_field_value?: string; // TODO: make non-optional once fields have been added to the results } export interface Token { diff --git a/x-pack/plugins/ml/common/types/common.ts b/x-pack/plugins/ml/common/types/common.ts index 691b3e9eb1163..f04ff2539e4e9 100644 --- a/x-pack/plugins/ml/common/types/common.ts +++ b/x-pack/plugins/ml/common/types/common.ts @@ -11,7 +11,7 @@ export interface Dictionary { // converts a dictionary to an array. note this loses the dictionary `key` information. // however it's able to retain the type information of the dictionary elements. export function dictionaryToArray(dict: Dictionary): TValue[] { - return Object.keys(dict).map(key => dict[key]); + return Object.keys(dict).map((key) => dict[key]); } // A recursive partial type to allow passing nested partial attributes. diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts new file mode 100644 index 0000000000000..5ba7f9c191a7f --- /dev/null +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; +export interface DeleteDataFrameAnalyticsWithIndexStatus { + success: boolean; + error?: CustomHttpResponseOptions; +} diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 3822e54ddd53c..1fef0e6e2ecba 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -178,7 +178,7 @@ export function isModelPlotEnabled( if (detectorHasPartitionField) { const partitionEntity = entityFields.find( - entityField => entityField.fieldName === detector.partition_field_name + (entityField) => entityField.fieldName === detector.partition_field_name ); isEnabled = partitionEntity?.fieldValue !== undefined && @@ -187,7 +187,7 @@ export function isModelPlotEnabled( if (isEnabled === true && detectorHasByField === true) { const byEntity = entityFields.find( - entityField => entityField.fieldName === detector.by_field_name + (entityField) => entityField.fieldName === detector.by_field_name ); isEnabled = byEntity?.fieldValue !== undefined && terms.indexOf(String(byEntity.fieldValue)) !== -1; @@ -349,7 +349,7 @@ export function basicJobValidation( // Analysis Configuration if (job.analysis_config.categorization_filters) { let v = true; - _.each(job.analysis_config.categorization_filters, d => { + _.each(job.analysis_config.categorization_filters, (d) => { try { new RegExp(d); } catch (e) { @@ -381,7 +381,7 @@ export function basicJobValidation( valid = false; } else { let v = true; - _.each(job.analysis_config.detectors, d => { + _.each(job.analysis_config.detectors, (d) => { if (_.isEmpty(d.function)) { v = false; } @@ -398,7 +398,7 @@ export function basicJobValidation( if (job.analysis_config.detectors.length >= 2) { // create an array of objects with a subset of the attributes // where we want to make sure they are not be the same across detectors - const compareSubSet = job.analysis_config.detectors.map(d => + const compareSubSet = job.analysis_config.detectors.map((d) => _.pick(d, [ 'function', 'field_name', @@ -479,8 +479,8 @@ export function basicJobValidation( return { messages, valid, - contains: id => messages.some(m => id === m.id), - find: id => messages.find(m => id === m.id), + contains: (id) => messages.some((m) => id === m.id), + find: (id) => messages.find((m) => id === m.id), }; } @@ -507,8 +507,8 @@ export function basicDatafeedValidation(datafeed: Datafeed): ValidationResults { return { messages, valid, - contains: id => messages.some(m => id === m.id), - find: id => messages.find(m => id === m.id), + contains: (id) => messages.some((m) => id === m.id), + find: (id) => messages.find((m) => id === m.id), }; } @@ -540,8 +540,8 @@ export function validateModelMemoryLimit(job: Job, limits: MlServerLimits): Vali return { valid, messages, - contains: id => messages.some(m => id === m.id), - find: id => messages.find(m => id === m.id), + contains: (id) => messages.some((m) => id === m.id), + find: (id) => messages.find((m) => id === m.id), }; } @@ -567,16 +567,16 @@ export function validateModelMemoryLimitUnits( return { valid, messages, - contains: id => messages.some(m => id === m.id), - find: id => messages.find(m => id === m.id), + contains: (id) => messages.some((m) => id === m.id), + find: (id) => messages.find((m) => id === m.id), }; } export function validateGroupNames(job: Job): ValidationResults { const { groups = [] } = job; const errorMessages: ValidationResults['messages'] = [ - ...(groups.some(group => !isJobIdValid(group)) ? [{ id: 'job_group_id_invalid' }] : []), - ...(groups.some(group => maxLengthValidator(JOB_ID_MAX_LENGTH)(group)) + ...(groups.some((group) => !isJobIdValid(group)) ? [{ id: 'job_group_id_invalid' }] : []), + ...(groups.some((group) => maxLengthValidator(JOB_ID_MAX_LENGTH)(group)) ? [{ id: 'job_group_id_invalid_max_length' }] : []), ]; @@ -586,8 +586,8 @@ export function validateGroupNames(job: Job): ValidationResults { return { valid, messages, - contains: id => messages.some(m => id === m.id), - find: id => messages.find(m => id === m.id), + contains: (id) => messages.some((m) => id === m.id), + find: (id) => messages.find((m) => id === m.id), }; } @@ -626,6 +626,6 @@ export function processCreatedBy(customSettings: CustomSettings) { export function splitIndexPatternNames(indexPatternName: string): string[] { return indexPatternName.includes(',') - ? indexPatternName.split(',').map(i => i.trim()) + ? indexPatternName.split(',').map((i) => i.trim()) : [indexPatternName]; } diff --git a/x-pack/plugins/ml/common/util/parse_interval.ts b/x-pack/plugins/ml/common/util/parse_interval.ts index 98a41be96941b..0f348f43d47b3 100644 --- a/x-pack/plugins/ml/common/util/parse_interval.ts +++ b/x-pack/plugins/ml/common/util/parse_interval.ts @@ -26,9 +26,7 @@ const SUPPORT_ZERO_DURATION_UNITS: SupportedUnits[] = ['ms', 's', 'm', 'h']; // 3. Fractional intervals e.g. 1.5h or 4.5d are not allowed, in line with the behaviour // of the Elasticsearch date histogram aggregation. export function parseInterval(interval: string): Duration | null { - const matches = String(interval) - .trim() - .match(INTERVAL_STRING_RE); + const matches = String(interval).trim().match(INTERVAL_STRING_RE); if (!Array.isArray(matches) || matches.length < 3) { return null; } diff --git a/x-pack/plugins/ml/common/util/string_utils.ts b/x-pack/plugins/ml/common/util/string_utils.ts index 9dd2ce3d74cd5..bd4ca02bf93cc 100644 --- a/x-pack/plugins/ml/common/util/string_utils.ts +++ b/x-pack/plugins/ml/common/util/string_utils.ts @@ -10,7 +10,7 @@ export function renderTemplate(str: string, data?: Record): stri const matches = str.match(/{{(.*?)}}/g); if (Array.isArray(matches) && data !== undefined) { - matches.forEach(v => { + matches.forEach((v) => { str = str.replace(v, data[v.replace(/{{|}}/g, '')]); }); } @@ -19,6 +19,6 @@ export function renderTemplate(str: string, data?: Record): stri } export function getMedianStringLength(strings: string[]) { - const sortedStringLengths = strings.map(s => s.length).sort((a, b) => a - b); + const sortedStringLengths = strings.map((s) => s.length).sort((a, b) => a - b); return sortedStringLengths[Math.floor(sortedStringLengths.length / 2)] || 0; } diff --git a/x-pack/plugins/ml/common/util/validation_utils.ts b/x-pack/plugins/ml/common/util/validation_utils.ts index 1ae5a899a4b4d..ee4be34c6f600 100644 --- a/x-pack/plugins/ml/common/util/validation_utils.ts +++ b/x-pack/plugins/ml/common/util/validation_utils.ts @@ -10,7 +10,7 @@ import { VALIDATION_STATUS } from '../constants/validation'; const contains = (arr: string[], str: string) => arr.indexOf(str) >= 0; export function getMostSevereMessageStatus(messages: Array<{ status: string }>): VALIDATION_STATUS { - const statuses = messages.map(m => m.status); + const statuses = messages.map((m) => m.status); return [VALIDATION_STATUS.INFO, VALIDATION_STATUS.WARNING, VALIDATION_STATUS.ERROR].reduce( (previous, current) => { return contains(statuses, current) ? current : previous; diff --git a/x-pack/plugins/ml/common/util/validators.ts b/x-pack/plugins/ml/common/util/validators.ts index 304d9a0029540..5dcdec0553106 100644 --- a/x-pack/plugins/ml/common/util/validators.ts +++ b/x-pack/plugins/ml/common/util/validators.ts @@ -13,7 +13,7 @@ import { ALLOWED_DATA_UNITS } from '../constants/validation'; export function maxLengthValidator( maxLength: number ): (value: string) => { maxLength: { requiredLength: number; actualLength: number } } | null { - return value => + return (value) => value && value.length > maxLength ? { maxLength: { @@ -31,7 +31,7 @@ export function maxLengthValidator( export function patternValidator( pattern: RegExp ): (value: string) => { pattern: { matchPattern: string } } | null { - return value => + return (value) => pattern.test(value) ? null : { @@ -48,7 +48,7 @@ export function patternValidator( export function composeValidators( ...validators: Array<(value: any) => { [key: string]: any } | null> ): (value: any) => { [key: string]: any } | null { - return value => { + return (value) => { const validationResult = validators.reduce((acc, validator) => { return { ...acc, @@ -65,6 +65,8 @@ export function requiredValidator() { }; } +export type ValidationResult = object | null; + export function memoryInputValidator(allowedUnits = ALLOWED_DATA_UNITS) { return (value: any) => { if (typeof value !== 'string' || value === '') { diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index e9796fcbb0fe4..4b6ff8c64822b 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -78,7 +78,7 @@ export const renderApp = ( const mlLicense = setLicenseCache(deps.licensing); - appMountParams.onAppLeave(actions => actions.default()); + appMountParams.onAppLeave((actions) => actions.default()); ReactDOM.render(, appMountParams.element); diff --git a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts index 1ca176d8d09ce..65ea03caef526 100644 --- a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts +++ b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts @@ -20,7 +20,7 @@ export function checkGetManagementMlJobsResolver() { ({ capabilities, isPlatinumOrTrialLicense, mlFeatureEnabledInSpace }) => { _capabilities = capabilities; // Loop through all capabilities to ensure they are all set to true. - const isManageML = Object.values(_capabilities).every(p => p === true); + const isManageML = Object.values(_capabilities).every((p) => p === true); if (isManageML === true && isPlatinumOrTrialLicense === true) { return resolve({ mlFeatureEnabledInSpace }); diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx b/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx index ceaf986b0d54f..f341f129e4653 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx +++ b/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx @@ -185,7 +185,7 @@ class AnnotationFlyoutUI extends Component { ); } }) - .catch(resp => { + .catch((resp) => { const toastNotifications = getToastNotifications(); if (typeof annotation._id === 'undefined') { toastNotifications.addDanger( @@ -339,7 +339,7 @@ class AnnotationFlyoutUI extends Component { } } -export const AnnotationFlyout: FC = props => { +export const AnnotationFlyout: FC = (props) => { const annotationProp = useObservable(annotation$); if (annotationProp === undefined) { diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index d9c32be41cd72..52d266cde1a2c 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -95,7 +95,7 @@ export class AnnotationsTable extends Component { maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE, }) .toPromise() - .then(resp => { + .then((resp) => { this.setState((prevState, props) => ({ annotations: resp.annotations[props.jobs[0].job_id] || [], errorMessage: undefined, @@ -103,7 +103,7 @@ export class AnnotationsTable extends Component { jobId: props.jobs[0].job_id, })); }) - .catch(resp => { + .catch((resp) => { console.log('Error loading list of annotations for jobs list:', resp); this.setState({ annotations: [], @@ -225,7 +225,7 @@ export class AnnotationsTable extends Component { window.open(`#/timeseriesexplorer${url}`, '_self'); }; - onMouseOverRow = record => { + onMouseOverRow = (record) => { if (this.mouseOverRecord !== undefined) { if (this.mouseOverRecord.rowId !== record.rowId) { // Mouse is over a different row, fire mouseleave on the previous record. @@ -354,7 +354,7 @@ export class AnnotationsTable extends Component { }, ]; - const jobIds = _.uniq(annotations.map(a => a.job_id)); + const jobIds = _.uniq(annotations.map((a) => a.job_id)); if (jobIds.length > 1) { columns.unshift({ field: 'job_id', @@ -373,7 +373,7 @@ export class AnnotationsTable extends Component { }), sortable: true, width: '60px', - render: key => { + render: (key) => { return {key}; }, }); @@ -382,7 +382,7 @@ export class AnnotationsTable extends Component { const actions = []; actions.push({ - render: annotation => { + render: (annotation) => { const editAnnotationsTooltipText = ( { + render: (annotation) => { const isDrillDownAvailable = isTimeSeriesViewJob(this.getJob(annotation.job_id)); const openInSingleMetricViewerTooltipText = isDrillDownAvailable ? ( { + const getRowProps = (item) => { return { onMouseOver: () => this.onMouseOverRow(item), onMouseLeave: () => this.onMouseLeaveRow(), diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js index 6728f019a6bd5..2a890f75fecd8 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js @@ -44,8 +44,8 @@ class AnomaliesTable extends Component { // Update the itemIdToExpandedRowMap state if a change to the table data has resulted // in an anomaly that was previously expanded no longer being in the data. const itemIdToExpandedRowMap = prevState.itemIdToExpandedRowMap; - const prevExpandedNotInData = Object.keys(itemIdToExpandedRowMap).find(rowId => { - const matching = nextProps.tableData.anomalies.find(anomaly => { + const prevExpandedNotInData = Object.keys(itemIdToExpandedRowMap).find((rowId) => { + const matching = nextProps.tableData.anomalies.find((anomaly) => { return anomaly.rowId === rowId; }); @@ -108,7 +108,7 @@ class AnomaliesTable extends Component { this.setState({ itemIdToExpandedRowMap }); }; - onMouseOverRow = record => { + onMouseOverRow = (record) => { if (this.mouseOverRecord !== undefined) { if (this.mouseOverRecord.rowId !== record.rowId) { // Mouse is over a different row, fire mouseleave on the previous record. @@ -132,7 +132,7 @@ class AnomaliesTable extends Component { } }; - setShowRuleEditorFlyoutFunction = func => { + setShowRuleEditorFlyoutFunction = (func) => { this.setState({ showRuleEditorFlyout: func, }); @@ -191,7 +191,7 @@ class AnomaliesTable extends Component { }, }; - const getRowProps = item => { + const getRowProps = (item) => { return { onMouseOver: () => this.onMouseOverRow(item), onMouseLeave: () => this.onMouseLeaveRow(), diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js index 8f79ce4a6c08a..af7c6c8e289f3 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js @@ -76,7 +76,7 @@ export function getColumns(

), - render: item => ( + render: (item) => ( toggleRow(item)} iconType={itemIdToExpandedRowMap[item.rowId] ? 'arrowDown' : 'arrowRight'} @@ -102,7 +102,7 @@ export function getColumns( }), dataType: 'date', scope: 'row', - render: date => renderTime(date, interval), + render: (date) => renderTime(date, interval), textOnly: true, sortable: true, }, @@ -131,7 +131,7 @@ export function getColumns( }, ]; - if (items.some(item => item.entityValue !== undefined)) { + if (items.some((item) => item.entityValue !== undefined)) { columns.push({ field: 'entityValue', 'data-test-subj': 'mlAnomaliesListColumnFoundFor', @@ -151,14 +151,14 @@ export function getColumns( }); } - if (items.some(item => item.influencers !== undefined)) { + if (items.some((item) => item.influencers !== undefined)) { columns.push({ field: 'influencers', 'data-test-subj': 'mlAnomaliesListColumnInfluencers', name: i18n.translate('xpack.ml.anomaliesTable.influencersColumnName', { defaultMessage: 'influenced by', }), - render: influencers => ( + render: (influencers) => ( item.actual !== undefined)) { + if (items.some((item) => item.actual !== undefined)) { columns.push({ field: 'actualSort', 'data-test-subj': 'mlAnomaliesListColumnActual', @@ -191,7 +191,7 @@ export function getColumns( }); } - if (items.some(item => item.typical !== undefined)) { + if (items.some((item) => item.typical !== undefined)) { columns.push({ field: 'typicalSort', 'data-test-subj': 'mlAnomaliesListColumnTypical', @@ -210,7 +210,7 @@ export function getColumns( // Assume that if we are showing typical, there will be an actual too, // so we can add a column to describe how actual compares to typical. - const nonTimeOfDayOrWeek = items.some(item => { + const nonTimeOfDayOrWeek = items.some((item) => { const summaryRecFunc = item.source.function; return summaryRecFunc !== 'time_of_day' && summaryRecFunc !== 'time_of_week'; }); @@ -241,7 +241,7 @@ export function getColumns( }); } - const showExamples = items.some(item => item.entityName === 'mlcategory'); + const showExamples = items.some((item) => item.entityName === 'mlcategory'); if (showExamples === true) { columns.push({ 'data-test-subj': 'mlAnomaliesListColumnCategoryExamples', @@ -250,7 +250,7 @@ export function getColumns( }), sortable: false, truncateText: true, - render: item => { + render: (item) => { const examples = _.get(examplesByJobId, [item.jobId, item.entityValue], []); return ( showLinksMenuForItem(item, showViewSeriesLink)); + const showLinks = items.some((item) => showLinksMenuForItem(item, showViewSeriesLink)); if (showLinks === true) { columns.push({ @@ -280,7 +280,7 @@ export function getColumns( name: i18n.translate('xpack.ml.anomaliesTable.actionsColumnName', { defaultMessage: 'actions', }), - render: item => { + render: (item) => { if (showLinksMenuForItem(item, showViewSeriesLink) === true) { return ( { + causes = sourceCauses.map((cause) => { const simplified = _.pick(cause, 'typical', 'actual', 'probability'); // Get the 'entity field name/value' to display in the cause - // For by and over, use by_field_name/value (over_field_name/value are in the top level fields) @@ -229,7 +229,7 @@ function getInfluencersItems(anomalyInfluencers, influencerFilter, numToDisplay) const items = []; for (let i = 0; i < numToDisplay; i++) { - Object.keys(anomalyInfluencers[i]).forEach(influencerFieldName => { + Object.keys(anomalyInfluencers[i]).forEach((influencerFieldName) => { const value = anomalyInfluencers[i][influencerFieldName]; items.push({ diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js b/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js index f4b16dab5ef52..2e42606c048d7 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js @@ -147,7 +147,7 @@ export class InfluencersCell extends Component { const recordInfluencers = this.props.influencers || []; const influencers = []; - recordInfluencers.forEach(influencer => { + recordInfluencers.forEach((influencer) => { _.each(influencer, (influencerFieldValue, influencerFieldName) => { influencers.push({ influencerFieldName, diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js index ef4f35591edf4..4850d583a626c 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -51,7 +51,7 @@ class LinksMenuUI extends Component { }; } - openCustomUrl = customUrl => { + openCustomUrl = (customUrl) => { const { anomaly, interval, isAggregatedData } = this.props; console.log('Anomalies Table - open customUrl for record:', anomaly); @@ -107,10 +107,10 @@ class LinksMenuUI extends Component { ml.results .getCategoryDefinition(jobId, categoryId) - .then(resp => { + .then((resp) => { // Prefix each of the terms with '+' so that the Elasticsearch Query String query // run in a drilldown Kibana dashboard has to match on all terms. - const termsArray = resp.terms.split(' ').map(term => `+${term}`); + const termsArray = resp.terms.split(' ').map((term) => `+${term}`); record.mlcategoryterms = termsArray.join(' '); record.mlcategoryregex = resp.regex; @@ -119,7 +119,7 @@ class LinksMenuUI extends Component { const urlPath = replaceStringTokens(customUrl.url_value, record, true); openCustomUrlWindow(urlPath, customUrl); }) - .catch(resp => { + .catch((resp) => { console.log('openCustomUrl(): error loading categoryDefinition:', resp); const { toasts } = this.props.kibana.services.notifications; toasts.addDanger( @@ -267,7 +267,7 @@ class LinksMenuUI extends Component { // categorization field is of mapping type text (preferred) or keyword. ml.results .getCategoryDefinition(record.job_id, categoryId) - .then(resp => { + .then((resp) => { let query = null; // Build query using categorization regex (if keyword type) or terms (if text type). // Check for terms or regex in case categoryId represents an anomaly from the absence of the @@ -327,7 +327,7 @@ class LinksMenuUI extends Component { path += '&_a=' + encodeURIComponent(_a); window.open(path, '_blank'); }) - .catch(resp => { + .catch((resp) => { console.log('viewExamples(): error loading categoryDefinition:', resp); const { toasts } = this.props.kibana.services.notifications; toasts.addDanger( @@ -344,7 +344,7 @@ class LinksMenuUI extends Component { function findFieldType(index) { getFieldTypeFromMapping(index, categorizationFieldName) - .then(resp => { + .then((resp) => { if (resp !== '') { createAndOpenUrl(index, resp); } else { @@ -363,7 +363,7 @@ class LinksMenuUI extends Component { }; onButtonClick = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx index 3ca191d6251cf..7897ef5cad0df 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx @@ -27,7 +27,7 @@ const Tooltip: FC<{ service: ChartTooltipService }> = React.memo(({ service }) = const refCallback = useRef(); useEffect(() => { - const subscription = service.tooltipState$.subscribe(tooltipState => { + const subscription = service.tooltipState$.subscribe((tooltipState) => { if (refCallback.current) { // update trigger refCallback.current(tooltipState.target); diff --git a/x-pack/plugins/ml/public/application/components/color_range_legend/color_range_legend.tsx b/x-pack/plugins/ml/public/application/components/color_range_legend/color_range_legend.tsx index d6f0d347a57ec..25af3f0ec2f7f 100644 --- a/x-pack/plugins/ml/public/application/components/color_range_legend/color_range_legend.tsx +++ b/x-pack/plugins/ml/public/application/components/color_range_legend/color_range_legend.tsx @@ -36,7 +36,7 @@ export const ColorRangeLegend: FC = ({ }) => { const d3Container = useRef(null); - const scale = d3.range(COLOR_RANGE_RESOLUTION + 1).map(d => ({ + const scale = d3.range(COLOR_RANGE_RESOLUTION + 1).map((d) => ({ offset: (d / COLOR_RANGE_RESOLUTION) * 100, stopColor: colorRange(d / COLOR_RANGE_RESOLUTION), })); @@ -59,9 +59,7 @@ export const ColorRangeLegend: FC = ({ const legendHeight = wrapperHeight - margin.top - margin.bottom; // remove, then redraw the legend - d3.select(d3Container.current) - .selectAll('*') - .remove(); + d3.select(d3Container.current).selectAll('*').remove(); const wrapper = d3 .select(d3Container.current) @@ -82,7 +80,7 @@ export const ColorRangeLegend: FC = ({ .attr('y2', '0%') .attr('spreadMethod', 'pad'); - scale.forEach(function(d) { + scale.forEach(function (d) { gradient .append('stop') .attr('offset', `${d.offset}%`) @@ -98,10 +96,7 @@ export const ColorRangeLegend: FC = ({ .attr('height', legendHeight) .style('fill', 'url(#mlColorRangeGradient)'); - const axisScale = d3.scale - .linear() - .domain([0, 1]) - .range([0, legendWidth]); + const axisScale = d3.scale.linear().domain([0, 1]).range([0, legendWidth]); // Using this formatter ensures we get e.g. `0` and not `0.0`, but still `0.1`, `0.2` etc. const tickFormat = d3.format(''); diff --git a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx index e1861b887b2a9..83e7b82986cf8 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx @@ -26,7 +26,7 @@ describe('SelectInterval', () => { expect(defaultSelectedValue).toBe('auto'); }); - test('currently selected value is updated correctly on click', done => { + test('currently selected value is updated correctly on click', (done) => { const wrapper = mount( diff --git a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx index cea3ef2a497b0..307900c6985ff 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx @@ -47,7 +47,7 @@ const OPTIONS = [ function optionValueToInterval(value: string) { // Builds the corresponding interval object with the required display and val properties // from the specified value. - const option = OPTIONS.find(opt => opt.value === value); + const option = OPTIONS.find((opt) => opt.value === value); // Default to auto if supplied value doesn't map to one of the options. let interval: TableInterval = { display: OPTIONS[0].text, val: OPTIONS[0].value }; diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx index e30c48c10a194..484a0c395f3f8 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx @@ -62,7 +62,7 @@ describe('SelectSeverity', () => { ); }); - test('state for currently selected value is updated correctly on click', done => { + test('state for currently selected value is updated correctly on click', (done) => { const wrapper = mount( diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx index a03594a5f213e..b8333e72c9ffb 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx @@ -67,7 +67,7 @@ export const SEVERITY_OPTIONS: TableSeverity[] = [ function optionValueToThreshold(value: number) { // Get corresponding threshold object with required display and val properties from the specified value. - let threshold = SEVERITY_OPTIONS.find(opt => opt.val === value); + let threshold = SEVERITY_OPTIONS.find((opt) => opt.val === value); // Default to warning if supplied value doesn't map to one of the options. if (threshold === undefined) { diff --git a/x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/custom_selection_table.js b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js similarity index 81% rename from x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/custom_selection_table.js rename to x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js index a7edb7184df00..c86b716b2f49b 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/custom_selection_table.js +++ b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js @@ -29,7 +29,7 @@ import { import { Pager } from '@elastic/eui/lib/services'; import { i18n } from '@kbn/i18n'; -const JOBS_PER_PAGE = 20; +const ITEMS_PER_PAGE = 20; function getError(error) { if (error !== null) { @@ -43,15 +43,18 @@ function getError(error) { } export function CustomSelectionTable({ + checkboxDisabledCheck, columns, filterDefaultFields, filters, items, + itemsPerPage = ITEMS_PER_PAGE, onTableChange, + radioDisabledCheck, selectedIds, singleSelection, sortableProperties, - timeseriesOnly, + tableItemId = 'id', }) { const [itemIdToSelectedMap, setItemIdToSelectedMap] = useState(getCurrentlySelectedItemIdsMap()); const [currentItems, setCurrentItems] = useState(items); @@ -59,7 +62,7 @@ export function CustomSelectionTable({ const [sortedColumn, setSortedColumn] = useState(''); const [pager, setPager] = useState(); const [pagerSettings, setPagerSettings] = useState({ - itemsPerPage: JOBS_PER_PAGE, + itemsPerPage: itemsPerPage, firstItemIndex: 0, lastItemIndex: 1, }); @@ -77,9 +80,9 @@ export function CustomSelectionTable({ }, [selectedIds]); // eslint-disable-line useEffect(() => { - const tablePager = new Pager(currentItems.length, JOBS_PER_PAGE); + const tablePager = new Pager(currentItems.length, itemsPerPage); setPagerSettings({ - itemsPerPage: JOBS_PER_PAGE, + itemsPerPage: itemsPerPage, firstItemIndex: tablePager.getFirstItemIndex(), lastItemIndex: tablePager.getLastItemIndex(), }); @@ -88,7 +91,7 @@ export function CustomSelectionTable({ function getCurrentlySelectedItemIdsMap() { const selectedIdsMap = { all: false }; - selectedIds.forEach(id => { + selectedIds.forEach((id) => { selectedIdsMap[id] = true; }); return selectedIdsMap; @@ -100,10 +103,10 @@ export function CustomSelectionTable({ function handleTableChange({ isSelected, itemId }) { const selectedMapIds = Object.getOwnPropertyNames(itemIdToSelectedMap); - const currentItemIds = currentItems.map(item => item.id); + const currentItemIds = currentItems.map((item) => item[tableItemId]); let currentSelected = selectedMapIds.filter( - id => itemIdToSelectedMap[id] === true && id !== itemId + (id) => itemIdToSelectedMap[id] === true && id !== itemId ); if (itemId !== 'all') { @@ -113,7 +116,7 @@ export function CustomSelectionTable({ } else { if (isSelected === false) { // don't include any current items in the selection update since we're deselecting 'all' - currentSelected = currentSelected.filter(id => currentItemIds.includes(id) === false); + currentSelected = currentSelected.filter((id) => currentItemIds.includes(id) === false); } else { // grab all id's currentSelected = [...currentSelected, ...currentItemIds]; @@ -124,11 +127,11 @@ export function CustomSelectionTable({ onTableChange(currentSelected); } - function handleChangeItemsPerPage(itemsPerPage) { - pager.setItemsPerPage(itemsPerPage); + function handleChangeItemsPerPage(numItemsPerPage) { + pager.setItemsPerPage(numItemsPerPage); setPagerSettings({ ...pagerSettings, - itemsPerPage, + itemsPerPage: numItemsPerPage, firstItemIndex: pager.getFirstItemIndex(), lastItemIndex: pager.getLastItemIndex(), }); @@ -161,7 +164,9 @@ export function CustomSelectionTable({ } function areAllItemsSelected() { - const indexOfUnselectedItem = currentItems.findIndex(item => !isItemSelected(item.id)); + const indexOfUnselectedItem = currentItems.findIndex( + (item) => !isItemSelected(item[tableItemId]) + ); return indexOfUnselectedItem === -1; } @@ -199,7 +204,7 @@ export function CustomSelectionTable({ function toggleAll() { const allSelected = areAllItemsSelected() || itemIdToSelectedMap.all === true; const newItemIdToSelectedMap = {}; - currentItems.forEach(item => (newItemIdToSelectedMap[item.id] = !allSelected)); + currentItems.forEach((item) => (newItemIdToSelectedMap[item[tableItemId]] = !allSelected)); setItemIdToSelectedMap(newItemIdToSelectedMap); handleTableChange({ isSelected: !allSelected, itemId: 'all' }); } @@ -244,8 +249,8 @@ export function CustomSelectionTable({ } function renderRows() { - const renderRow = item => { - const cells = columns.map(column => { + const renderRow = (item) => { + const cells = columns.map((column) => { const cell = item[column.id]; let child; @@ -255,20 +260,23 @@ export function CustomSelectionTable({ {!singleSelection && ( toggleItem(item.id)} + disabled={ + checkboxDisabledCheck !== undefined ? checkboxDisabledCheck(item) : undefined + } + id={`${item[tableItemId]}-checkbox`} + data-test-subj={`${item[tableItemId]}-checkbox`} + checked={isItemSelected(item[tableItemId])} + onChange={() => toggleItem(item[tableItemId])} type="inList" /> )} {singleSelection && ( toggleItem(item.id)} - disabled={timeseriesOnly && item.isSingleMetricViewerJob === false} + id={item[tableItemId]} + data-test-subj={`${item[tableItemId]}-radio-button`} + checked={isItemSelected(item[tableItemId])} + onChange={() => toggleItem(item[tableItemId])} + disabled={radioDisabledCheck !== undefined ? radioDisabledCheck(item) : undefined} /> )} @@ -299,11 +307,11 @@ export function CustomSelectionTable({ return ( {cells} @@ -331,7 +339,7 @@ export function CustomSelectionTable({ - + {renderSelectAll(true)} - + {renderHeaderCells()} {renderRows()} @@ -368,10 +376,10 @@ export function CustomSelectionTable({ handlePageChange(pageIndex)} + onChangePage={(pageIndex) => handlePageChange(pageIndex)} /> )} @@ -379,13 +387,16 @@ export function CustomSelectionTable({ } CustomSelectionTable.propTypes = { + checkboxDisabledCheck: PropTypes.func, columns: PropTypes.array.isRequired, filterDefaultFields: PropTypes.array, filters: PropTypes.array, items: PropTypes.array.isRequired, + itemsPerPage: PropTypes.number, onTableChange: PropTypes.func.isRequired, + radioDisabledCheck: PropTypes.func, selectedId: PropTypes.array, singleSelection: PropTypes.bool, sortableProperties: PropTypes.object, - timeseriesOnly: PropTypes.bool, + tableItemId: PropTypes.string, }; diff --git a/x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/index.js b/x-pack/plugins/ml/public/application/components/custom_selection_table/index.js similarity index 100% rename from x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/index.js rename to x-pack/plugins/ml/public/application/components/custom_selection_table/index.js diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index d141b68b5d03f..44a2473f75937 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -55,8 +55,8 @@ export const euiDataGridToolbarSettings = { }; export const getFieldsFromKibanaIndexPattern = (indexPattern: IndexPattern): string[] => { - const allFields = indexPattern.fields.map(f => f.name); - const indexPatternFields: string[] = allFields.filter(f => { + const allFields = indexPattern.fields.map((f) => f.name); + const indexPatternFields: string[] = allFields.filter((f) => { if (indexPattern.metaFields.includes(f)) { return false; } @@ -78,7 +78,7 @@ export interface FieldTypes { } export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, resultsField: string) => { - return Object.keys(fieldTypes).map(field => { + return Object.keys(fieldTypes).map((field) => { // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] // To fall back to the default string schema it needs to be undefined. let schema; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index aeb774a224021..618075a77d906 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -52,7 +52,7 @@ function isWithHeader(arg: any): arg is PropsWithHeader { type Props = PropsWithHeader | PropsWithoutHeader; export const DataGrid: FC = memo( - props => { + (props) => { const { columns, dataTestSubj, @@ -75,7 +75,7 @@ export const DataGrid: FC = memo( useEffect(() => { if (invalidSortingColumnns.length > 0) { - invalidSortingColumnns.forEach(columnId => { + invalidSortingColumnns.forEach((columnId) => { toastNotifications.addDanger( i18n.translate('xpack.ml.dataGrid.invalidSortingColumnError', { defaultMessage: `The column '{columnId}' cannot be used for sorting.`, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts index c7c4f46031b6e..7843bf2ea801b 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts @@ -37,15 +37,15 @@ export const useDataGrid = ( const [pagination, setPagination] = useState(defaultPagination); const [sortingColumns, setSortingColumns] = useState([]); - const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback(pageSize => { - setPagination(p => { + const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback((pageSize) => { + setPagination((p) => { const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); return { pageIndex, pageSize }; }); }, []); const onChangePage: OnChangePage = useCallback( - pageIndex => setPagination(p => ({ ...p, pageIndex })), + (pageIndex) => setPagination((p) => ({ ...p, pageIndex })), [] ); @@ -54,7 +54,7 @@ export const useDataGrid = ( // Column visibility const [visibleColumns, setVisibleColumns] = useState([]); - const columnIds = columns.map(c => c.id); + const columnIds = columns.map((c) => c.id); const filteredColumnIds = defaultVisibleColumnsFilter !== undefined ? columnIds.filter(defaultVisibleColumnsFilter) @@ -69,10 +69,10 @@ export const useDataGrid = ( const [invalidSortingColumnns, setInvalidSortingColumnns] = useState([]); const onSort: OnSort = useCallback( - sc => { + (sc) => { // Check if an unsupported column type for sorting was selected. const updatedInvalidSortingColumnns = sc.reduce((arr, current) => { - const columnType = columns.find(dgc => dgc.id === current.id); + const columnType = columns.find((dgc) => dgc.id === current.id); if (columnType?.schema === 'json') { arr.push(current.id); } diff --git a/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js b/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js index fd19e6b2f06b6..5790b8bd239e0 100644 --- a/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js +++ b/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js @@ -28,8 +28,8 @@ export class DataRecognizer extends Component { componentDidMount() { // once the mount is complete, call the recognize endpoint to see if the index format is known to us, ml.recognizeIndex({ indexPatternTitle: this.indexPattern.title }) - .then(resp => { - const results = resp.map(r => ( + .then((resp) => { + const results = resp.map((r) => ( { + .catch((e) => { console.error('Error attempting to recognize index', e); }); } diff --git a/x-pack/plugins/ml/public/application/components/influencers_list/influencers_list.js b/x-pack/plugins/ml/public/application/components/influencers_list/influencers_list.js index 1044caa83e2ad..06978dc0aa736 100644 --- a/x-pack/plugins/ml/public/application/components/influencers_list/influencers_list.js +++ b/x-pack/plugins/ml/public/application/components/influencers_list/influencers_list.js @@ -103,7 +103,7 @@ Influencer.propTypes = { }; function InfluencersByName({ influencerFieldName, influencerFilter, fieldValues }) { - const influencerValues = fieldValues.map(valueData => ( + const influencerValues = fieldValues.map((valueData) => ( ( + const influencersByName = Object.keys(influencers).map((influencerFieldName) => ( = 0} - onChange={e => { + onChange={(e) => { setItemSelected(item, e.target.checked); }} /> diff --git a/x-pack/plugins/ml/public/application/components/items_grid/items_grid_pagination.js b/x-pack/plugins/ml/public/application/components/items_grid/items_grid_pagination.js index b76cb5d66a35f..8d170e76664b1 100644 --- a/x-pack/plugins/ml/public/application/components/items_grid/items_grid_pagination.js +++ b/x-pack/plugins/ml/public/application/components/items_grid/items_grid_pagination.js @@ -48,11 +48,11 @@ export class ItemsGridPagination extends Component { }); }; - onPageClick = pageNumber => { + onPageClick = (pageNumber) => { this.props.setActivePage(pageNumber); }; - onChangeItemsPerPage = pageSize => { + onChangeItemsPerPage = (pageSize) => { this.closePopover(); this.props.setItemsPerPage(pageSize); }; @@ -78,7 +78,7 @@ export class ItemsGridPagination extends Component { const pageCount = Math.ceil(itemCount / itemsPerPage); - const items = itemsPerPageOptions.map(pageSize => { + const items = itemsPerPageOptions.map((pageSize) => { return ( ; } /** * Component for rendering job messages for anomaly detection * and data frame analytics jobs. */ -export const JobMessages: FC = ({ messages, loading, error }) => { +export const JobMessages: FC = ({ messages, loading, error, refreshMessage }) => { const columns = [ { - name: '', + name: refreshMessage ? ( + + + + ) : ( + '' + ), render: (message: JobMessage) => , width: `${theme.euiSizeL}`, }, diff --git a/x-pack/plugins/ml/public/application/components/job_selector/id_badges/id_badges.js b/x-pack/plugins/ml/public/application/components/job_selector/id_badges/id_badges.js index 1810896770c7a..8afe9aa06fb09 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/id_badges/id_badges.js +++ b/x-pack/plugins/ml/public/application/components/job_selector/id_badges/id_badges.js @@ -38,7 +38,7 @@ export function IdBadges({ limit, maps, onLinkClick, selectedIds, showAllBarBadg if (maps.groupsMap[currentId] === undefined) { const jobGroups = maps.jobsMap[currentId] || []; - if (jobGroups.some(g => currentGroups.includes(g)) === false) { + if (jobGroups.some((g) => currentGroups.includes(g)) === false) { badges.push( diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts b/x-pack/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts index 3a215f8cfb46d..3f3de405711c5 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts @@ -15,10 +15,10 @@ export function getGroupsFromJobs(jobs: MlJobWithTimeRange[]) { const groups: Dictionary = {}; const groupsMap: Dictionary = {}; - jobs.forEach(job => { + jobs.forEach((job) => { // Organize job by group if (job.groups !== undefined) { - job.groups.forEach(g => { + job.groups.forEach((g) => { if (groups[g] === undefined) { groups[g] = { id: g, @@ -61,7 +61,7 @@ export function getGroupsFromJobs(jobs: MlJobWithTimeRange[]) { } }); - Object.keys(groups).forEach(groupId => { + Object.keys(groups).forEach((groupId) => { const group = groups[groupId]; group.timeRange.widthPx = group.timeRange.toPx - group.timeRange.fromPx; group.timeRange.toMoment = moment(group.timeRange.to); @@ -78,13 +78,13 @@ export function getGroupsFromJobs(jobs: MlJobWithTimeRange[]) { }); }); - return { groups: Object.keys(groups).map(g => groups[g]), groupsMap }; + return { groups: Object.keys(groups).map((g) => groups[g]), groupsMap }; } export function getTimeRangeFromSelection(jobs: MlJobWithTimeRange[], selection: string[]) { if (jobs.length > 0) { const times: number[] = []; - jobs.forEach(job => { + jobs.forEach((job) => { if (selection.includes(job.job_id)) { if (job.timeRange.from !== undefined) { times.push(job.timeRange.from); @@ -110,18 +110,15 @@ export function normalizeTimes( dateFormatTz: string, ganttBarWidth: number ) { - const jobsWithTimeRange = jobs.filter(job => { + const jobsWithTimeRange = jobs.filter((job) => { return job.timeRange.to !== undefined && job.timeRange.from !== undefined; }); - const min = Math.min(...jobsWithTimeRange.map(job => +job.timeRange.from)); - const max = Math.max(...jobsWithTimeRange.map(job => +job.timeRange.to)); - const ganttScale = d3.scale - .linear() - .domain([min, max]) - .range([1, ganttBarWidth]); + const min = Math.min(...jobsWithTimeRange.map((job) => +job.timeRange.from)); + const max = Math.max(...jobsWithTimeRange.map((job) => +job.timeRange.to)); + const ganttScale = d3.scale.linear().domain([min, max]).range([1, ganttBarWidth]); - jobs.forEach(job => { + jobs.forEach((job) => { if (job.timeRange.to !== undefined && job.timeRange.from !== undefined) { job.timeRange.fromPx = ganttScale(job.timeRange.from); job.timeRange.toPx = ganttScale(job.timeRange.to); diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx index f709c161bef17..6bdf2fdb7caa2 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx @@ -33,12 +33,12 @@ function mergeSelection( const selectedIds: string[] = []; const alreadySelected: string[] = []; - groupObjs.forEach(group => { + groupObjs.forEach((group) => { selectedIds.push(group.groupId); alreadySelected.push(...group.jobIds); }); - jobIds.forEach(jobId => { + jobIds.forEach((jobId) => { // Add jobId if not already included in group selection if (alreadySelected.includes(jobId) === false) { selectedIds.push(jobId); @@ -53,7 +53,7 @@ export function getInitialGroupsMap(selectedGroups: GroupObj[]): GroupsMap { const map: GroupsMap = {}; if (selectedGroups.length) { - selectedGroups.forEach(group => { + selectedGroups.forEach((group) => { map[group.groupId] = group.jobIds; }); } diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx index 66aa05d2aaa97..3fb654f35be4d 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx @@ -85,7 +85,7 @@ export const JobSelectorFlyout: FC = ({ const allNewSelection: string[] = []; const groupSelection: Array<{ groupId: string; jobIds: string[] }> = []; - newSelection.forEach(id => { + newSelection.forEach((id) => { if (jobGroupsMaps.groupsMap[id] !== undefined) { // Push all jobs from selected groups into the newSelection list allNewSelection.push(...jobGroupsMaps.groupsMap[id]); @@ -111,7 +111,7 @@ export const JobSelectorFlyout: FC = ({ } function removeId(id: string) { - setNewSelection(newSelection.filter(item => item !== id)); + setNewSelection(newSelection.filter((item) => item !== id)); } function toggleTimerangeSwitch() { diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js index c55e03776c09d..7b104ea372ae5 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js @@ -6,7 +6,7 @@ import React, { Fragment, useState, useEffect } from 'react'; import { PropTypes } from 'prop-types'; -import { CustomSelectionTable } from '../custom_selection_table'; +import { CustomSelectionTable } from '../../custom_selection_table'; import { JobSelectorBadge } from '../job_selector_badge'; import { TimeRangeBar } from '../timerange_bar'; @@ -38,12 +38,12 @@ export function JobSelectorTable({ sortablePropertyItems = [ { name: 'job_id', - getValue: item => item.job_id.toLowerCase(), + getValue: (item) => item.job_id.toLowerCase(), isAscending: true, }, { name: 'groups', - getValue: item => (item.groups && item.groups[0] ? item.groups[0].toLowerCase() : ''), + getValue: (item) => (item.groups && item.groups[0] ? item.groups[0].toLowerCase() : ''), isAscending: true, }, ]; @@ -52,7 +52,7 @@ export function JobSelectorTable({ sortablePropertyItems = [ { name: 'id', - getValue: item => item.id.toLowerCase(), + getValue: (item) => item.id.toLowerCase(), isAscending: true, }, ]; @@ -81,7 +81,7 @@ export function JobSelectorTable({ ]; function getGroupOptions() { - return groupsList.map(g => ({ + return groupsList.map((g) => ({ value: g.id, view: ( @@ -107,7 +107,7 @@ export function JobSelectorTable({ id: 'checkbox', isCheckbox: true, textOnly: false, - width: '24px', + width: '32px', }, { label: 'job ID', @@ -121,7 +121,9 @@ export function JobSelectorTable({ isSortable: true, alignment: LEFT_ALIGNMENT, render: ({ groups = [] }) => - groups.map(group => ), + groups.map((group) => ( + + )), }, { label: 'time range', @@ -154,7 +156,10 @@ export function JobSelectorTable({ filters={filters} filterDefaultFields={!singleSelection ? JOB_FILTER_FIELDS : undefined} items={jobs} - onTableChange={selectionFromTable => onSelection({ selectionFromTable })} + onTableChange={(selectionFromTable) => onSelection({ selectionFromTable })} + radioDisabledCheck={(item) => { + return timeseriesOnly && item.isSingleMetricViewerJob === false; + }} selectedIds={selectedIds} singleSelection={singleSelection} sortableProperties={sortableProperties} @@ -200,7 +205,7 @@ export function JobSelectorTable({ columns={groupColumns} filterDefaultFields={!singleSelection ? GROUP_FILTER_FIELDS : undefined} items={groupsList} - onTableChange={selectionFromTable => onSelection({ selectionFromTable })} + onTableChange={(selectionFromTable) => onSelection({ selectionFromTable })} selectedIds={selectedIds} sortableProperties={sortableProperties} /> @@ -213,7 +218,7 @@ export function JobSelectorTable({ size="s" tabs={tabs} initialSelectedTab={tabs[0]} - onTabClick={tab => { + onTabClick={(tab) => { setCurrentTab(tab.id); }} /> diff --git a/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts b/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts index d3fad9ae6bc2c..74c238a0895ca 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts +++ b/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts @@ -19,8 +19,8 @@ import { getTimeRangeFromSelection } from './job_select_service_utils'; // check that the ids read from the url exist by comparing them to the // jobs loaded via mlJobsService. function getInvalidJobIds(jobs: MlJobWithTimeRange[], ids: string[]) { - return ids.filter(id => { - const jobExists = jobs.some(job => job.job_id === id); + return ids.filter((id) => { + const jobExists = jobs.some((job) => job.job_id === id); return jobExists === false && id !== '*'; }); } diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx index edc6aece265f3..abaaf2cc3a185 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx @@ -27,7 +27,7 @@ interface RefreshInterval { } function getRecentlyUsedRangesFactory(timeHistory: TimeHistoryContract) { - return function(): Duration[] { + return function (): Duration[] { return ( timeHistory.get()?.map(({ from, to }: TimeRange) => { return { diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.js b/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.js index e6b1f55775608..ceb03b5f175ac 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.js @@ -84,17 +84,17 @@ export class ConditionExpression extends Component { }); }; - changeAppliesTo = event => { + changeAppliesTo = (event) => { const { index, operator, value, updateCondition } = this.props; updateCondition(index, event.target.value, operator, value); }; - changeOperator = event => { + changeOperator = (event) => { const { index, appliesTo, value, updateCondition } = this.props; updateCondition(index, appliesTo, event.target.value, value); }; - changeValue = event => { + changeValue = (event) => { const { index, appliesTo, operator, updateCondition } = this.props; updateCondition(index, appliesTo, operator, +event.target.value); }; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js index f259a1f1ffb02..eed57aaf1b491 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js @@ -91,7 +91,7 @@ class RuleEditorFlyoutUI extends Component { } } - showFlyout = anomaly => { + showFlyout = (anomaly) => { let ruleIndex = -1; const job = mlJobService.getJob(anomaly.jobId); if (job === undefined) { @@ -144,13 +144,13 @@ class RuleEditorFlyoutUI extends Component { // Load the current list of filters. These are used for configuring rule scope. ml.filters .filters() - .then(filters => { - const filterListIds = filters.map(filter => filter.filter_id); + .then((filters) => { + const filterListIds = filters.map((filter) => filter.filter_id); this.setState({ filterListIds, }); }) - .catch(resp => { + .catch((resp) => { console.log('Error loading list of filters:', resp); const { toasts } = this.props.kibana.services.notifications; toasts.addDanger( @@ -169,7 +169,7 @@ class RuleEditorFlyoutUI extends Component { this.setState({ isFlyoutVisible: false }); }; - setEditRuleIndex = ruleIndex => { + setEditRuleIndex = (ruleIndex) => { const detectorIndex = this.state.anomaly.detectorIndex; const detector = this.state.job.analysis_config.detectors[detectorIndex]; const rules = detector.custom_rules; @@ -182,7 +182,7 @@ class RuleEditorFlyoutUI extends Component { const isScopeEnabled = rule.scope !== undefined && Object.keys(rule.scope).length > 0; if (isScopeEnabled === true) { // Add 'enabled:true' to mark them as selected in the UI. - Object.keys(rule.scope).forEach(field => { + Object.keys(rule.scope).forEach((field) => { rule.scope[field].enabled = true; }); } @@ -195,9 +195,9 @@ class RuleEditorFlyoutUI extends Component { }); }; - onSkipResultChange = e => { + onSkipResultChange = (e) => { const checked = e.target.checked; - this.setState(prevState => { + this.setState((prevState) => { const actions = [...prevState.rule.actions]; const idx = actions.indexOf(ACTION.SKIP_RESULT); if (idx === -1 && checked) { @@ -212,9 +212,9 @@ class RuleEditorFlyoutUI extends Component { }); }; - onSkipModelUpdateChange = e => { + onSkipModelUpdateChange = (e) => { const checked = e.target.checked; - this.setState(prevState => { + this.setState((prevState) => { const actions = [...prevState.rule.actions]; const idx = actions.indexOf(ACTION.SKIP_MODEL_UPDATE); if (idx === -1 && checked) { @@ -229,9 +229,9 @@ class RuleEditorFlyoutUI extends Component { }); }; - onConditionsEnabledChange = e => { + onConditionsEnabledChange = (e) => { const isConditionsEnabled = e.target.checked; - this.setState(prevState => { + this.setState((prevState) => { let conditions; if (isConditionsEnabled === false) { // Clear any conditions that have been added. @@ -249,7 +249,7 @@ class RuleEditorFlyoutUI extends Component { }; addCondition = () => { - this.setState(prevState => { + this.setState((prevState) => { const conditions = [...prevState.rule.conditions]; conditions.push(getNewConditionDefaults()); @@ -260,7 +260,7 @@ class RuleEditorFlyoutUI extends Component { }; updateCondition = (index, appliesTo, operator, value) => { - this.setState(prevState => { + this.setState((prevState) => { const conditions = [...prevState.rule.conditions]; if (index < conditions.length) { conditions[index] = { @@ -276,8 +276,8 @@ class RuleEditorFlyoutUI extends Component { }); }; - deleteCondition = index => { - this.setState(prevState => { + deleteCondition = (index) => { + this.setState((prevState) => { const conditions = [...prevState.rule.conditions]; if (index < conditions.length) { conditions.splice(index, 1); @@ -289,9 +289,9 @@ class RuleEditorFlyoutUI extends Component { }); }; - onScopeEnabledChange = e => { + onScopeEnabledChange = (e) => { const isScopeEnabled = e.target.checked; - this.setState(prevState => { + this.setState((prevState) => { const rule = { ...prevState.rule }; if (isScopeEnabled === false) { // Clear scope property. @@ -306,7 +306,7 @@ class RuleEditorFlyoutUI extends Component { }; updateScope = (fieldName, filterId, filterType, enabled) => { - this.setState(prevState => { + this.setState((prevState) => { let scope = { ...prevState.rule.scope }; if (scope === undefined) { scope = {}; @@ -338,7 +338,7 @@ class RuleEditorFlyoutUI extends Component { const detectorIndex = anomaly.detectorIndex; saveJobRule(job, detectorIndex, ruleIndex, editedRule) - .then(resp => { + .then((resp) => { if (resp.success) { toasts.add({ title: i18n.translate( @@ -370,7 +370,7 @@ class RuleEditorFlyoutUI extends Component { ); } }) - .catch(error => { + .catch((error) => { console.error(error); toasts.addDanger( i18n.translate( @@ -384,14 +384,14 @@ class RuleEditorFlyoutUI extends Component { }); }; - deleteRuleAtIndex = index => { + deleteRuleAtIndex = (index) => { const { toasts } = this.props.kibana.services.notifications; const { job, anomaly } = this.state; const jobId = job.job_id; const detectorIndex = anomaly.detectorIndex; deleteJobRule(job, detectorIndex, index) - .then(resp => { + .then((resp) => { if (resp.success) { toasts.addSuccess( i18n.translate( @@ -402,7 +402,14 @@ class RuleEditorFlyoutUI extends Component { } ) ); - this.closeFlyout(); + const updatedJob = mlJobService.getJob(anomaly.jobId); + const updatedDetector = updatedJob.analysis_config.detectors[detectorIndex]; + const updatedRules = updatedDetector.custom_rules; + if (!updatedRules) { + this.closeFlyout(); + } else { + this.setState({ job: { ...updatedJob } }); + } } else { toasts.addDanger( i18n.translate( @@ -415,7 +422,7 @@ class RuleEditorFlyoutUI extends Component { ); } }) - .catch(error => { + .catch((error) => { console.error(error); let errorMessage = i18n.translate( 'xpack.ml.ruleEditor.ruleEditorFlyout.errorWithDeletingRuleFromJobDetectorNotificationMessage', @@ -456,7 +463,7 @@ class RuleEditorFlyoutUI extends Component { this.closeFlyout(); } }) - .catch(error => { + .catch((error) => { console.log(`Error adding ${item} to filter ${filterId}:`, error); toasts.addDanger( i18n.translate( diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js index 3e8a17eeb8617..79080ef0f9bb6 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js @@ -50,7 +50,7 @@ jest.mock('../../capabilities/check_capabilities', () => ({ })); jest.mock('../../../../../../../src/plugins/kibana_react/public', () => ({ - withKibana: comp => { + withKibana: (comp) => { return comp; }, })); diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js b/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js index d776ebb8c4cd5..a8c7ac0f6f598 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js @@ -29,7 +29,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; const POPOVER_STYLE = { zIndex: '200' }; function getFilterListOptions(filterListIds) { - return filterListIds.map(filterId => ({ value: filterId, text: filterId })); + return filterListIds.map((filterId) => ({ value: filterId, text: filterId })); } export class ScopeExpression extends Component { @@ -53,19 +53,19 @@ export class ScopeExpression extends Component { }); }; - onChangeFilterType = event => { + onChangeFilterType = (event) => { const { fieldName, filterId, enabled, updateScope } = this.props; updateScope(fieldName, filterId, event.target.value, enabled); }; - onChangeFilterId = event => { + onChangeFilterId = (event) => { const { fieldName, filterType, enabled, updateScope } = this.props; updateScope(fieldName, event.target.value, filterType, enabled); }; - onEnableChange = event => { + onEnableChange = (event) => { const { fieldName, filterId, filterType, updateScope } = this.props; updateScope(fieldName, filterId, filterType, event.target.checked); @@ -131,7 +131,7 @@ export class ScopeExpression extends Component { } value={fieldName} isActive={false} - onClick={event => event.preventDefault()} + onClick={(event) => event.preventDefault()} /> diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js index 189fb6a35d3e6..839dcee870983 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js @@ -12,7 +12,7 @@ jest.mock('../../services/job_service.js', () => 'mlJobService'); // with 'mock' so it can be used lazily. const mockCheckPermission = jest.fn(() => true); jest.mock('../../capabilities/check_capabilities', () => ({ - checkPermission: privilege => mockCheckPermission(privilege), + checkPermission: (privilege) => mockCheckPermission(privilege), })); import { shallowWithIntl } from 'test_utils/enzyme_helpers'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.js index 07ca459949947..612cebff60d02 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.js @@ -52,7 +52,7 @@ export class EditConditionLink extends Component { this.state = { value }; } - onChangeValue = event => { + onChangeValue = (event) => { const enteredValue = event.target.value; this.setState({ value: enteredValue !== '' ? +enteredValue : '', diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.js index ec966ec8bcded..c8071d2629eeb 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.js @@ -57,13 +57,13 @@ export class RuleActionPanel extends Component { const filterId = scope[partitionFieldName].filter_id; ml.filters .filters({ filterId }) - .then(filter => { + .then((filter) => { const filterItems = filter.items; if (filterItems.indexOf(partitionFieldValue[0]) === -1) { this.setState({ showAddToFilterListLink: true }); } }) - .catch(resp => { + .catch((resp) => { console.log(`Error loading filter ${filterId}:`, resp); }); } diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/select_rule_action.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/select_rule_action.js index 309e271ad26a4..c2f5820f84a9e 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/select_rule_action.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/select_rule_action.js @@ -32,7 +32,7 @@ export function SelectRuleAction({ if (rules.length > 0) { ruleActionPanels = rules.map((rule, index) => { return ( - + scope[field].enabled === true); + isValid = Object.keys(scope).some((field) => scope[field].enabled === true); } } } @@ -76,7 +76,7 @@ export function saveJobRule(job, detectorIndex, ruleIndex, editedRule) { const clonedRule = cloneDeep(editedRule); const scope = clonedRule.scope; if (scope !== undefined) { - Object.keys(scope).forEach(field => { + Object.keys(scope).forEach((field) => { if (scope[field].enabled === false) { delete scope[field]; } else { @@ -148,7 +148,7 @@ export function updateJobRules(job, detectorIndex, rules) { return new Promise((resolve, reject) => { mlJobService .updateJob(jobId, jobData) - .then(resp => { + .then((resp) => { if (resp.success) { // Refresh the job data in the job service before resolving. mlJobService @@ -156,14 +156,14 @@ export function updateJobRules(job, detectorIndex, rules) { .then(() => { resolve({ success: true }); }) - .catch(refreshResp => { + .catch((refreshResp) => { reject(refreshResp); }); } else { reject(resp); } }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -175,10 +175,10 @@ export function addItemToFilter(item, filterId) { return new Promise((resolve, reject) => { ml.filters .updateFilter(filterId, undefined, [item], undefined) - .then(updatedFilter => { + .then((updatedFilter) => { resolve(updatedFilter); }) - .catch(error => { + .catch((error) => { reject(error); }); }); diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx index 4ad1139bc9b52..0bd33a8c99f49 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx +++ b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx @@ -32,7 +32,7 @@ interface StatsBarProps { } export const StatsBar: FC = ({ stats, dataTestSub }) => { - const statsList = Object.keys(stats).map(k => stats[k as StatsKey]); + const statsList = Object.keys(stats).map((k) => stats[k as StatsKey]); return (
{statsList diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js index 6001d7cbf6f61..dde6925631d3e 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js @@ -47,7 +47,7 @@ const getDefaultState = () => ({ title: '', }); -const statusToEuiColor = status => { +const statusToEuiColor = (status) => { switch (status) { case VALIDATION_STATUS.INFO: return 'primary'; @@ -60,7 +60,7 @@ const statusToEuiColor = status => { } }; -const statusToEuiIconType = status => { +const statusToEuiIconType = (status) => { switch (status) { case VALIDATION_STATUS.INFO: return 'iInCircle'; @@ -104,7 +104,7 @@ Message.propTypes = { const MessageList = ({ messages, idFilterList }) => { const callouts = messages - .filter(m => idFilterList.includes(m.id) === false) + .filter((m) => idFilterList.includes(m.id) === false) .map((m, i) => ); // there could be no error or success messages due to the @@ -209,7 +209,7 @@ export class ValidateJob extends Component { if (typeof job === 'object') { let shouldShowLoadingIndicator = true; - this.props.mlJobService.validateJob({ duration, fields, job }).then(data => { + this.props.mlJobService.validateJob({ duration, fields, job }).then((data) => { shouldShowLoadingIndicator = false; this.setState({ ...this.state, @@ -224,7 +224,7 @@ export class ValidateJob extends Component { }); if (typeof this.props.setIsValid === 'function') { this.props.setIsValid( - data.messages.some(m => m.status === VALIDATION_STATUS.ERROR) === false + data.messages.some((m) => m.status === VALIDATION_STATUS.ERROR) === false ); } }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss index 89a0018f90401..5508c021d3313 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss @@ -2,3 +2,4 @@ @import 'pages/analytics_management/components/analytics_list/index'; @import 'pages/analytics_management/components/create_analytics_form/index'; @import 'pages/analytics_management/components/create_analytics_flyout/index'; +@import 'pages/analytics_management/components/create_analytics_button/index'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index 7501fe3d82fc6..0b4e6d27b96e5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -24,6 +24,21 @@ export enum ANALYSIS_CONFIG_TYPE { CLASSIFICATION = 'classification', } +export enum ANALYSIS_ADVANCED_FIELDS { + FEATURE_INFLUENCE_THRESHOLD = 'feature_influence_threshold', + GAMMA = 'gamma', + LAMBDA = 'lambda', + MAX_TREES = 'max_trees', + NUM_TOP_FEATURE_IMPORTANCE_VALUES = 'num_top_feature_importance_values', +} + +export enum OUTLIER_ANALYSIS_METHOD { + LOF = 'lof', + LDOF = 'ldof', + DISTANCE_KTH_NN = 'distance_kth_nn', + DISTANCE_KNN = 'distance_knn', +} + interface OutlierAnalysis { [key: string]: {}; outlier_detection: {}; @@ -263,11 +278,13 @@ export const isClassificationAnalysis = (arg: any): arg is ClassificationAnalysi }; export const isResultsSearchBoolQuery = (arg: any): arg is ResultsSearchBoolQuery => { + if (arg === undefined) return false; const keys = Object.keys(arg); return keys.length === 1 && keys[0] === 'bool'; }; export const isQueryStringQuery = (arg: any): arg is QueryStringQuery => { + if (arg === undefined) return false; const keys = Object.keys(arg); return keys.length === 1 && keys[0] === 'query_string'; }; @@ -343,7 +360,7 @@ export const useRefreshAnalyticsList = ( subscriptions.push( distinct$ - .pipe(filter(state => state === REFRESH_ANALYTICS_LIST_STATE.REFRESH)) + .pipe(filter((state) => state === REFRESH_ANALYTICS_LIST_STATE.REFRESH)) .subscribe(() => typeof callback.onRefresh === 'function' && callback.onRefresh()) ); } @@ -351,7 +368,7 @@ export const useRefreshAnalyticsList = ( if (typeof callback.isLoading === 'function') { subscriptions.push( distinct$.subscribe( - state => + (state) => typeof callback.isLoading === 'function' && callback.isLoading(state === REFRESH_ANALYTICS_LIST_STATE.LOADING) ) @@ -359,7 +376,7 @@ export const useRefreshAnalyticsList = ( } return () => { - subscriptions.map(sub => sub.unsubscribe()); + subscriptions.map((sub) => sub.unsubscribe()); }; }, []); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts index 8423bc1b94a09..8db349b395cfc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -51,7 +51,7 @@ export const ML__ID_COPY = 'ml__id_copy'; export const isKeywordAndTextType = (fieldName: string): boolean => { const { fields } = newJobCapsService; - const fieldType = fields.find(field => field.name === fieldName)?.type; + const fieldType = fields.find((field) => field.name === fieldName)?.type; let isBothTypes = false; // If it's a keyword type - check if it has a corresponding text type @@ -192,8 +192,8 @@ export const getDefaultFieldsFromJobCaps = ( featureInfluenceFields.push( ...fields - .filter(d => !jobConfig.analyzed_fields.excludes.includes(d.id)) - .map(d => ({ + .filter((d) => !jobConfig.analyzed_fields.excludes.includes(d.id)) + .map((d) => ({ id: `${resultsField}.${FEATURE_INFLUENCE}.${d.id}`, name: `${resultsField}.${FEATURE_INFLUENCE}.${d.name}`, type: KBN_FIELD_TYPES.NUMBER, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts index 87b8c15aeaa78..eb38a23d10eef 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts @@ -34,7 +34,7 @@ export const getIndexData = async ( try { const sort: EsSorting = sortingColumns - .map(column => { + .map((column) => { const { id } = column; column.id = isKeywordAndTextType(id) ? `${id}.keyword` : id; return column; @@ -57,7 +57,7 @@ export const getIndexData = async ( setRowCount(resp.hits.total.value); - const docs = resp.hits.hits.map(d => d._source); + const docs = resp.hits.hits.map((d) => d._source); setTableItems(docs); setStatus(INDEX_STATUS.LOADED); } catch (e) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts index 12ae4a586e949..9aacf216dd2a5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts @@ -29,13 +29,13 @@ export const getIndexFields = ( const types: FieldTypes = {}; const allFields: string[] = []; - docFields.forEach(field => { + docFields.forEach((field) => { types[field.id] = field.type; allFields.push(field.id); }); return { - defaultSelectedFields: defaultSelected.map(field => field.id), + defaultSelectedFields: defaultSelected.map((field) => field.id), fieldTypes: types, tableFields: allFields, }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts index 400902c152c9e..58343e26153cc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts @@ -17,6 +17,7 @@ export { IndexPattern, REFRESH_ANALYTICS_LIST_STATE, ANALYSIS_CONFIG_TYPE, + OUTLIER_ANALYSIS_METHOD, RegressionEvaluateResponse, getValuesFromResponse, loadEvalData, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts index 0bc9e78207596..2570dd20416be 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts @@ -34,7 +34,7 @@ export const useResultsViewConfig = (jobId: string) => { // get analytics configuration, index pattern and field caps useEffect(() => { - (async function() { + (async function () { setIsLoadingJobConfig(false); try { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx new file mode 100644 index 0000000000000..f957dcab2e87e --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiForm } from '@elastic/eui'; + +import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { AdvancedStepForm } from './advanced_step_form'; +import { AdvancedStepDetails } from './advanced_step_details'; +import { ANALYTICS_STEPS } from '../../page'; + +export const AdvancedStep: FC = ({ + actions, + state, + step, + setCurrentStep, + stepActivated, +}) => { + return ( + + {step === ANALYTICS_STEPS.ADVANCED && ( + + )} + {step !== ANALYTICS_STEPS.ADVANCED && stepActivated === true && ( + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx new file mode 100644 index 0000000000000..a9c8b6d4040ad --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx @@ -0,0 +1,274 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonEmpty, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { + UNSET_CONFIG_ITEM, + State, +} from '../../../analytics_management/hooks/use_create_analytics_form/state'; +import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; +import { ANALYTICS_STEPS } from '../../page'; + +function getStringValue(value: number | undefined) { + return value !== undefined ? `${value}` : UNSET_CONFIG_ITEM; +} + +export interface ListItems { + title: string; + description: string | JSX.Element; +} + +export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ + setCurrentStep, + state, +}) => { + const { form, isJobCreated } = state; + const { + computeFeatureInfluence, + dependentVariable, + eta, + featureBagFraction, + featureInfluenceThreshold, + gamma, + jobType, + lambda, + method, + maxTrees, + modelMemoryLimit, + nNeighbors, + numTopClasses, + numTopFeatureImportanceValues, + outlierFraction, + predictionFieldName, + randomizeSeed, + standardizationEnabled, + } = form; + + const isRegOrClassJob = + jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; + + const advancedFirstCol: ListItems[] = []; + const advancedSecondCol: ListItems[] = []; + const advancedThirdCol: ListItems[] = []; + + const hyperFirstCol: ListItems[] = []; + const hyperSecondCol: ListItems[] = []; + const hyperThirdCol: ListItems[] = []; + + if (jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION) { + advancedFirstCol.push({ + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.computeFeatureInfluence', + { + defaultMessage: 'Compute feature influence', + } + ), + description: computeFeatureInfluence, + }); + + advancedSecondCol.push({ + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.featureInfluenceThreshold', + { + defaultMessage: 'Feature influence threshold', + } + ), + description: getStringValue(featureInfluenceThreshold), + }); + + advancedThirdCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.modelMemoryLimit', { + defaultMessage: 'Model memory limit', + }), + description: `${modelMemoryLimit}`, + }); + + hyperFirstCol.push( + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.nNeighbors', { + defaultMessage: 'N neighbors', + }), + description: getStringValue(nNeighbors), + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.outlierFraction', { + defaultMessage: 'Outlier fraction', + }), + description: getStringValue(outlierFraction), + } + ); + + hyperSecondCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.method', { + defaultMessage: 'Method', + }), + description: method !== undefined ? method : UNSET_CONFIG_ITEM, + }); + + hyperThirdCol.push({ + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.standardizationEnabled', + { + defaultMessage: 'Standardization enabled', + } + ), + description: `${standardizationEnabled}`, + }); + } + + if (isRegOrClassJob) { + if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) { + advancedFirstCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.numTopClasses', { + defaultMessage: 'Top classes', + }), + description: `${numTopClasses}`, + }); + } + + advancedFirstCol.push({ + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.numTopFeatureImportanceValues', + { + defaultMessage: 'Top feature importance values', + } + ), + description: `${numTopFeatureImportanceValues}`, + }); + + hyperFirstCol.push( + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.lambdaFields', { + defaultMessage: 'Lambda', + }), + description: getStringValue(lambda), + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.eta', { + defaultMessage: 'Eta', + }), + description: getStringValue(eta), + } + ); + + advancedSecondCol.push({ + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.predictionFieldName', + { + defaultMessage: 'Prediction field name', + } + ), + description: predictionFieldName ? predictionFieldName : `${dependentVariable}_prediction`, + }); + + hyperSecondCol.push( + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.maxTreesFields', { + defaultMessage: 'Max trees', + }), + description: getStringValue(maxTrees), + }, + { + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.featureBagFraction', + { + defaultMessage: 'Feature bag fraction', + } + ), + description: getStringValue(featureBagFraction), + } + ); + + advancedThirdCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.modelMemoryLimit', { + defaultMessage: 'Model memory limit', + }), + description: `${modelMemoryLimit}`, + }); + + hyperThirdCol.push( + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.gamma', { + defaultMessage: 'Gamma', + }), + description: getStringValue(gamma), + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.randomizedSeed', { + defaultMessage: 'Randomized seed', + }), + description: getStringValue(randomizeSeed), + } + ); + } + + return ( + + +

+ {i18n.translate('xpack.ml.dataframe.analytics.create.advancedConfigDetailsTitle', { + defaultMessage: 'Advanced configuration', + })} +

+
+ + + + + + + + + + + + + + +

+ {i18n.translate('xpack.ml.dataframe.analytics.create.hyperParametersDetailsTitle', { + defaultMessage: 'Hyper parameters', + })} +

+
+ + + + + + + + + + + + + + {!isJobCreated && ( + { + setCurrentStep(ANALYTICS_STEPS.ADVANCED); + }} + > + {i18n.translate('xpack.ml.dataframe.analytics.create.advancedDetails.editButtonText', { + defaultMessage: 'Edit', + })} + + )} +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx new file mode 100644 index 0000000000000..8b137ac72361c --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx @@ -0,0 +1,332 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useMemo } from 'react'; +import { + EuiAccordion, + EuiFieldNumber, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { HyperParameters } from './hyper_parameters'; +import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { getModelMemoryLimitErrors } from '../../../analytics_management/hooks/use_create_analytics_form/reducer'; +import { + ANALYSIS_CONFIG_TYPE, + NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN, +} from '../../../../common/analytics'; +import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../analytics_management/hooks/use_create_analytics_form/state'; +import { ANALYTICS_STEPS } from '../../page'; +import { ContinueButton } from '../continue_button'; +import { OutlierHyperParameters } from './outlier_hyper_parameters'; + +export function getNumberValue(value?: number) { + return value === undefined ? '' : +value; +} + +export const AdvancedStepForm: FC = ({ + actions, + state, + setCurrentStep, +}) => { + const { setFormState } = actions; + const { form, isJobCreated } = state; + const { + computeFeatureInfluence, + featureInfluenceThreshold, + jobType, + modelMemoryLimit, + modelMemoryLimitValidationResult, + numTopClasses, + numTopFeatureImportanceValues, + numTopFeatureImportanceValuesValid, + predictionFieldName, + } = form; + + const mmlErrors = useMemo(() => getModelMemoryLimitErrors(modelMemoryLimitValidationResult), [ + modelMemoryLimitValidationResult, + ]); + + const isRegOrClassJob = + jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; + + const mmlInvalid = modelMemoryLimitValidationResult !== null; + + const outlierDetectionAdvancedConfig = ( + + + + { + setFormState({ + computeFeatureInfluence: e.target.value, + }); + }} + /> + + + + + + setFormState({ + featureInfluenceThreshold: e.target.value === '' ? undefined : +e.target.value, + }) + } + data-test-subj="mlAnalyticsCreateJobWizardFeatureInfluenceThresholdInput" + min={0} + max={1} + step={0.001} + value={getNumberValue(featureInfluenceThreshold)} + /> + + + + ); + + const regAndClassAdvancedConfig = ( + + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.numTopFeatureImportanceValuesErrorText', + { + defaultMessage: 'Invalid maximum number of feature importance values.', + } + )} + , + ] + : []), + ]} + > + + setFormState({ + numTopFeatureImportanceValues: e.target.value === '' ? undefined : +e.target.value, + }) + } + step={1} + value={getNumberValue(numTopFeatureImportanceValues)} + /> + + + + _prediction.', + } + )} + > + setFormState({ predictionFieldName: e.target.value })} + data-test-subj="mlAnalyticsCreateJobWizardPredictionFieldNameInput" + /> + + + + ); + + return ( + + +

+ {i18n.translate('xpack.ml.dataframe.analytics.create.advancedConfigSectionTitle', { + defaultMessage: 'Advanced configuration', + })} +

+
+ + {jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && outlierDetectionAdvancedConfig} + {isRegOrClassJob && regAndClassAdvancedConfig} + {jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && ( + + + + setFormState({ + numTopClasses: e.target.value === '' ? undefined : +e.target.value, + }) + } + step={1} + value={getNumberValue(numTopClasses)} + /> + + + )} + + + setFormState({ modelMemoryLimit: e.target.value })} + isInvalid={mmlInvalid} + data-test-subj="mlAnalyticsCreateJobWizardModelMemoryInput" + /> + + + + + +

+ {i18n.translate('xpack.ml.dataframe.analytics.create.hyperParametersSectionTitle', { + defaultMessage: 'Hyper parameters', + })} +

+ + } + initialIsOpen={false} + data-test-subj="mlAnalyticsCreateJobWizardHyperParametersSection" + > + + {jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && ( + + )} + {isRegOrClassJob && } + +
+ + { + setCurrentStep(ANALYTICS_STEPS.DETAILS); + }} + /> +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx new file mode 100644 index 0000000000000..144a062106003 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment } from 'react'; +import { EuiFieldNumber, EuiFlexItem, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { getNumberValue } from './advanced_step_form'; + +const MAX_TREES_LIMIT = 2000; + +export const HyperParameters: FC = ({ actions, state }) => { + const { setFormState } = actions; + + const { eta, featureBagFraction, gamma, lambda, maxTrees, randomizeSeed } = state.form; + + return ( + + + + + setFormState({ lambda: e.target.value === '' ? undefined : +e.target.value }) + } + step={0.001} + min={0} + value={getNumberValue(lambda)} + /> + + + + + + setFormState({ maxTrees: e.target.value === '' ? undefined : +e.target.value }) + } + isInvalid={maxTrees !== undefined && !Number.isInteger(maxTrees)} + step={1} + min={1} + max={MAX_TREES_LIMIT} + value={getNumberValue(maxTrees)} + /> + + + + + + setFormState({ gamma: e.target.value === '' ? undefined : +e.target.value }) + } + step={0.001} + min={0} + value={getNumberValue(gamma)} + /> + + + + + + setFormState({ eta: e.target.value === '' ? undefined : +e.target.value }) + } + step={0.001} + min={0.001} + max={1} + value={getNumberValue(eta)} + /> + + + + + + setFormState({ + featureBagFraction: e.target.value === '' ? undefined : +e.target.value, + }) + } + isInvalid={ + featureBagFraction !== undefined && + (featureBagFraction > 1 || featureBagFraction <= 0) + } + step={0.001} + max={1} + value={getNumberValue(featureBagFraction)} + /> + + + + + + setFormState({ randomizeSeed: e.target.value === '' ? undefined : +e.target.value }) + } + isInvalid={randomizeSeed !== undefined && typeof randomizeSeed !== 'number'} + value={getNumberValue(randomizeSeed)} + step={1} + /> + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/index.ts new file mode 100644 index 0000000000000..6a19e55b533c1 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedStep } from './advanced_step'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/outlier_hyper_parameters.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/outlier_hyper_parameters.tsx new file mode 100644 index 0000000000000..dfe7969d8a6d9 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/outlier_hyper_parameters.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment } from 'react'; +import { EuiFieldNumber, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { OUTLIER_ANALYSIS_METHOD } from '../../../../common/analytics'; +import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { getNumberValue } from './advanced_step_form'; + +export const OutlierHyperParameters: FC = ({ actions, state }) => { + const { setFormState } = actions; + + const { method, nNeighbors, outlierFraction, standardizationEnabled } = state.form; + + return ( + + + + ({ + value: outlierMethod, + text: outlierMethod, + }))} + value={method} + hasNoInitialSelection={true} + onChange={(e) => { + setFormState({ method: e.target.value }); + }} + data-test-subj="mlAnalyticsCreateJobWizardMethodInput" + /> + + + + + + setFormState({ nNeighbors: e.target.value === '' ? undefined : +e.target.value }) + } + step={1} + min={1} + value={getNumberValue(nNeighbors)} + /> + + + + + + setFormState({ outlierFraction: e.target.value === '' ? undefined : +e.target.value }) + } + step={0.001} + min={0} + max={1} + value={getNumberValue(outlierFraction)} + /> + + + + + { + setFormState({ standardizationEnabled: e.target.value }); + }} + /> + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx new file mode 100644 index 0000000000000..e437d27372a3e --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment } from 'react'; +import { EuiCard, EuiHorizontalRule, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +function redirectToAnalyticsManagementPage() { + window.location.href = '#/data_frame_analytics?'; +} + +export const BackToListPanel: FC = () => ( + + + } + title={i18n.translate('xpack.ml.dataframe.analytics.create.analyticsListCardTitle', { + defaultMessage: 'Data Frame Analytics', + })} + description={i18n.translate( + 'xpack.ml.dataframe.analytics.create.analyticsListCardDescription', + { + defaultMessage: 'Return to the analytics management page.', + } + )} + onClick={redirectToAnalyticsManagementPage} + data-test-subj="analyticsWizardCardManagement" + /> + +); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/index.ts new file mode 100644 index 0000000000000..4da6561562879 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { BackToListPanel } from './back_to_list_panel'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx new file mode 100644 index 0000000000000..ad540285e49f0 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, memo, useEffect, useState } from 'react'; +import { EuiCallOut, EuiFormRow, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +// @ts-ignore no declaration +import { LEFT_ALIGNMENT, CENTER_ALIGNMENT, SortableProperties } from '@elastic/eui/lib/services'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { FieldSelectionItem } from '../../../../common/analytics'; +// @ts-ignore could not find declaration file +import { CustomSelectionTable } from '../../../../../components/custom_selection_table'; + +const columns = [ + { + id: 'checkbox', + isCheckbox: true, + textOnly: false, + width: '32px', + }, + { + label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.fieldNameColumn', { + defaultMessage: 'Field name', + }), + id: 'name', + isSortable: true, + alignment: LEFT_ALIGNMENT, + }, + { + id: 'mapping_types', + label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.mappingColumn', { + defaultMessage: 'Mapping', + }), + isSortable: false, + alignment: LEFT_ALIGNMENT, + }, + { + label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.isIncludedColumn', { + defaultMessage: 'Is included', + }), + id: 'is_included', + alignment: LEFT_ALIGNMENT, + isSortable: true, + // eslint-disable-next-line @typescript-eslint/camelcase + render: ({ is_included }: { is_included: boolean }) => (is_included ? 'Yes' : 'No'), + }, + { + label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.isRequiredColumn', { + defaultMessage: 'Is required', + }), + id: 'is_required', + alignment: LEFT_ALIGNMENT, + isSortable: true, + // eslint-disable-next-line @typescript-eslint/camelcase + render: ({ is_required }: { is_required: boolean }) => (is_required ? 'Yes' : 'No'), + }, + { + label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.reasonColumn', { + defaultMessage: 'Reason', + }), + id: 'reason', + alignment: LEFT_ALIGNMENT, + isSortable: false, + }, +]; + +const checkboxDisabledCheck = (item: FieldSelectionItem) => + (item.is_included === false && !item.reason?.includes('in excludes list')) || + item.is_required === true; + +export const MemoizedAnalysisFieldsTable: FC<{ + excludes: string[]; + loadingItems: boolean; + setFormState: any; + tableItems: FieldSelectionItem[]; +}> = memo( + ({ excludes, loadingItems, setFormState, tableItems }) => { + const [sortableProperties, setSortableProperties] = useState(); + const [currentSelection, setCurrentSelection] = useState([]); + + useEffect(() => { + if (excludes.length > 0) { + setCurrentSelection(excludes); + } + }, []); + + // Only set form state on unmount to prevent re-renders due to props changing if exludes was updated on each selection + useEffect(() => { + return () => { + setFormState({ excludes: currentSelection }); + }; + }, [currentSelection]); + + useEffect(() => { + let sortablePropertyItems = []; + const defaultSortProperty = 'name'; + + sortablePropertyItems = [ + { + name: 'name', + getValue: (item: any) => item.name.toLowerCase(), + isAscending: true, + }, + { + name: 'is_included', + getValue: (item: any) => item.is_included, + isAscending: true, + }, + { + name: 'is_required', + getValue: (item: any) => item.is_required, + isAscending: true, + }, + ]; + const sortableProps = new SortableProperties(sortablePropertyItems, defaultSortProperty); + + setSortableProperties(sortableProps); + }, []); + + const filters = [ + { + type: 'field_value_selection', + field: 'is_included', + name: i18n.translate('xpack.ml.dataframe.analytics.create.excludedFilterLabel', { + defaultMessage: 'Is included', + }), + multiSelect: false, + options: [ + { + value: true, + view: ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.isIncludedOption', { + defaultMessage: 'Yes', + })} + + ), + }, + { + value: false, + view: ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.isNotIncludedOption', { + defaultMessage: 'No', + })} + + ), + }, + ], + }, + ]; + + return ( + + + + + {tableItems.length === 0 && ( + + + + )} + {tableItems.length > 0 && ( + + { + setCurrentSelection(selection); + }} + selectedIds={currentSelection} + singleSelection={false} + sortableProperties={sortableProperties} + tableItemId={'name'} + /> + + )} + + + ); + }, + (prevProps, nextProps) => prevProps.tableItems.length === nextProps.tableItems.length +); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx new file mode 100644 index 0000000000000..220910535aafe --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiForm } from '@elastic/eui'; + +import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { ConfigurationStepDetails } from './configuration_step_details'; +import { ConfigurationStepForm } from './configuration_step_form'; +import { ANALYTICS_STEPS } from '../../page'; + +export const ConfigurationStep: FC = ({ + actions, + state, + setCurrentStep, + step, + stepActivated, +}) => { + return ( + + {step === ANALYTICS_STEPS.CONFIGURATION && ( + + )} + {step !== ANALYTICS_STEPS.CONFIGURATION && stepActivated === true && ( + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx new file mode 100644 index 0000000000000..6603af9aa302e --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonEmpty, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import { + State, + UNSET_CONFIG_ITEM, +} from '../../../analytics_management/hooks/use_create_analytics_form/state'; +import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; +import { useMlContext } from '../../../../../contexts/ml'; +import { ANALYTICS_STEPS } from '../../page'; + +interface Props { + setCurrentStep: React.Dispatch>; + state: State; +} + +export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) => { + const mlContext = useMlContext(); + const { currentIndexPattern } = mlContext; + const { form, isJobCreated } = state; + const { dependentVariable, excludes, jobConfigQueryString, jobType, trainingPercent } = form; + + const isJobTypeWithDepVar = + jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; + + const detailsFirstCol = [ + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.sourceIndex', { + defaultMessage: 'Source index', + }), + description: currentIndexPattern.title || UNSET_CONFIG_ITEM, + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.Query', { + defaultMessage: 'Query', + }), + description: jobConfigQueryString || UNSET_CONFIG_ITEM, + }, + ]; + + const detailsSecondCol = [ + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobType', { + defaultMessage: 'Job type', + }), + description: jobType! as string, + }, + ]; + + const detailsThirdCol = [ + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.excludedFields', { + defaultMessage: 'Excluded fields', + }), + description: excludes.length > 0 ? excludes.join(', ') : UNSET_CONFIG_ITEM, + }, + ]; + + if (isJobTypeWithDepVar) { + detailsSecondCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.trainingPercent', { + defaultMessage: 'Training percent', + }), + description: `${trainingPercent}`, + }); + detailsThirdCol.unshift({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.dependentVariable', { + defaultMessage: 'Dependent variable', + }), + description: dependentVariable, + }); + } + + return ( + + + + + + + + + + + + + + {!isJobCreated && ( + { + setCurrentStep(ANALYTICS_STEPS.CONFIGURATION); + }} + > + {i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.editButtonText', { + defaultMessage: 'Edit', + })} + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx new file mode 100644 index 0000000000000..9446dfd4ed525 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -0,0 +1,449 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useEffect, useRef } from 'react'; +import { EuiBadge, EuiComboBox, EuiFormRow, EuiRange, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { debounce } from 'lodash'; + +import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; +import { useMlContext } from '../../../../../contexts/ml'; + +import { + DfAnalyticsExplainResponse, + FieldSelectionItem, + ANALYSIS_CONFIG_TYPE, + TRAINING_PERCENT_MIN, + TRAINING_PERCENT_MAX, +} from '../../../../common/analytics'; +import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { Messages } from '../../../analytics_management/components/create_analytics_form/messages'; +import { + DEFAULT_MODEL_MEMORY_LIMIT, + getJobConfigFromFormState, + State, +} from '../../../analytics_management/hooks/use_create_analytics_form/state'; +import { shouldAddAsDepVarOption } from '../../../analytics_management/components/create_analytics_form/form_options_validation'; +import { ml } from '../../../../../services/ml_api_service'; +import { getToastNotifications } from '../../../../../util/dependency_cache'; + +import { ANALYTICS_STEPS } from '../../page'; +import { ContinueButton } from '../continue_button'; +import { JobType } from './job_type'; +import { SupportedFieldsMessage } from './supported_fields_message'; +import { MemoizedAnalysisFieldsTable } from './analysis_fields_table'; +import { DataGrid } from '../../../../../components/data_grid'; +import { useIndexData } from '../../hooks'; +import { ExplorationQueryBar } from '../../../analytics_exploration/components/exploration_query_bar'; +import { useSavedSearch } from './use_saved_search'; + +const requiredFieldsErrorText = i18n.translate( + 'xpack.ml.dataframe.analytics.createWizard.requiredFieldsErrorMessage', + { + defaultMessage: 'At least one field must be included in the analysis.', + } +); + +export const ConfigurationStepForm: FC = ({ + actions, + state, + setCurrentStep, +}) => { + const mlContext = useMlContext(); + const { currentSavedSearch, currentIndexPattern } = mlContext; + const { savedSearchQuery, savedSearchQueryStr } = useSavedSearch(); + + const { initiateWizard, setEstimatedModelMemoryLimit, setFormState } = actions; + const { estimatedModelMemoryLimit, form, isJobCreated, requestMessages } = state; + const firstUpdate = useRef(true); + const { + dependentVariable, + dependentVariableFetchFail, + dependentVariableOptions, + excludes, + excludesTableItems, + fieldOptionsFetchFail, + jobConfigQuery, + jobConfigQueryString, + jobType, + loadingDepVarOptions, + loadingFieldOptions, + maxDistinctValuesError, + modelMemoryLimit, + previousJobType, + requiredFieldsError, + trainingPercent, + } = form; + + const setJobConfigQuery = ({ query, queryString }: { query: any; queryString: string }) => { + setFormState({ jobConfigQuery: query, jobConfigQueryString: queryString }); + }; + + const indexData = useIndexData( + currentIndexPattern, + savedSearchQuery !== undefined ? savedSearchQuery : jobConfigQuery + ); + + const indexPreviewProps = { + ...indexData, + dataTestSubj: 'mlAnalyticsCreationDataGrid', + toastNotifications: getToastNotifications(), + }; + + const isJobTypeWithDepVar = + jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; + + const dependentVariableEmpty = isJobTypeWithDepVar && dependentVariable === ''; + + const isStepInvalid = + dependentVariableEmpty || + jobType === undefined || + maxDistinctValuesError !== undefined || + requiredFieldsError !== undefined; + + const loadDepVarOptions = async (formState: State['form']) => { + setFormState({ + loadingDepVarOptions: true, + maxDistinctValuesError: undefined, + }); + try { + if (currentIndexPattern !== undefined) { + const formStateUpdate: { + loadingDepVarOptions: boolean; + dependentVariableFetchFail: boolean; + dependentVariableOptions: State['form']['dependentVariableOptions']; + dependentVariable?: State['form']['dependentVariable']; + } = { + loadingDepVarOptions: false, + dependentVariableFetchFail: false, + dependentVariableOptions: [] as State['form']['dependentVariableOptions'], + }; + + // Get fields and filter for supported types for job type + const { fields } = newJobCapsService; + + let resetDependentVariable = true; + for (const field of fields) { + if (shouldAddAsDepVarOption(field, jobType)) { + formStateUpdate.dependentVariableOptions.push({ + label: field.id, + }); + + if (formState.dependentVariable === field.id) { + resetDependentVariable = false; + } + } + } + + if (resetDependentVariable) { + formStateUpdate.dependentVariable = ''; + } + + setFormState(formStateUpdate); + } + } catch (e) { + setFormState({ loadingDepVarOptions: false, dependentVariableFetchFail: true }); + } + }; + + const debouncedGetExplainData = debounce(async () => { + const jobTypeChanged = previousJobType !== jobType; + const shouldUpdateModelMemoryLimit = !firstUpdate.current || !modelMemoryLimit; + const shouldUpdateEstimatedMml = + !firstUpdate.current || !modelMemoryLimit || estimatedModelMemoryLimit === ''; + + if (firstUpdate.current) { + firstUpdate.current = false; + } + // Reset if jobType changes (jobType requires dependent_variable to be set - + // which won't be the case if switching from outlier detection) + if (jobTypeChanged) { + setFormState({ + loadingFieldOptions: true, + }); + } + + try { + const jobConfig = getJobConfigFromFormState(form); + delete jobConfig.dest; + delete jobConfig.model_memory_limit; + const resp: DfAnalyticsExplainResponse = await ml.dataFrameAnalytics.explainDataFrameAnalytics( + jobConfig + ); + const expectedMemoryWithoutDisk = resp.memory_estimation?.expected_memory_without_disk; + + if (shouldUpdateEstimatedMml) { + setEstimatedModelMemoryLimit(expectedMemoryWithoutDisk); + } + + const fieldSelection: FieldSelectionItem[] | undefined = resp.field_selection; + + let hasRequiredFields = false; + if (fieldSelection) { + for (let i = 0; i < fieldSelection.length; i++) { + const field = fieldSelection[i]; + if (field.is_included === true && field.is_required === false) { + hasRequiredFields = true; + break; + } + } + } + + // If job type has changed load analysis field options again + if (jobTypeChanged) { + setFormState({ + ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), + excludesTableItems: fieldSelection ? fieldSelection : [], + loadingFieldOptions: false, + fieldOptionsFetchFail: false, + maxDistinctValuesError: undefined, + requiredFieldsError: !hasRequiredFields ? requiredFieldsErrorText : undefined, + }); + } else { + setFormState({ + ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), + requiredFieldsError: !hasRequiredFields ? requiredFieldsErrorText : undefined, + }); + } + } catch (e) { + let errorMessage; + if ( + jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && + e.body && + e.body.message !== undefined && + e.body.message.includes('status_exception') && + (e.body.message.includes('must have at most') || + e.body.message.includes('must have at least')) + ) { + errorMessage = e.body.message; + } + const fallbackModelMemoryLimit = + jobType !== undefined + ? DEFAULT_MODEL_MEMORY_LIMIT[jobType] + : DEFAULT_MODEL_MEMORY_LIMIT.outlier_detection; + setEstimatedModelMemoryLimit(fallbackModelMemoryLimit); + setFormState({ + fieldOptionsFetchFail: true, + maxDistinctValuesError: errorMessage, + loadingFieldOptions: false, + ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: fallbackModelMemoryLimit } : {}), + }); + } + }, 300); + + useEffect(() => { + initiateWizard(); + }, []); + + useEffect(() => { + setFormState({ sourceIndex: currentIndexPattern.title }); + }, []); + + useEffect(() => { + if (savedSearchQueryStr !== undefined) { + setFormState({ jobConfigQuery: savedSearchQuery, jobConfigQueryString: savedSearchQueryStr }); + } + }, [JSON.stringify(savedSearchQuery), savedSearchQueryStr]); + + useEffect(() => { + if (isJobTypeWithDepVar) { + loadDepVarOptions(form); + } + }, [jobType]); + + useEffect(() => { + const hasBasicRequiredFields = jobType !== undefined; + + const hasRequiredAnalysisFields = + (isJobTypeWithDepVar && dependentVariable !== '') || + jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION; + + if (hasBasicRequiredFields && hasRequiredAnalysisFields) { + debouncedGetExplainData(); + } + + return () => { + debouncedGetExplainData.cancel(); + }; + }, [jobType, dependentVariable, trainingPercent, JSON.stringify(excludes), jobConfigQueryString]); + + return ( + + + + + {savedSearchQuery === undefined && ( + + + + )} + + {savedSearchQuery !== undefined && ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.savedSearchLabel', { + defaultMessage: 'Saved search', + })} + + )} + + {savedSearchQuery !== undefined + ? currentSavedSearch?.attributes.title + : currentIndexPattern.title} + + + } + fullWidth + > + + + {isJobTypeWithDepVar && ( + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.dependentVariableOptionsFetchError', + { + defaultMessage: + 'There was a problem fetching fields. Please refresh the page and try again.', + } + )} + , + ] + : []), + ...(fieldOptionsFetchFail === true && maxDistinctValuesError !== undefined + ? [ + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.dependentVariableMaxDistictValuesError', + { + defaultMessage: 'Invalid. {message}', + values: { message: maxDistinctValuesError }, + } + )} + , + ] + : []), + ]} + > + + setFormState({ + dependentVariable: selectedOptions[0].label || '', + }) + } + isClearable={false} + isInvalid={dependentVariable === ''} + data-test-subj="mlAnalyticsCreateJobWizardDependentVariableSelect" + /> + + + )} + + + + + {isJobTypeWithDepVar && ( + + setFormState({ trainingPercent: +e.target.value })} + data-test-subj="mlAnalyticsCreateJobWizardTrainingPercentSlider" + /> + + )} + + { + setCurrentStep(ANALYTICS_STEPS.ADVANCED); + }} + /> + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/index.ts new file mode 100644 index 0000000000000..ba67a6f080d45 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConfigurationStep } from './configuration_step'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx new file mode 100644 index 0000000000000..f31c9cd28f65a --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, FC } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { ANALYSIS_CONFIG_TYPE } from '../../../../common'; + +import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state'; + +interface Props { + type: AnalyticsJobType; + setFormState: React.Dispatch>; +} + +export const JobType: FC = ({ type, setFormState }) => { + const outlierHelpText = i18n.translate( + 'xpack.ml.dataframe.analytics.create.outlierDetectionHelpText', + { + defaultMessage: + 'Outlier detection jobs require a source index that is mapped as a table-like data structure and analyze only numeric and boolean fields. Use the advanced editor to add custom options to the configuration.', + } + ); + + const regressionHelpText = i18n.translate( + 'xpack.ml.dataframe.analytics.create.outlierRegressionHelpText', + { + defaultMessage: + 'Regression jobs analyze only numeric fields. Use the advanced editor to apply custom options, such as the prediction field name.', + } + ); + + const classificationHelpText = i18n.translate( + 'xpack.ml.dataframe.analytics.create.classificationHelpText', + { + defaultMessage: + 'Classification jobs require a source index that is mapped as a table-like data structure and support fields that are numeric, boolean, text, keyword, or ip. Use the advanced editor to apply custom options, such as the prediction field name.', + } + ); + + const helpText = { + [ANALYSIS_CONFIG_TYPE.REGRESSION]: regressionHelpText, + [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: outlierHelpText, + [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: classificationHelpText, + }; + + return ( + + + ({ + value: jobType, + text: jobType.replace(/_/g, ' '), + 'data-test-subj': `mlAnalyticsCreation-${jobType}-option`, + }))} + value={type} + hasNoInitialSelection={true} + onChange={(e) => { + const value = e.target.value as AnalyticsJobType; + setFormState({ + previousJobType: type, + jobType: value, + excludes: [], + requiredFieldsError: undefined, + }); + }} + data-test-subj="mlAnalyticsCreateJobWizardJobTypeSelect" + /> + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx new file mode 100644 index 0000000000000..fe13cc1d6edfc --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState, useEffect } from 'react'; +import { EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state'; +import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; +import { Field, EVENT_RATE_FIELD_ID } from '../../../../../../../common/types/fields'; +import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES } from '../../../../common/fields'; +import { + OMIT_FIELDS, + CATEGORICAL_TYPES, +} from '../../../analytics_management/components/create_analytics_form/form_options_validation'; +import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; +import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; + +const containsClassificationFieldsCb = ({ name, type }: Field) => + !OMIT_FIELDS.includes(name) && + name !== EVENT_RATE_FIELD_ID && + (BASIC_NUMERICAL_TYPES.has(type) || + CATEGORICAL_TYPES.has(type) || + type === ES_FIELD_TYPES.BOOLEAN); + +const containsRegressionFieldsCb = ({ name, type }: Field) => + !OMIT_FIELDS.includes(name) && + name !== EVENT_RATE_FIELD_ID && + (BASIC_NUMERICAL_TYPES.has(type) || EXTENDED_NUMERICAL_TYPES.has(type)); + +const containsOutlierFieldsCb = ({ name, type }: Field) => + !OMIT_FIELDS.includes(name) && name !== EVENT_RATE_FIELD_ID && BASIC_NUMERICAL_TYPES.has(type); + +const callbacks: Record boolean> = { + [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: containsClassificationFieldsCb, + [ANALYSIS_CONFIG_TYPE.REGRESSION]: containsRegressionFieldsCb, + [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: containsOutlierFieldsCb, +}; + +const messages: Record = { + [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: ( + + ), + [ANALYSIS_CONFIG_TYPE.REGRESSION]: ( + + ), + [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: ( + + ), +}; + +interface Props { + jobType: AnalyticsJobType; +} + +export const SupportedFieldsMessage: FC = ({ jobType }) => { + const [sourceIndexContainsSupportedFields, setSourceIndexContainsSupportedFields] = useState< + boolean + >(true); + const [sourceIndexFieldsCheckFailed, setSourceIndexFieldsCheckFailed] = useState(false); + const { fields } = newJobCapsService; + + // Find out if index pattern contains supported fields for job type. Provides a hint in the form + // that job may not run correctly if no supported fields are found. + const validateFields = () => { + if (fields && jobType !== undefined) { + try { + const containsSupportedFields: boolean = fields.some(callbacks[jobType]); + + setSourceIndexContainsSupportedFields(containsSupportedFields); + setSourceIndexFieldsCheckFailed(false); + } catch (e) { + setSourceIndexFieldsCheckFailed(true); + } + } + }; + + useEffect(() => { + if (jobType !== undefined) { + setSourceIndexContainsSupportedFields(true); + setSourceIndexFieldsCheckFailed(false); + validateFields(); + } + }, [jobType]); + + if (sourceIndexContainsSupportedFields === true) return null; + + if (sourceIndexFieldsCheckFailed === true) { + return ( + + + + + ); + } + + return ( + + + {jobType !== undefined && messages[jobType]} + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts new file mode 100644 index 0000000000000..856358538b26f --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState, useEffect } from 'react'; +import { useMlContext } from '../../../../../contexts/ml'; +import { esQuery, esKuery } from '../../../../../../../../../../src/plugins/data/public'; +import { SEARCH_QUERY_LANGUAGE } from '../../../../../../../common/constants/search'; +import { getQueryFromSavedSearch } from '../../../../../util/index_utils'; + +export function useSavedSearch() { + const [savedSearchQuery, setSavedSearchQuery] = useState(undefined); + const [savedSearchQueryStr, setSavedSearchQueryStr] = useState(undefined); + + const mlContext = useMlContext(); + const { currentSavedSearch, currentIndexPattern, kibanaConfig } = mlContext; + + const getQueryData = () => { + let qry; + let qryString; + + if (currentSavedSearch !== null) { + const { query } = getQueryFromSavedSearch(currentSavedSearch); + const queryLanguage = query.language as SEARCH_QUERY_LANGUAGE; + qryString = query.query; + + if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) { + const ast = esKuery.fromKueryExpression(qryString); + qry = esKuery.toElasticsearchQuery(ast, currentIndexPattern); + } else { + qry = esQuery.luceneStringToDsl(qryString); + esQuery.decorateQuery(qry, kibanaConfig.get('query:queryString:options')); + } + + setSavedSearchQuery(qry); + setSavedSearchQueryStr(qryString); + } + }; + + useEffect(() => { + getQueryData(); + }, []); + + return { + savedSearchQuery, + savedSearchQueryStr, + }; +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/continue_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/continue_button.tsx new file mode 100644 index 0000000000000..6e95a0a246573 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/continue_button.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const continueButtonText = i18n.translate( + 'xpack.ml.dataframe.analytics.creation.continueButtonText', + { + defaultMessage: 'Continue', + } +); + +export const ContinueButton: FC<{ isDisabled: boolean; onClick: any }> = ({ + isDisabled, + onClick, +}) => ( + + + + {continueButtonText} + + + +); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx new file mode 100644 index 0000000000000..2dda5f5d819b7 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState } from 'react'; +import { + EuiButton, + EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { Messages } from '../../../analytics_management/components/create_analytics_form/messages'; +import { ANALYTICS_STEPS } from '../../page'; +import { BackToListPanel } from '../back_to_list_panel'; + +interface Props extends CreateAnalyticsFormProps { + step: ANALYTICS_STEPS; +} + +export const CreateStep: FC = ({ actions, state, step }) => { + const { createAnalyticsJob, startAnalyticsJob } = actions; + const { + isAdvancedEditorValidJson, + isJobCreated, + isJobStarted, + isModalButtonDisabled, + isValid, + requestMessages, + } = state; + + const [checked, setChecked] = useState(true); + + if (step !== ANALYTICS_STEPS.CREATE) return null; + + const handleCreation = async () => { + await createAnalyticsJob(); + + if (checked) { + startAnalyticsJob(); + } + }; + + return ( + + {!isJobCreated && !isJobStarted && ( + + + + setChecked(e.target.checked)} + /> + + + + + {i18n.translate('xpack.ml.dataframe.analytics.create.wizardCreateButton', { + defaultMessage: 'Create', + })} + + + + )} + + + {isJobCreated === true && } + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/index.ts new file mode 100644 index 0000000000000..01c8e4bff934d --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { CreateStep } from './create_step'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx new file mode 100644 index 0000000000000..a40813ed2fc3e --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiForm } from '@elastic/eui'; + +import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { DetailsStepDetails } from './details_step_details'; +import { DetailsStepForm } from './details_step_form'; +import { ANALYTICS_STEPS } from '../../page'; + +export const DetailsStep: FC = ({ + actions, + state, + setCurrentStep, + step, + stepActivated, +}) => { + return ( + + {step === ANALYTICS_STEPS.DETAILS && ( + + )} + {step !== ANALYTICS_STEPS.DETAILS && stepActivated === true && ( + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx new file mode 100644 index 0000000000000..a4d86b48006e8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonEmpty, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import { State } from '../../../analytics_management/hooks/use_create_analytics_form/state'; +import { ANALYTICS_STEPS } from '../../page'; + +export interface ListItems { + title: string; + description: string | JSX.Element; +} + +export const DetailsStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ + setCurrentStep, + state, +}) => { + const { form, isJobCreated } = state; + const { description, jobId, destinationIndex } = form; + + const detailsFirstCol: ListItems[] = [ + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobId', { + defaultMessage: 'Job ID', + }), + description: jobId, + }, + ]; + + const detailsSecondCol: ListItems[] = [ + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobDescription', { + defaultMessage: 'Job description', + }), + description, + }, + ]; + + const detailsThirdCol: ListItems[] = [ + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.destIndex', { + defaultMessage: 'Destination index', + }), + description: destinationIndex || '', + }, + ]; + + return ( + + + + + + + + + + + + + + {!isJobCreated && ( + { + setCurrentStep(ANALYTICS_STEPS.DETAILS); + }} + > + {i18n.translate('xpack.ml.dataframe.analytics.create.detailsDetails.editButtonText', { + defaultMessage: 'Edit', + })} + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx new file mode 100644 index 0000000000000..67f8472e7ad14 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx @@ -0,0 +1,221 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useRef } from 'react'; +import { EuiFieldText, EuiFormRow, EuiLink, EuiSpacer, EuiSwitch, EuiTextArea } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { useMlKibana } from '../../../../../contexts/kibana'; +import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { JOB_ID_MAX_LENGTH } from '../../../../../../../common/constants/validation'; +import { ContinueButton } from '../continue_button'; +import { ANALYTICS_STEPS } from '../../page'; + +export const DetailsStepForm: FC = ({ + actions, + state, + setCurrentStep, +}) => { + const { + services: { docLinks }, + } = useMlKibana(); + const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; + + const { setFormState } = actions; + const { form, isJobCreated } = state; + const { + createIndexPattern, + description, + destinationIndex, + destinationIndexNameEmpty, + destinationIndexNameExists, + destinationIndexNameValid, + destinationIndexPatternTitleExists, + jobId, + jobIdEmpty, + jobIdExists, + jobIdInvalidMaxLength, + jobIdValid, + } = form; + const forceInput = useRef(null); + + const isStepInvalid = + jobIdEmpty === true || + jobIdExists === true || + jobIdValid === false || + destinationIndexNameEmpty === true || + destinationIndexNameValid === false || + (destinationIndexPatternTitleExists === true && createIndexPattern === true); + + return ( + + + { + if (input) { + forceInput.current = input; + } + }} + disabled={isJobCreated} + placeholder={i18n.translate('xpack.ml.dataframe.analytics.create.jobIdPlaceholder', { + defaultMessage: 'Job ID', + })} + value={jobId} + onChange={(e) => setFormState({ jobId: e.target.value })} + aria-label={i18n.translate('xpack.ml.dataframe.analytics.create.jobIdInputAriaLabel', { + defaultMessage: 'Choose a unique analytics job ID.', + })} + isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists || jobIdEmpty} + data-test-subj="mlAnalyticsCreateJobFlyoutJobIdInput" + /> + + + { + const value = e.target.value; + setFormState({ description: value }); + }} + data-test-subj="mlDFAnalyticsJobCreationJobDescription" + /> + + + {i18n.translate('xpack.ml.dataframe.analytics.create.destinationIndexInvalidError', { + defaultMessage: 'Invalid destination index name.', + })} +
+ + {i18n.translate( + 'xpack.ml.dataframe.stepDetailsForm.destinationIndexInvalidErrorLink', + { + defaultMessage: 'Learn more about index name limitations.', + } + )} + +
, + ] + } + > + setFormState({ destinationIndex: e.target.value })} + aria-label={i18n.translate( + 'xpack.ml.dataframe.analytics.create.destinationIndexInputAriaLabel', + { + defaultMessage: 'Choose a unique destination index name.', + } + )} + isInvalid={!destinationIndexNameEmpty && !destinationIndexNameValid} + data-test-subj="mlAnalyticsCreateJobFlyoutDestinationIndexInput" + /> + + + setFormState({ createIndexPattern: !createIndexPattern })} + data-test-subj="mlAnalyticsCreateJobWizardCreateIndexPatternSwitch" + /> + + + { + setCurrentStep(ANALYTICS_STEPS.CREATE); + }} + /> + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/index.ts new file mode 100644 index 0000000000000..6cadd87d97e27 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DetailsStep } from './details_step'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/index.ts new file mode 100644 index 0000000000000..efd7c0634bed4 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConfigurationStep } from './configuration_step/index'; +export { AdvancedStep } from './advanced_step/index'; +export { DetailsStep } from './details_step/index'; +export { CreateStep } from './create_step/index'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/index.ts new file mode 100644 index 0000000000000..5199fa1b6e4c6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useIndexData } from './use_index_data'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts new file mode 100644 index 0000000000000..e8f25584201e3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; + +import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; +import { + getDataGridSchemaFromKibanaFieldType, + getFieldsFromKibanaIndexPattern, + useDataGrid, + useRenderCellValue, + EsSorting, + SearchResponse7, + UseIndexDataReturnType, +} from '../../../../components/data_grid'; +import { getErrorMessage } from '../../../../../../common/util/errors'; +import { INDEX_STATUS } from '../../../common/analytics'; +import { ml } from '../../../../services/ml_api_service'; + +type IndexSearchResponse = SearchResponse7; + +export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDataReturnType => { + const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern); + + // EuiDataGrid State + const columns = [ + ...indexPatternFields.map((id) => { + const field = indexPattern.fields.getByName(id); + const schema = getDataGridSchemaFromKibanaFieldType(field); + return { id, schema }; + }), + ]; + + const dataGrid = useDataGrid(columns); + + const { + pagination, + resetPagination, + setErrorMessage, + setRowCount, + setStatus, + setTableItems, + sortingColumns, + tableItems, + } = dataGrid; + + useEffect(() => { + resetPagination(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(query)]); + + const getIndexData = async function () { + setErrorMessage(''); + setStatus(INDEX_STATUS.LOADING); + + const sort: EsSorting = sortingColumns.reduce((s, column) => { + s[column.id] = { order: column.direction }; + return s; + }, {} as EsSorting); + + const esSearchRequest = { + index: indexPattern.title, + body: { + // Instead of using the default query (`*`), fall back to a more efficient `match_all` query. + query, // isDefaultQuery(query) ? matchAllQuery : query, + from: pagination.pageIndex * pagination.pageSize, + size: pagination.pageSize, + ...(Object.keys(sort).length > 0 ? { sort } : {}), + }, + }; + + try { + const resp: IndexSearchResponse = await ml.esSearch(esSearchRequest); + + const docs = resp.hits.hits.map((d) => d._source); + + setRowCount(resp.hits.total.value); + setTableItems(docs); + setStatus(INDEX_STATUS.LOADED); + } catch (e) { + setErrorMessage(getErrorMessage(e)); + setStatus(INDEX_STATUS.ERROR); + } + }; + + useEffect(() => { + getIndexData(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); + + const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems); + + return { + ...dataGrid, + columns, + renderCellValue, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/index.ts new file mode 100644 index 0000000000000..7e2d651439ae3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { Page } from './page'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx new file mode 100644 index 0000000000000..def862b859162 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useEffect, useState } from 'react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiSpacer, + EuiSteps, + EuiStepStatus, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useMlContext } from '../../../contexts/ml'; +import { newJobCapsService } from '../../../services/new_job_capabilities_service'; +import { useCreateAnalyticsForm } from '../analytics_management/hooks/use_create_analytics_form'; +import { CreateAnalyticsAdvancedEditor } from '../analytics_management/components/create_analytics_advanced_editor'; +import { AdvancedStep, ConfigurationStep, CreateStep, DetailsStep } from './components'; + +export enum ANALYTICS_STEPS { + CONFIGURATION, + ADVANCED, + DETAILS, + CREATE, +} + +export const Page: FC = () => { + const [currentStep, setCurrentStep] = useState(ANALYTICS_STEPS.CONFIGURATION); + const [activatedSteps, setActivatedSteps] = useState([true, false, false, false]); + + const mlContext = useMlContext(); + const { currentIndexPattern } = mlContext; + + const createAnalyticsForm = useCreateAnalyticsForm(); + const { isAdvancedEditorEnabled } = createAnalyticsForm.state; + const { jobType } = createAnalyticsForm.state.form; + const { switchToAdvancedEditor } = createAnalyticsForm.actions; + + useEffect(() => { + if (activatedSteps[currentStep] === false) { + activatedSteps.splice(currentStep, 1, true); + setActivatedSteps(activatedSteps); + } + }, [currentStep]); + + useEffect(() => { + if (currentIndexPattern) { + (async function () { + await newJobCapsService.initializeFromIndexPattern(currentIndexPattern, false, false); + })(); + } + }, []); + + const analyticsWizardSteps = [ + { + title: i18n.translate('xpack.ml.dataframe.analytics.creation.configurationStepTitle', { + defaultMessage: 'Configuration', + }), + children: ( + + ), + status: + currentStep >= ANALYTICS_STEPS.CONFIGURATION ? undefined : ('incomplete' as EuiStepStatus), + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.creation.advancedStepTitle', { + defaultMessage: 'Additional options', + }), + children: ( + + ), + status: currentStep >= ANALYTICS_STEPS.ADVANCED ? undefined : ('incomplete' as EuiStepStatus), + 'data-test-subj': 'mlAnalyticsCreateJobWizardAdvancedStep', + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.creation.detailsStepTitle', { + defaultMessage: 'Job details', + }), + children: ( + + ), + status: currentStep >= ANALYTICS_STEPS.DETAILS ? undefined : ('incomplete' as EuiStepStatus), + 'data-test-subj': 'mlAnalyticsCreateJobWizardDetailsStep', + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.creation.createStepTitle', { + defaultMessage: 'Create', + }), + children: , + status: currentStep >= ANALYTICS_STEPS.CREATE ? undefined : ('incomplete' as EuiStepStatus), + 'data-test-subj': 'mlAnalyticsCreateJobWizardCreateStep', + }, + ]; + + return ( + + + + + + + + +

+ +

+
+
+ +

+ +

+
+
+
+ {isAdvancedEditorEnabled === false && ( + + + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.switchToJsonEditorSwitch', + { + defaultMessage: 'Switch to json editor', + } + )} + + + + + )} +
+ + {isAdvancedEditorEnabled === true && ( + + )} + {isAdvancedEditorEnabled === false && ( + + )} +
+
+
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx index 14493ab024f34..64fdf161b5615 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx @@ -36,7 +36,7 @@ export function getColumnData(confusionMatrixData: ConfusionMatrix[]) { let showOther = false; - confusionMatrixData.forEach(classData => { + confusionMatrixData.forEach((classData) => { const otherCount = classData.other_predicted_class_doc_count; if (otherCount > 0) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx index f95e6a93058ba..8c158c1ca14a0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Dispatch, FC, SetStateAction, useState } from 'react'; +import React, { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'; import { EuiCode, EuiInputPopover } from '@elastic/eui'; @@ -30,11 +30,15 @@ interface ErrorMessage { interface ExplorationQueryBarProps { indexPattern: IIndexPattern; setSearchQuery: Dispatch>; + includeQueryString?: boolean; + defaultQueryString?: string; } export const ExplorationQueryBar: FC = ({ indexPattern, setSearchQuery, + includeQueryString = false, + defaultQueryString, }) => { // The internal state of the input query bar updated on every key stroke. const [searchInput, setSearchInput] = useState({ @@ -44,20 +48,34 @@ export const ExplorationQueryBar: FC = ({ const [errorMessage, setErrorMessage] = useState(undefined); + useEffect(() => { + if (defaultQueryString !== undefined) { + setSearchInput({ query: defaultQueryString, language: SEARCH_QUERY_LANGUAGE.KUERY }); + } + }, []); + const searchChangeHandler = (query: Query) => setSearchInput(query); const searchSubmitHandler = (query: Query) => { try { switch (query.language) { case SEARCH_QUERY_LANGUAGE.KUERY: + const convertedKQuery = esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(query.query as string), + indexPattern + ); setSearchQuery( - esKuery.toElasticsearchQuery( - esKuery.fromKueryExpression(query.query as string), - indexPattern - ) + includeQueryString + ? { queryString: query.query, query: convertedKQuery } + : convertedKQuery ); return; case SEARCH_QUERY_LANGUAGE.LUCENE: - setSearchQuery(esQuery.luceneStringToDsl(query.query as string)); + const convertedLQuery = esQuery.luceneStringToDsl(query.query as string); + setSearchQuery( + includeQueryString + ? { queryString: query.query, query: convertedLQuery } + : convertedLQuery + ); return; } } catch (e) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts index e664a1ddbdbcc..e391b90e6eb96 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts @@ -48,7 +48,7 @@ export const useExplorationResults = ( // reduce default selected rows from 20 to 8 for performance reasons. 8, // by default, hide feature-importance columns and the doc id copy - d => !d.includes(`.${FEATURE_IMPORTANCE}.`) && d !== ML__ID_COPY + (d) => !d.includes(`.${FEATURE_IMPORTANCE}.`) && d !== ML__ID_COPY ); useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts index bfd3dd33995aa..3746fa12bdc1e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts @@ -17,7 +17,7 @@ export const getFeatureCount = (resultsField: string, tableItems: DataGridItem[] return 0; } - return Object.keys(tableItems[0]).filter(key => + return Object.keys(tableItems[0]).filter((key) => key.includes(`${resultsField}.${FEATURE_INFLUENCE}.`) ).length; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts index 75b2f6aa867df..ebac6ccb2298e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -55,7 +55,7 @@ export const useOutlierData = ( // reduce default selected rows from 20 to 8 for performance reasons. 8, // by default, hide feature-influence columns and the doc id copy - d => !d.includes(`.${FEATURE_INFLUENCE}.`) && d !== ML__ID_COPY + (d) => !d.includes(`.${FEATURE_INFLUENCE}.`) && d !== ML__ID_COPY ); useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx index 3e5224b76329e..33217f127f998 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx @@ -5,20 +5,41 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; - +import { fireEvent, render } from '@testing-library/react'; import * as CheckPrivilige from '../../../../../capabilities/check_capabilities'; - -import { DeleteAction } from './action_delete'; - import mockAnalyticsListItem from './__mocks__/analytics_list_item.json'; +import { DeleteAction } from './action_delete'; +import { I18nProvider } from '@kbn/i18n/react'; +import { + coreMock as mockCoreServices, + i18nServiceMock, +} from '../../../../../../../../../../src/core/public/mocks'; jest.mock('../../../../../capabilities/check_capabilities', () => ({ checkPermission: jest.fn(() => false), createPermissionFailureMessage: jest.fn(), })); +jest.mock('../../../../../../application/util/dependency_cache', () => ({ + getToastNotifications: () => ({ addSuccess: jest.fn(), addDanger: jest.fn() }), +})); + +jest.mock('../../../../../contexts/kibana', () => ({ + useMlKibana: () => ({ + services: mockCoreServices.createStart(), + }), +})); +export const MockI18nService = i18nServiceMock.create(); +export const I18nServiceConstructor = jest.fn().mockImplementation(() => MockI18nService); +jest.doMock('@kbn/i18n', () => ({ + I18nService: I18nServiceConstructor, +})); + describe('DeleteAction', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + test('When canDeleteDataFrameAnalytics permission is false, button should be disabled.', () => { const { getByTestId } = render(); expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); @@ -26,7 +47,7 @@ describe('DeleteAction', () => { test('When canDeleteDataFrameAnalytics permission is true, button should not be disabled.', () => { const mock = jest.spyOn(CheckPrivilige, 'checkPermission'); - mock.mockImplementation(p => p === 'canDeleteDataFrameAnalytics'); + mock.mockImplementation((p) => p === 'canDeleteDataFrameAnalytics'); const { getByTestId } = render(); expect(getByTestId('mlAnalyticsJobDeleteButton')).not.toHaveAttribute('disabled'); @@ -46,4 +67,24 @@ describe('DeleteAction', () => { expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); }); + + describe('When delete model is open', () => { + test('should allow to delete target index by default.', () => { + const mock = jest.spyOn(CheckPrivilige, 'checkPermission'); + mock.mockImplementation((p) => p === 'canDeleteDataFrameAnalytics'); + const { getByTestId, queryByTestId } = render( + + + + ); + const deleteButton = getByTestId('mlAnalyticsJobDeleteButton'); + fireEvent.click(deleteButton); + expect(getByTestId('mlAnalyticsJobDeleteModal')).toBeInTheDocument(); + expect(getByTestId('mlAnalyticsJobDeleteIndexSwitch')).toBeInTheDocument(); + const mlAnalyticsJobDeleteIndexSwitch = getByTestId('mlAnalyticsJobDeleteIndexSwitch'); + expect(mlAnalyticsJobDeleteIndexSwitch).toHaveAttribute('aria-checked', 'true'); + expect(queryByTestId('mlAnalyticsJobDeleteIndexPatternSwitch')).toBeNull(); + mock.mockRestore(); + }); + }); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx index 2923938ae68ac..2d433f6b18484 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx @@ -4,24 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useState } from 'react'; +import React, { Fragment, FC, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiConfirmModal, EuiOverlayMask, EuiToolTip, + EuiSwitch, + EuiFlexGroup, + EuiFlexItem, EUI_MODAL_CONFIRM_BUTTON, } from '@elastic/eui'; - -import { deleteAnalytics } from '../../services/analytics_service'; - +import { IIndexPattern } from 'src/plugins/data/common'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + deleteAnalytics, + deleteAnalyticsAndDestIndex, + canDeleteIndex, +} from '../../services/analytics_service'; import { checkPermission, createPermissionFailureMessage, } from '../../../../../capabilities/check_capabilities'; - +import { useMlKibana } from '../../../../../contexts/kibana'; import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; +import { extractErrorMessage } from '../../../../../util/error_utils'; interface DeleteActionProps { item: DataFrameAnalyticsListRow; @@ -29,17 +37,99 @@ interface DeleteActionProps { export const DeleteAction: FC = ({ item }) => { const disabled = isDataFrameAnalyticsRunning(item.stats.state); - const canDeleteDataFrameAnalytics: boolean = checkPermission('canDeleteDataFrameAnalytics'); const [isModalVisible, setModalVisible] = useState(false); + const [deleteTargetIndex, setDeleteTargetIndex] = useState(true); + const [deleteIndexPattern, setDeleteIndexPattern] = useState(true); + const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); + const [indexPatternExists, setIndexPatternExists] = useState(false); + + const { savedObjects, notifications } = useMlKibana().services; + const savedObjectsClient = savedObjects.client; + + const indexName = item.config.dest.index; + + const checkIndexPatternExists = async () => { + try { + const response = await savedObjectsClient.find({ + type: 'index-pattern', + perPage: 10, + search: `"${indexName}"`, + searchFields: ['title'], + fields: ['title'], + }); + const ip = response.savedObjects.find( + (obj) => obj.attributes.title.toLowerCase() === indexName.toLowerCase() + ); + if (ip !== undefined) { + setIndexPatternExists(true); + } + } catch (e) { + const { toasts } = notifications; + const error = extractErrorMessage(e); + + toasts.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfIndexPatternExistsNotificationErrorMessage', + { + defaultMessage: + 'An error occurred checking if index pattern {indexPattern} exists: {error}', + values: { indexPattern: indexName, error }, + } + ) + ); + } + }; + const checkUserIndexPermission = () => { + try { + const userCanDelete = canDeleteIndex(indexName); + if (userCanDelete) { + setUserCanDeleteIndex(true); + } + } catch (e) { + const { toasts } = notifications; + const error = extractErrorMessage(e); + + toasts.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage', + { + defaultMessage: + 'An error occurred checking if user can delete {destinationIndex}: {error}', + values: { destinationIndex: indexName, error }, + } + ) + ); + } + }; + + useEffect(() => { + // Check if an index pattern exists corresponding to current DFA job + // if pattern does exist, show it to user + checkIndexPatternExists(); + + // Check if an user has permission to delete the index & index pattern + checkUserIndexPermission(); + }, []); const closeModal = () => setModalVisible(false); const deleteAndCloseModal = () => { setModalVisible(false); - deleteAnalytics(item); + + if ((userCanDeleteIndex && deleteTargetIndex) || (userCanDeleteIndex && deleteIndexPattern)) { + deleteAnalyticsAndDestIndex( + item, + deleteTargetIndex, + indexPatternExists && deleteIndexPattern + ); + } else { + deleteAnalytics(item); + } }; const openModal = () => setModalVisible(true); + const toggleDeleteIndex = () => setDeleteTargetIndex(!deleteTargetIndex); + const toggleDeleteIndexPattern = () => setDeleteIndexPattern(!deleteIndexPattern); const buttonDeleteText = i18n.translate('xpack.ml.dataframe.analyticsList.deleteActionName', { defaultMessage: 'Delete', @@ -84,8 +174,9 @@ export const DeleteAction: FC = ({ item }) => { {deleteButton} {isModalVisible && ( - + = ({ item }) => { buttonColor="danger" >

- {i18n.translate('xpack.ml.dataframe.analyticsList.deleteModalBody', { - defaultMessage: `Are you sure you want to delete this analytics job? The analytics job's destination index and optional Kibana index pattern will not be deleted.`, - })} +

+ + + + {userCanDeleteIndex && ( + + )} + + + {userCanDeleteIndex && indexPatternExists && ( + + )} + +
)} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index 72514c91ff58b..295a3988e1b58 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; -import { DeepReadonly } from '../../../../../../../common/types/common'; +// import { DeepReadonly } from '../../../../../../../common/types/common'; import { checkPermission, @@ -21,7 +21,7 @@ import { isClassificationAnalysis, } from '../../../../common/analytics'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -import { CloneAction } from './action_clone'; +// import { CloneAction } from './action_clone'; import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; import { stopAnalytics } from '../../services/analytics_service'; @@ -106,10 +106,10 @@ export const getActions = (createAnalyticsForm: CreateAnalyticsFormProps) => { return ; }, }, - { - render: (item: DeepReadonly) => { - return ; - }, - }, + // { + // render: (item: DeepReadonly) => { + // return ; + // }, + // }, ]; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 68e728c019873..bb012a2190859 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -53,13 +53,14 @@ import { CreateAnalyticsButton } from '../create_analytics_button'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; import { CreateAnalyticsFlyoutWrapper } from '../create_analytics_flyout_wrapper'; import { getSelectedJobIdFromUrl } from '../../../../../jobs/jobs_list/components/utils'; +import { SourceSelection } from '../source_selection'; function getItemIdToExpandedRowMap( itemIds: DataFrameAnalyticsId[], dataFrameAnalytics: DataFrameAnalyticsListRow[] ): ItemIdToExpandedRowMap { return itemIds.reduce((m: ItemIdToExpandedRowMap, analyticsId: DataFrameAnalyticsId) => { - const item = dataFrameAnalytics.find(analytics => analytics.config.id === analyticsId); + const item = dataFrameAnalytics.find((analytics) => analytics.config.id === analyticsId); if (item !== undefined) { m[analyticsId] = ; } @@ -90,6 +91,7 @@ export const DataFrameAnalyticsList: FC = ({ createAnalyticsForm, }) => { const [isInitialized, setIsInitialized] = useState(false); + const [isSourceIndexModalVisible, setIsSourceIndexModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [filterActive, setFilterActive] = useState(false); @@ -176,7 +178,7 @@ export const DataFrameAnalyticsList: FC = ({ return p; }, {}); - clauses.forEach(c => { + clauses.forEach((c) => { // the search term could be negated with a minus, e.g. -bananas const bool = c.match === 'must'; let ts: DataFrameAnalyticsListRow[]; @@ -187,12 +189,12 @@ export const DataFrameAnalyticsList: FC = ({ // if the term has been negated, AND the matches if (bool === true) { ts = analytics.filter( - d => stringMatch(d.id, c.value) === bool // || + (d) => stringMatch(d.id, c.value) === bool // || // stringMatch(d.config.description, c.value) === bool ); } else { ts = analytics.filter( - d => stringMatch(d.id, c.value) === bool // && + (d) => stringMatch(d.id, c.value) === bool // && // stringMatch(d.config.description, c.value) === bool ); } @@ -200,25 +202,25 @@ export const DataFrameAnalyticsList: FC = ({ // filter other clauses, i.e. the mode and status filters if (Array.isArray(c.value)) { if (c.field === 'job_type') { - ts = analytics.filter(d => + ts = analytics.filter((d) => (c.value as string).includes(getAnalysisType(d.config.analysis)) ); } else { // the status value is an array of string(s) e.g. ['failed', 'stopped'] - ts = analytics.filter(d => (c.value as string).includes(d.stats.state)); + ts = analytics.filter((d) => (c.value as string).includes(d.stats.state)); } } else { - ts = analytics.filter(d => d.mode === c.value); + ts = analytics.filter((d) => d.mode === c.value); } } - ts.forEach(t => matches[t.id].count++); + ts.forEach((t) => matches[t.id].count++); }); // loop through the matches and return only analytics which have match all the clauses const filtered = Object.values(matches) - .filter(m => (m && m.count) >= clauses.length) - .map(m => m.analytics); + .filter((m) => (m && m.count) >= clauses.length) + .map((m) => m.analytics); let pageStart = pageIndex * pageSize; if (pageStart >= filtered.length && filtered.length !== 0) { @@ -271,7 +273,7 @@ export const DataFrameAnalyticsList: FC = ({ !isManagementTable && createAnalyticsForm ? [ setIsSourceIndexModalVisible(true)} isDisabled={disabled} data-test-subj="mlAnalyticsCreateFirstButton" > @@ -287,6 +289,9 @@ export const DataFrameAnalyticsList: FC = ({ {!isManagementTable && createAnalyticsForm && ( )} + {isSourceIndexModalVisible === true && ( + setIsSourceIndexModalVisible(false)} /> + )}
); } @@ -330,7 +335,7 @@ export const DataFrameAnalyticsList: FC = ({ defaultMessage: 'Type', }), multiSelect: 'or', - options: Object.values(ANALYSIS_CONFIG_TYPE).map(val => ({ + options: Object.values(ANALYSIS_CONFIG_TYPE).map((val) => ({ value: val, name: val, view: getJobTypeBadge(val), @@ -343,7 +348,7 @@ export const DataFrameAnalyticsList: FC = ({ defaultMessage: 'Status', }), multiSelect: 'or', - options: Object.values(DATA_FRAME_TASK_STATE).map(val => ({ + options: Object.values(DATA_FRAME_TASK_STATE).map((val) => ({ value: val, name: val, view: getTaskStateBadge(val), @@ -402,7 +407,10 @@ export const DataFrameAnalyticsList: FC = ({ {!isManagementTable && createAnalyticsForm && ( - + )} @@ -426,7 +434,7 @@ export const DataFrameAnalyticsList: FC = ({ sorting={sorting} search={search} data-test-subj={isLoading ? 'mlAnalyticsTable loading' : 'mlAnalyticsTable loaded'} - rowProps={item => ({ + rowProps={(item) => ({ 'data-test-subj': `mlAnalyticsTableRow row-${item.id}`, })} /> @@ -435,6 +443,9 @@ export const DataFrameAnalyticsList: FC = ({ {!isManagementTable && createAnalyticsForm?.state.isModalVisible && ( )} + {isSourceIndexModalVisible === true && ( + setIsSourceIndexModalVisible(false)} /> + )} ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index 94dc7ec87cc61..0ee57fe5be141 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -186,7 +186,7 @@ export const ExpandedRow: FC = ({ item }) => { ), description: `${currentPhase}/${totalPhases}`, }, - ...item.stats.progress.map(s => { + ...item.stats.progress.map((s) => { return { title: s.phase, description: , diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_details_pane.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_details_pane.tsx index 32b2ca3472018..71ca2b6f60492 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_details_pane.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_details_pane.tsx @@ -53,8 +53,8 @@ export const ExpandedRowDetailsPane: FC = ({ sectio {sections - .filter(s => s.position === 'left') - .map(s => ( + .filter((s) => s.position === 'left') + .map((s) => (
@@ -63,8 +63,8 @@ export const ExpandedRowDetailsPane: FC = ({ sectio {sections - .filter(s => s.position === 'right') - .map(s => ( + .filter((s) => s.position === 'right') + .map((s) => (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx index fc860251bf83d..0dd9eba172e1c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx @@ -22,7 +22,6 @@ export const ExpandedRowMessagesPane: FC = ({ analyticsId }) => { const getMessagesFactory = () => { let concurrentLoads = 0; - return async function getMessages() { try { concurrentLoads++; @@ -52,8 +51,14 @@ export const ExpandedRowMessagesPane: FC = ({ analyticsId }) => { } }; }; - useRefreshAnalyticsList({ onRefresh: getMessagesFactory() }); - return ; + return ( + + ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx index cef03cc0d0c76..17b905cab135b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx @@ -23,11 +23,14 @@ import { XJsonMode } from '../../../../../../../shared_imports'; const xJsonMode = new XJsonMode(); import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; +import { CreateStep } from '../../../analytics_creation/components/create_step'; +import { ANALYTICS_STEPS } from '../../../analytics_creation/page'; -export const CreateAnalyticsAdvancedEditor: FC = ({ actions, state }) => { +export const CreateAnalyticsAdvancedEditor: FC = (props) => { + const { actions, state } = props; const { setAdvancedEditorRawString, setFormState } = actions; - const { advancedEditorMessages, advancedEditorRawString, isJobCreated, requestMessages } = state; + const { advancedEditorMessages, advancedEditorRawString, isJobCreated } = state; const { createIndexPattern, @@ -56,120 +59,105 @@ export const CreateAnalyticsAdvancedEditor: FC = ({ ac return ( - {requestMessages.map((requestMessage, i) => ( + + { + if (input) { + forceInput.current = input; + } + }} + disabled={isJobCreated} + placeholder="analytics job ID" + value={jobId} + onChange={(e) => setFormState({ jobId: e.target.value })} + aria-label={i18n.translate( + 'xpack.ml.dataframe.analytics.create.advancedEditor.jobIdInputAriaLabel', + { + defaultMessage: 'Choose a unique analytics job ID.', + } + )} + isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists} + /> + + + + + + + {advancedEditorMessages.map((advancedEditorMessage, i) => ( - {requestMessage.error !== undefined ?

{requestMessage.error}

: null} + {advancedEditorMessage.message !== '' && advancedEditorMessage.error !== undefined ? ( +

{advancedEditorMessage.error}

+ ) : null}
- +
))} {!isJobCreated && ( - - { - if (input) { - forceInput.current = input; - } - }} - disabled={isJobCreated} - placeholder="analytics job ID" - value={jobId} - onChange={e => setFormState({ jobId: e.target.value })} - aria-label={i18n.translate( - 'xpack.ml.dataframe.analytics.create.advancedEditor.jobIdInputAriaLabel', - { - defaultMessage: 'Choose a unique analytics job ID.', - } - )} - isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists} - /> - - - - - - - {advancedEditorMessages.map((advancedEditorMessage, i) => ( - - - {advancedEditorMessage.message !== '' && - advancedEditorMessage.error !== undefined ? ( -

{advancedEditorMessage.error}

- ) : null} -
- -
- ))} = ({ ac
)} + +
); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/_index.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/_index.scss new file mode 100644 index 0000000000000..14ff9de7ded4d --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/_index.scss @@ -0,0 +1,4 @@ +.dataFrameAnalyticsCreateSearchDialog { + width: $euiSizeL * 30; + min-height: $euiSizeL * 25; +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx index 10565fb4d7a93..8e9b09ef41fa8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx @@ -35,7 +35,9 @@ describe('Data Frame Analytics: ', () => { test('Minimal initialization', () => { const { getLastHookValue } = getMountedHook(); const props = getLastHookValue(); - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper.find('EuiButton').text()).toBe('Create analytics job'); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx index 3c8c3c3b3aa55..595a1cf73e189 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx @@ -10,15 +10,26 @@ import { i18n } from '@kbn/i18n'; import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -export const CreateAnalyticsButton: FC = props => { - const { disabled } = props.state; - const { openModal } = props.actions; +interface Props extends CreateAnalyticsFormProps { + setIsSourceIndexModalVisible: React.Dispatch>; +} + +export const CreateAnalyticsButton: FC = ({ + state, + actions, + setIsSourceIndexModalVisible, +}) => { + const { disabled } = state; + + const handleClick = () => { + setIsSourceIndexModalVisible(true); + }; const button = ( = props => { +export const CreateAnalyticsFlyoutWrapper: FC = (props) => { const { isAdvancedEditorEnabled, isModalVisible } = props.state; if (isModalVisible === false) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx index 11052b171845d..64fe736e67b17 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx @@ -465,7 +465,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta ]} > { + inputRef={(input) => { if (input) { forceInput.current = input; } @@ -475,7 +475,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta defaultMessage: 'Job ID', })} value={jobId} - onChange={e => setFormState({ jobId: e.target.value })} + onChange={(e) => setFormState({ jobId: e.target.value })} aria-label={i18n.translate( 'xpack.ml.dataframe.analytics.create.jobIdInputAriaLabel', { @@ -579,7 +579,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta disabled={isJobCreated} placeholder="destination index" value={destinationIndex} - onChange={e => setFormState({ destinationIndex: e.target.value })} + onChange={(e) => setFormState({ destinationIndex: e.target.value })} aria-label={i18n.translate( 'xpack.ml.dataframe.analytics.create.destinationIndexInputAriaLabel', { @@ -667,7 +667,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta singleSelection={true} options={dependentVariableOptions} selectedOptions={dependentVariable ? [{ label: dependentVariable }] : []} - onChange={selectedOptions => + onChange={(selectedOptions) => setFormState({ dependentVariable: selectedOptions[0].label || '', }) @@ -691,7 +691,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta showValue value={trainingPercent} // @ts-ignore Property 'value' does not exist on type 'EventTarget' | (EventTarget & HTMLInputElement) - onChange={e => setFormState({ trainingPercent: +e.target.value })} + onChange={(e) => setFormState({ trainingPercent: +e.target.value })} data-test-subj="mlAnalyticsCreateJobFlyoutTrainingPercentSlider" /> @@ -738,7 +738,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta disabled={false} isInvalid={numTopFeatureImportanceValuesValid === false} min={NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN} - onChange={e => setFormState({ numTopFeatureImportanceValues: +e.target.value })} + onChange={(e) => setFormState({ numTopFeatureImportanceValues: +e.target.value })} step={1} value={numTopFeatureImportanceValues} /> @@ -790,12 +790,12 @@ export const CreateAnalyticsForm: FC = ({ actions, sta isDisabled={isJobCreated} isLoading={loadingFieldOptions} options={excludesOptions} - selectedOptions={excludes.map(field => ({ + selectedOptions={excludes.map((field) => ({ label: field, }))} onCreateOption={onCreateOption} - onChange={selectedOptions => - setFormState({ excludes: selectedOptions.map(option => option.label) }) + onChange={(selectedOptions) => + setFormState({ excludes: selectedOptions.map((option) => option.label) }) } isClearable={true} data-test-subj="mlAnalyticsCreateJobFlyoutExcludesSelect" @@ -816,7 +816,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta } disabled={isJobCreated} value={modelMemoryLimit || ''} - onChange={e => setFormState({ modelMemoryLimit: e.target.value })} + onChange={(e) => setFormState({ modelMemoryLimit: e.target.value })} isInvalid={modelMemoryLimitValidationResult !== null} data-test-subj="mlAnalyticsCreateJobFlyoutModelMemoryInput" /> diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts index 4bd03fec7cc72..0579283c97d61 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts @@ -10,7 +10,7 @@ import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; import { AnalyticsJobType } from '../../hooks/use_create_analytics_form/state'; import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES } from '../../../../common/fields'; -const CATEGORICAL_TYPES = new Set(['ip', 'keyword']); +export const CATEGORICAL_TYPES = new Set(['ip', 'keyword']); // List of system fields we want to ignore for the numeric field check. export const OMIT_FIELDS: string[] = ['_source', '_type', '_index', '_id', '_version', '_score']; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx index 58f3129280c09..46301a6f832e7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx @@ -27,7 +27,7 @@ export const JobDescriptionInput: FC = ({ description, setFormState }) => value={description} placeholder={helpText} rows={2} - onChange={e => { + onChange={(e) => { const value = e.target.value; setFormState({ description: value }); }} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx index 210f2c2dbedf1..6daa72dd805b1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx @@ -57,13 +57,13 @@ export const JobType: FC = ({ type, setFormState }) => { helpText={type !== undefined ? helpText[type] : ''} > ({ + options={Object.values(ANALYSIS_CONFIG_TYPE).map((jobType) => ({ value: jobType, text: jobType.replace(/_/g, ' '), }))} value={type} hasNoInitialSelection={true} - onChange={e => { + onChange={(e) => { const value = e.target.value as AnalyticsJobType; setFormState({ previousJobType: type, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/messages.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/messages.tsx index 68a9a264ddf78..27285e3df7f57 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/messages.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/messages.tsx @@ -20,6 +20,7 @@ export const Messages: FC = ({ messages }) => { {messages.map((requestMessage, i) => ( void; +} + +export const SourceSelection: FC = ({ onClose }) => { + const { uiSettings, savedObjects } = useMlKibana().services; + + const onSearchSelected = (id: string, type: string) => { + window.location.href = `ml#/data_frame_analytics/new_job?${ + type === 'index-pattern' ? 'index' : 'savedSearchId' + }=${encodeURIComponent(id)}`; + }; + + return ( + <> + + + + + {' '} + /{' '} + + + + + 'search', + name: i18n.translate( + 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.search', + { + defaultMessage: 'Saved search', + } + ), + }, + { + type: 'index-pattern', + getIconForSavedObject: () => 'indexPatternApp', + name: i18n.translate( + 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.indexPattern', + { + defaultMessage: 'Index pattern', + } + ), + }, + ]} + fixedPageSize={fixedPageSize} + uiSettings={uiSettings} + savedObjects={savedObjects} + /> + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts index 66e4103f5bb41..c42e03b584a56 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts @@ -72,6 +72,7 @@ export interface ActionDispatchers { closeModal: () => void; createAnalyticsJob: () => void; openModal: () => Promise; + initiateWizard: () => Promise; resetAdvancedEditorMessages: () => void; setAdvancedEditorRawString: (payload: State['advancedEditorRawString']) => void; setFormState: (payload: Partial) => void; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts index e5e56c084f7b9..8ec415bc2abb4 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts @@ -5,4 +5,9 @@ */ export { DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES } from './state'; -export { useCreateAnalyticsForm, CreateAnalyticsFormProps } from './use_create_analytics_form'; +export { + AnalyticsCreationStep, + useCreateAnalyticsForm, + CreateAnalyticsFormProps, + CreateAnalyticsStepProps, +} from './use_create_analytics_form'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 1cab42d8ee12d..a79a8fcf61ed4 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -144,7 +144,7 @@ export const validateAdvancedEditor = (state: State): State => { sourceIndexNameValid = !sourceIndex.includes(','); } if (Array.isArray(sourceIndex)) { - sourceIndexNameValid = !sourceIndex.some(d => d?.includes(',')); + sourceIndexNameValid = !sourceIndex.some((d) => d?.includes(',')); } } @@ -479,7 +479,7 @@ export function reducer(state: State, action: Action): State { // update state attributes which are derived from other state attributes. if (action.payload.destinationIndex !== undefined) { newFormState.destinationIndexNameExists = state.indexNames.some( - name => newFormState.destinationIndex === name + (name) => newFormState.destinationIndex === name ); newFormState.destinationIndexNameEmpty = newFormState.destinationIndex === ''; newFormState.destinationIndexNameValid = isValidIndexName(newFormState.destinationIndex); @@ -488,7 +488,7 @@ export function reducer(state: State, action: Action): State { } if (action.payload.jobId !== undefined) { - newFormState.jobIdExists = state.jobIds.some(id => newFormState.jobId === id); + newFormState.jobIdExists = state.jobIds.some((id) => newFormState.jobId === id); newFormState.jobIdEmpty = newFormState.jobId === ''; newFormState.jobIdValid = isJobIdValid(newFormState.jobId); newFormState.jobIdInvalidMaxLength = !!maxLengthValidator(JOB_ID_MAX_LENGTH)( @@ -515,7 +515,7 @@ export function reducer(state: State, action: Action): State { case ACTION.SET_INDEX_NAMES: { const newState = { ...state, indexNames: action.indexNames }; newState.form.destinationIndexNameExists = newState.indexNames.some( - name => newState.form.destinationIndex === name + (name) => newState.form.destinationIndex === name ); return newState; } @@ -547,7 +547,7 @@ export function reducer(state: State, action: Action): State { case ACTION.SET_JOB_IDS: { const newState = { ...state, jobIds: action.jobIds }; - newState.form.jobIdExists = newState.jobIds.some(id => newState.form.jobId === id); + newState.form.jobIdExists = newState.jobIds.some((id) => newState.form.jobId === id); return newState; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 8ca985a537b6e..387ce89ee4120 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -11,6 +11,7 @@ import { mlNodesAvailable } from '../../../../../ml_nodes_check'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { + FieldSelectionItem, isClassificationAnalysis, isRegressionAnalysis, DataFrameAnalyticsId, @@ -26,7 +27,8 @@ export enum DEFAULT_MODEL_MEMORY_LIMIT { classification = '100mb', } -export const DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES = 2; +export const DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES = 0; +export const UNSET_CONFIG_ITEM = '--'; export type EsIndexName = string; export type DependentVariable = string; @@ -47,6 +49,7 @@ export interface State { advancedEditorMessages: FormMessage[]; advancedEditorRawString: string; form: { + computeFeatureInfluence: string; createIndexPattern: boolean; dependentVariable: DependentVariable; dependentVariableFetchFail: boolean; @@ -57,31 +60,47 @@ export interface State { destinationIndexNameEmpty: boolean; destinationIndexNameValid: boolean; destinationIndexPatternTitleExists: boolean; + eta: undefined | number; excludes: string[]; + excludesTableItems: FieldSelectionItem[]; excludesOptions: EuiComboBoxOptionOption[]; + featureBagFraction: undefined | number; + featureInfluenceThreshold: undefined | number; fieldOptionsFetchFail: boolean; + gamma: undefined | number; jobId: DataFrameAnalyticsId; jobIdExists: boolean; jobIdEmpty: boolean; jobIdInvalidMaxLength: boolean; jobIdValid: boolean; jobType: AnalyticsJobType; + jobConfigQuery: any; + jobConfigQueryString: string | undefined; + lambda: number | undefined; loadingDepVarOptions: boolean; loadingFieldOptions: boolean; maxDistinctValuesError: string | undefined; + maxTrees: undefined | number; + method: undefined | string; modelMemoryLimit: string | undefined; modelMemoryLimitUnitValid: boolean; modelMemoryLimitValidationResult: any; + nNeighbors: undefined | number; numTopFeatureImportanceValues: number | undefined; numTopFeatureImportanceValuesValid: boolean; + numTopClasses: number; + outlierFraction: undefined | number; + predictionFieldName: undefined | string; previousJobType: null | AnalyticsJobType; previousSourceIndex: EsIndexName | undefined; requiredFieldsError: string | undefined; + randomizeSeed: undefined | number; sourceIndex: EsIndexName; sourceIndexNameEmpty: boolean; sourceIndexNameValid: boolean; sourceIndexContainsNumericalFields: boolean; sourceIndexFieldsCheckFailed: boolean; + standardizationEnabled: undefined | string; trainingPercent: number; }; disabled: boolean; @@ -105,7 +124,8 @@ export const getInitialState = (): State => ({ advancedEditorMessages: [], advancedEditorRawString: '', form: { - createIndexPattern: false, + computeFeatureInfluence: 'true', + createIndexPattern: true, dependentVariable: '', dependentVariableFetchFail: false, dependentVariableOptions: [], @@ -115,8 +135,13 @@ export const getInitialState = (): State => ({ destinationIndexNameEmpty: true, destinationIndexNameValid: false, destinationIndexPatternTitleExists: false, + eta: undefined, excludes: [], + featureBagFraction: undefined, + featureInfluenceThreshold: undefined, fieldOptionsFetchFail: false, + gamma: undefined, + excludesTableItems: [], excludesOptions: [], jobId: '', jobIdExists: false, @@ -124,22 +149,33 @@ export const getInitialState = (): State => ({ jobIdInvalidMaxLength: false, jobIdValid: false, jobType: undefined, + jobConfigQuery: { match_all: {} }, + jobConfigQueryString: undefined, + lambda: undefined, loadingDepVarOptions: false, loadingFieldOptions: false, maxDistinctValuesError: undefined, + maxTrees: undefined, + method: undefined, modelMemoryLimit: undefined, modelMemoryLimitUnitValid: true, modelMemoryLimitValidationResult: null, + nNeighbors: undefined, numTopFeatureImportanceValues: DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES, numTopFeatureImportanceValuesValid: true, + numTopClasses: 2, + outlierFraction: undefined, + predictionFieldName: undefined, previousJobType: null, previousSourceIndex: undefined, requiredFieldsError: undefined, + randomizeSeed: undefined, sourceIndex: '', sourceIndexNameEmpty: true, sourceIndexNameValid: false, sourceIndexContainsNumericalFields: true, sourceIndexFieldsCheckFailed: false, + standardizationEnabled: 'true', trainingPercent: 80, }, jobConfig: {}, @@ -220,8 +256,9 @@ export const getJobConfigFromFormState = ( // the into an array of indices to be in the correct format for // the data frame analytics API. index: formState.sourceIndex.includes(',') - ? formState.sourceIndex.split(',').map(d => d.trim()) + ? formState.sourceIndex.split(',').map((d) => d.trim()) : formState.sourceIndex, + query: formState.jobConfigQuery, }, dest: { index: formState.destinationIndex, @@ -239,15 +276,55 @@ export const getJobConfigFromFormState = ( formState.jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION ) { - jobConfig.analysis = { - [formState.jobType]: { - dependent_variable: formState.dependentVariable, - num_top_feature_importance_values: formState.numTopFeatureImportanceValues, - training_percent: formState.trainingPercent, + let analysis = { + dependent_variable: formState.dependentVariable, + num_top_feature_importance_values: formState.numTopFeatureImportanceValues, + training_percent: formState.trainingPercent, + }; + + analysis = Object.assign( + analysis, + formState.predictionFieldName && { prediction_field_name: formState.predictionFieldName }, + formState.eta && { eta: formState.eta }, + formState.featureBagFraction && { + feature_bag_fraction: formState.featureBagFraction, }, + formState.gamma && { gamma: formState.gamma }, + formState.lambda && { lambda: formState.lambda }, + formState.maxTrees && { max_trees: formState.maxTrees }, + formState.randomizeSeed && { randomize_seed: formState.randomizeSeed } + ); + + jobConfig.analysis = { + [formState.jobType]: analysis, }; } + if ( + formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && + jobConfig?.analysis?.classification !== undefined && + formState.numTopClasses !== undefined + ) { + // @ts-ignore + jobConfig.analysis.classification.num_top_classes = formState.numTopClasses; + } + + if (formState.jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION) { + const analysis = Object.assign( + {}, + formState.method && { method: formState.method }, + formState.nNeighbors && { + n_neighbors: formState.nNeighbors, + }, + formState.outlierFraction && { outlier_fraction: formState.outlierFraction }, + formState.standardizationEnabled && { + standardization_enabled: formState.standardizationEnabled, + } + ); + // @ts-ignore + jobConfig.analysis.outlier_detection = analysis; + } + return jobConfig; }; @@ -279,6 +356,11 @@ export function getCloneFormStateFromJobConfig( resultState.dependentVariable = analysisConfig.dependent_variable; resultState.numTopFeatureImportanceValues = analysisConfig.num_top_feature_importance_values; resultState.trainingPercent = analysisConfig.training_percent; + + if (isClassificationAnalysis(analyticsJobConfig.analysis)) { + // @ts-ignore + resultState.numTopClasses = analysisConfig.num_top_classes; + } } return resultState; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index 2478dbf7cf63d..c4cbe149f88bc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -36,11 +36,24 @@ import { getCloneFormStateFromJobConfig, } from './state'; +import { ANALYTICS_STEPS } from '../../../analytics_creation/page'; + +export interface AnalyticsCreationStep { + number: ANALYTICS_STEPS; + completed: boolean; +} + export interface CreateAnalyticsFormProps { actions: ActionDispatchers; state: State; } +export interface CreateAnalyticsStepProps extends CreateAnalyticsFormProps { + setCurrentStep: React.Dispatch>; + step?: ANALYTICS_STEPS; + stepActivated?: boolean; +} + export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const mlContext = useMlContext(); const [state, dispatch] = useReducer(reducer, getInitialState()); @@ -214,7 +227,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } try { - setIndexNames((await ml.getIndices()).map(index => index.name)); + setIndexNames((await ml.getIndices()).map((index) => index.name)); } catch (e) { addRequestMessage({ error: getErrorMessage(e), @@ -261,8 +274,12 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { dispatch({ type: ACTION.OPEN_MODAL }); }; + const initiateWizard = async () => { + await mlContext.indexPatterns.clearCache(); + await prepareFormValidation(); + }; + const startAnalyticsJob = async () => { - setIsModalButtonDisabled(true); try { const response = await ml.dataFrameAnalytics.startDataFrameAnalytics(jobId); if (response.acknowledged !== true) { @@ -278,7 +295,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { ), }); setIsJobStarted(true); - setIsModalButtonDisabled(false); refresh(); } catch (e) { addRequestMessage({ @@ -290,7 +306,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } ), }); - setIsModalButtonDisabled(false); } }; @@ -331,6 +346,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { closeModal, createAnalyticsJob, openModal, + initiateWizard, resetAdvancedEditorMessages, setAdvancedEditorRawString, setFormState, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts index 7383f565bd673..26cefff0a3f59 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts @@ -3,17 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { i18n } from '@kbn/i18n'; import { getToastNotifications } from '../../../../../util/dependency_cache'; import { ml } from '../../../../../services/ml_api_service'; - import { refreshAnalyticsList$, REFRESH_ANALYTICS_LIST_STATE } from '../../../../common'; - import { isDataFrameAnalyticsFailed, DataFrameAnalyticsListRow, } from '../../components/analytics_list/common'; +import { extractErrorMessage } from '../../../../../util/error_utils'; export const deleteAnalytics = async (d: DataFrameAnalyticsListRow) => { const toastNotifications = getToastNotifications(); @@ -24,18 +22,139 @@ export const deleteAnalytics = async (d: DataFrameAnalyticsListRow) => { await ml.dataFrameAnalytics.deleteDataFrameAnalytics(d.config.id); toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage', { - defaultMessage: 'Request to delete data frame analytics {analyticsId} acknowledged.', + defaultMessage: 'Request to delete data frame analytics job {analyticsId} acknowledged.', values: { analyticsId: d.config.id }, }) ); } catch (e) { + const error = extractErrorMessage(e); + toastNotifications.addDanger( i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage', { defaultMessage: - 'An error occurred deleting the data frame analytics {analyticsId}: {error}', - values: { analyticsId: d.config.id, error: JSON.stringify(e) }, + 'An error occurred deleting the data frame analytics job {analyticsId}: {error}', + values: { analyticsId: d.config.id, error }, }) ); } refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.REFRESH); }; + +export const deleteAnalyticsAndDestIndex = async ( + d: DataFrameAnalyticsListRow, + deleteDestIndex: boolean, + deleteDestIndexPattern: boolean +) => { + const toastNotifications = getToastNotifications(); + const destinationIndex = Array.isArray(d.config.dest.index) + ? d.config.dest.index[0] + : d.config.dest.index; + try { + if (isDataFrameAnalyticsFailed(d.stats.state)) { + await ml.dataFrameAnalytics.stopDataFrameAnalytics(d.config.id, true); + } + const status = await ml.dataFrameAnalytics.deleteDataFrameAnalyticsAndDestIndex( + d.config.id, + deleteDestIndex, + deleteDestIndexPattern + ); + if (status.analyticsJobDeleted?.success) { + toastNotifications.addSuccess( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage', { + defaultMessage: 'Request to delete data frame analytics job {analyticsId} acknowledged.', + values: { analyticsId: d.config.id }, + }) + ); + } + if (status.analyticsJobDeleted?.error) { + const error = extractErrorMessage(status.analyticsJobDeleted.error); + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage', { + defaultMessage: + 'An error occurred deleting the data frame analytics job {analyticsId}: {error}', + values: { analyticsId: d.config.id, error }, + }) + ); + } + + if (status.destIndexDeleted?.success) { + toastNotifications.addSuccess( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage', { + defaultMessage: 'Request to delete destination index {destinationIndex} acknowledged.', + values: { destinationIndex }, + }) + ); + } + if (status.destIndexDeleted?.error) { + const error = extractErrorMessage(status.destIndexDeleted.error); + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage', { + defaultMessage: + 'An error occurred deleting destination index {destinationIndex}: {error}', + values: { destinationIndex, error }, + }) + ); + } + + if (status.destIndexPatternDeleted?.success) { + toastNotifications.addSuccess( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage', + { + defaultMessage: 'Request to delete index pattern {destinationIndex} acknowledged.', + values: { destinationIndex }, + } + ) + ); + } + if (status.destIndexPatternDeleted?.error) { + const error = extractErrorMessage(status.destIndexPatternDeleted.error); + toastNotifications.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage', + { + defaultMessage: 'An error occurred deleting index pattern {destinationIndex}: {error}', + values: { destinationIndex, error }, + } + ) + ); + } + } catch (e) { + const error = extractErrorMessage(e); + + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage', { + defaultMessage: + 'An error occurred deleting the data frame analytics job {analyticsId}: {error}', + values: { analyticsId: d.config.id, error }, + }) + ); + } + refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.REFRESH); +}; + +export const canDeleteIndex = async (indexName: string) => { + const toastNotifications = getToastNotifications(); + try { + const privilege = await ml.hasPrivileges({ + index: [ + { + names: [indexName], // uses wildcard + privileges: ['delete_index'], + }, + ], + }); + if (!privilege) { + return false; + } + return privilege.securityDisabled === true || privilege.has_all_requested === true; + } catch (e) { + const error = extractErrorMessage(e); + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage', { + defaultMessage: 'User does not have permission to delete index {indexName}: {error}', + values: { indexName, error }, + }) + ); + } +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts index df58f225e62de..964e8e4062b38 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts @@ -125,7 +125,7 @@ export const getAnalyticsFactory = ( const tableRows = analyticsConfigs.data_frame_analytics.reduce( (reducedtableRows, config) => { const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats) - ? analyticsStats.data_frame_analytics.find(d => config.id === d.id) + ? analyticsStats.data_frame_analytics.find((d) => config.id === d.id) : undefined; // A newly created analytics job might not have corresponding stats yet. diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts index 0d1a87e7c4c1f..68aa58e7e1f19 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts @@ -3,8 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - export { getAnalyticsFactory } from './get_analytics'; -export { deleteAnalytics } from './delete_analytics'; +export { deleteAnalytics, deleteAnalyticsAndDestIndex, canDeleteIndex } from './delete_analytics'; export { startAnalytics } from './start_analytics'; export { stopAnalytics } from './stop_analytics'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index 126fd25a536f6..fd86d9f48f46d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -182,10 +182,7 @@ export const DatavisualizerSelector: FC = () => { } description={startTrialDescription()} footer={ - + = ({ onFilePickerChange }) => { defaultMessage: 'Select or drag and drop a file', } )} - onChange={files => onFilePickerChange(files)} + onChange={(files) => onFilePickerChange(files)} className="file-datavisualizer-file-picker" />
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/edit_flyout.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/edit_flyout.js index 253ec0ba4d028..1f0115a7ee9d2 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/edit_flyout.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/edit_flyout.js @@ -36,13 +36,13 @@ export class EditFlyout extends Component { this.props.closeEditFlyout(); }; - setApplyOverrides = applyOverrides => { + setApplyOverrides = (applyOverrides) => { this.applyOverrides = applyOverrides; }; unsetApplyOverrides = () => { this.applyOverrides = () => {}; }; - setOverridesValid = overridesValid => { + setOverridesValid = (overridesValid) => { this.setState({ overridesValid }); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/options.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/options.js index 503f26a9e978f..7728e96d6fc64 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/options.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/options.js @@ -13,7 +13,7 @@ import { } from './option_lists'; function getOptions(list) { - return list.map(o => ({ label: o })); + return list.map((o) => ({ label: o })); } export function getFormatOptions() { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.js index c84e456b206cd..bbbc33052f3c7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.js @@ -130,7 +130,7 @@ class OverridesUI extends Component { } if (originalTimestampFormat !== undefined) { - const optionExists = TIMESTAMP_OPTIONS.some(option => option === originalTimestampFormat); + const optionExists = TIMESTAMP_OPTIONS.some((option) => option === originalTimestampFormat); if (optionExists === false) { // Incoming format does not exist in dropdown. Display custom input with incoming format as default value. const overrides = { ...this.state.overrides }; @@ -177,7 +177,7 @@ class OverridesUI extends Component { } }; - onCustomTimestampFormatChange = e => { + onCustomTimestampFormatChange = (e) => { this.setState({ customTimestampFormat: e.target.value }); // check whether the value is valid and set that to state. const { isValid, errorMessage } = isTimestampFormatValid(e.target.value); @@ -195,7 +195,7 @@ class OverridesUI extends Component { this.setOverride({ delimiter }); }; - onCustomDelimiterChange = e => { + onCustomDelimiterChange = (e) => { this.setState({ customDelimiter: e.target.value }); }; @@ -204,11 +204,11 @@ class OverridesUI extends Component { this.setOverride({ quote }); }; - onHasHeaderRowChange = e => { + onHasHeaderRowChange = (e) => { this.setOverride({ hasHeaderRow: e.target.checked }); }; - onShouldTrimFieldsChange = e => { + onShouldTrimFieldsChange = (e) => { this.setOverride({ shouldTrimFields: e.target.checked }); }; @@ -223,11 +223,11 @@ class OverridesUI extends Component { this.setOverride({ columnNames }); }; - grokPatternChange = e => { + grokPatternChange = (e) => { this.setOverride({ grokPattern: e.target.value }); }; - onLinesToSampleChange = e => { + onLinesToSampleChange = (e) => { const linesToSample = +e.target.value; this.setOverride({ linesToSample }); @@ -493,7 +493,7 @@ class OverridesUI extends Component { this.onColumnNameChange(e, i)} + onChange={(e) => this.onColumnNameChange(e, i)} /> ))} @@ -514,7 +514,7 @@ function selectedOption(opt) { // also sort alphanumerically function getSortedFields(fields) { return fields - .map(f => ({ label: f })) + .map((f) => ({ label: f })) .sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true })); } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.test.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.test.js index 0257e69053d33..1a2bc20a5192a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.test.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.test.js @@ -10,7 +10,7 @@ import React from 'react'; import { Overrides } from './overrides'; jest.mock('../../../../../../../../../src/plugins/kibana_react/public', () => ({ - withKibana: comp => { + withKibana: (comp) => { return comp; }, })); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js index 5dfae43f223b1..9e83f72d7a07b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js @@ -31,7 +31,7 @@ export class FieldsStats extends Component { return (
- {this.state.fields.map(f => ( + {this.state.fields.map((f) => ( @@ -53,7 +53,7 @@ function createFields(results) { if (mappings && fieldStats) { const fieldNames = getFieldNames(results); - return fieldNames.map(name => { + return fieldNames.map((name) => { if (fieldStats[name] !== undefined) { const field = { name }; const f = fieldStats[name]; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx index 6564b9a1f4d83..ee73b20051427 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx @@ -60,8 +60,5 @@ export const FileContents: FC = ({ data, format, numberOfLines }) => { }; function limitByNumberOfLines(data: string, numberOfLines: number) { - return data - .split('\n') - .slice(0, numberOfLines) - .join('\n'); + return data.split('\n').slice(0, numberOfLines).join('\n'); } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js index c73ab4b9e11c7..7cb545cd5a776 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js @@ -67,7 +67,7 @@ export class FileDataVisualizerView extends Component { this.setState({ hasPermissionToImport }); } - onFilePickerChange = files => { + onFilePickerChange = (files) => { this.overrides = {}; this.setState( @@ -147,8 +147,8 @@ export class FileDataVisualizerView extends Component { // if no overrides were used, store all the settings returned from the endpoint this.originalSettings = serverSettings; } else { - Object.keys(serverOverrides).forEach(o => { - const camelCaseO = o.replace(/_\w/g, m => m[1].toUpperCase()); + Object.keys(serverOverrides).forEach((o) => { + const camelCaseO = o.replace(/_\w/g, (m) => m[1].toUpperCase()); this.overrides[camelCaseO] = serverOverrides[o]; }); @@ -156,7 +156,7 @@ export class FileDataVisualizerView extends Component { // e.g. changing the name of the time field which is also the time field // will cause the timestamp_field setting to change. // if any have changed, update the originalSettings value - Object.keys(serverSettings).forEach(o => { + Object.keys(serverSettings).forEach((o) => { const value = serverSettings[o]; if ( this.overrides[o] === undefined && @@ -225,7 +225,7 @@ export class FileDataVisualizerView extends Component { this.setState({ bottomBarVisible: false }); }; - setOverrides = overrides => { + setOverrides = (overrides) => { console.log('setOverrides', overrides); this.setState( { @@ -239,7 +239,7 @@ export class FileDataVisualizerView extends Component { ); }; - changeMode = mode => { + changeMode = (mode) => { this.setState({ mode }); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx index 32b51c8b7d4ee..07f2825687515 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx @@ -56,7 +56,7 @@ export const FilebeatConfigFlyout: FC = ({ useEffect(() => { if (security !== undefined) { - security.authc.getCurrentUser().then(user => { + security.authc.getCurrentUser().then((user) => { setUsername(user.username === undefined ? null : user.username); }); } @@ -86,7 +86,7 @@ export const FilebeatConfigFlyout: FC = ({ - {copy => ( + {(copy) => ( { + onConfigModeChange = (configMode) => { this.setState({ configMode, }); }; - onIndexChange = e => { + onIndexChange = (e) => { const name = e.target.value; const { indexNames, indexPattern, indexPatternNames } = this.state; @@ -341,7 +341,7 @@ export class ImportView extends Component { }); }; - onIndexPatternChange = e => { + onIndexPatternChange = (e) => { const name = e.target.value; const { indexPatternNames, index } = this.state; this.setState({ @@ -350,37 +350,37 @@ export class ImportView extends Component { }); }; - onCreateIndexPatternChange = e => { + onCreateIndexPatternChange = (e) => { this.setState({ createIndexPattern: e.target.checked, }); }; - onIndexSettingsStringChange = text => { + onIndexSettingsStringChange = (text) => { this.setState({ indexSettingsString: text, }); }; - onMappingsStringChange = text => { + onMappingsStringChange = (text) => { this.setState({ mappingsString: text, }); }; - onPipelineStringChange = text => { + onPipelineStringChange = (text) => { this.setState({ pipelineString: text, }); }; - setImportProgress = progress => { + setImportProgress = (progress) => { this.setState({ uploadProgress: progress, }); }; - setReadProgress = progress => { + setReadProgress = (progress) => { this.setState({ readProgress: progress, }); @@ -398,7 +398,7 @@ export class ImportView extends Component { async loadIndexNames() { const indices = await ml.getIndices(); - const indexNames = indices.map(i => i.name); + const indexNames = indices.map((i) => i.name); this.setState({ indexNames }); } @@ -656,7 +656,7 @@ function getDefaultState(state, results) { } function isIndexNameValid(name, indexNames) { - if (indexNames.find(i => i === name)) { + if (indexNames.find((i) => i === name)) { return ( i === name)) { + if (indexPatternNames.find((i) => i === name)) { return ( = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/data/index_management/indices/filter/${index}`} + href={`${basePath.get()}/app/management/data/index_management/indices/filter/${index}`} /> @@ -153,7 +153,7 @@ export const ResultsLinks: FC = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/kibana/indexPatterns${ + href={`${basePath.get()}/app/management/kibana/indexPatterns${ createIndexPattern ? `/patterns/${indexPatternId}` : '' }`} /> diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts index 3b82a34b889b7..6111974f369c8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts @@ -91,7 +91,7 @@ export function createUrlOverrides(overrides: InputOverrides, originalSettings: value = ''; } - const snakeCaseO = o.replace(/([A-Z])/g, $1 => `_${$1.toLowerCase()}`); + const snakeCaseO = o.replace(/([A-Z])/g, ($1) => `_${$1.toLowerCase()}`); formattedOverrides[snakeCaseO] = value; } } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx index e2c156fc66ded..e2fb8ae5547cc 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx @@ -145,7 +145,7 @@ export const NumberContent: FC = ({ config }) => { setDetailsMode(optionId as DETAILS_MODE)} + onChange={(optionId) => setDetailsMode(optionId as DETAILS_MODE)} aria-label={i18n.translate( 'xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel', { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx index cf0e3ec1a9c9b..4189308a3bc99 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx @@ -59,7 +59,7 @@ export const MetricDistributionChart: FC = ({ width, height, chartData, f const headerFormatter: TooltipValueFormatter = (tooltipData: ChartTooltipValue) => { const xValue = tooltipData.value; const chartPoint: MetricDistributionChartData | undefined = chartData.find( - data => data.x === xValue + (data) => data.x === xValue ); return ( @@ -97,9 +97,9 @@ export const MetricDistributionChart: FC = ({ width, height, chartData, f kibanaFieldFormat(d, fieldFormat)} + tickFormat={(d) => kibanaFieldFormat(d, fieldFormat)} /> - d.toFixed(3)} hide={true} /> + d.toFixed(3)} hide={true} /> data.y); + let barHeights = processedData.map((data) => data.y); barHeights = barHeights.sort((a, b) => a - b); let maxBarHeight = 0; @@ -131,12 +131,12 @@ export function buildChartDataFromStats( 2; } - processedData.forEach(data => { + processedData.forEach((data) => { data.y = Math.min(data.y, maxBarHeight); }); // Convert the data to the format used by the chart. - chartData = processedData.map(data => { + chartData = processedData.map((data) => { const { x0, y, dataMin, dataMax, percent } = data; return { x: x0, y, dataMin, dataMax, percent }; }); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_types_select/field_types_select.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_types_select/field_types_select.tsx index 6fd08076e1f46..24081f835a017 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_types_select/field_types_select.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_types_select/field_types_select.tsx @@ -31,7 +31,7 @@ export const FieldTypesSelect: FC = ({ }), }, ]; - fieldTypes.forEach(fieldType => { + fieldTypes.forEach((fieldType) => { options.push({ value: fieldType, text: i18n.translate('xpack.ml.datavisualizer.fieldTypesSelect.typeOptionLabel', { @@ -47,7 +47,7 @@ export const FieldTypesSelect: FC = ({ setSelectedFieldType(e.target.value as ML_JOB_FIELD_TYPES | '*')} + onChange={(e) => setSelectedFieldType(e.target.value as ML_JOB_FIELD_TYPES | '*')} aria-label={i18n.translate('xpack.ml.datavisualizer.fieldTypesSelect.selectAriaLabel', { defaultMessage: 'Select field types to display', })} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx index 16004475eb44f..b93ae9e67ef72 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx @@ -43,7 +43,7 @@ interface Props { totalCount: number; } -const searchSizeOptions = [1000, 5000, 10000, 100000, -1].map(v => { +const searchSizeOptions = [1000, 5000, 10000, 100000, -1].map((v) => { return { value: String(v), inputDisplay: @@ -150,7 +150,7 @@ export const SearchPanel: FC = ({ setSamplerShardSize(+value)} + onChange={(value) => setSamplerShardSize(+value)} aria-label={i18n.translate( 'xpack.ml.datavisualizer.searchPanel.sampleSizeAriaLabel', { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/data_loader/data_loader.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/data_loader/data_loader.ts index 9ba99ce891538..7d1f456d2334f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/data_loader/data_loader.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/data_loader/data_loader.ts @@ -38,7 +38,7 @@ export class DataLoader { ): Promise { const aggregatableFields: string[] = []; const nonAggregatableFields: string[] = []; - this._indexPattern.fields.forEach(field => { + this._indexPattern.fields.forEach((field) => { const fieldName = field.displayName !== undefined ? field.displayName : field.name; if (this.isDisplayField(fieldName) === true) { if (field.aggregatable === true) { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx index 06d89ab782167..97b4043c9fd64 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -27,6 +27,7 @@ import { Query, esQuery, esKuery, + UI_SETTINGS, } from '../../../../../../../src/plugins/data/public'; import { SavedSearchSavedObject } from '../../../../common/types/kibana'; import { NavigationMenu } from '../../components/navigation_menu'; @@ -131,7 +132,7 @@ export const Page: FC = () => { // Obtain the list of non metric field types which appear in the index pattern. let indexedFieldTypes: ML_JOB_FIELD_TYPES[] = []; const indexPatternFields: IFieldType[] = currentIndexPattern.fields; - indexPatternFields.forEach(field => { + indexPatternFields.forEach((field) => { if (field.scripted !== true) { const dataVisualizerType: ML_JOB_FIELD_TYPES | undefined = kbnTypeToMLJobType(field); if ( @@ -254,7 +255,7 @@ export const Page: FC = () => { qry = esKuery.toElasticsearchQuery(ast, currentIndexPattern); } else { qry = esQuery.luceneStringToDsl(qryString); - esQuery.decorateQuery(qry, kibanaConfig.get('query:queryString:options')); + esQuery.decorateQuery(qry, kibanaConfig.get(UI_SETTINGS.QUERY_STRING_OPTIONS)); } return { @@ -300,7 +301,7 @@ export const Page: FC = () => { } const configsToLoad = metricConfigs.filter( - config => config.existsInDocs === true && config.loading === true + (config) => config.existsInDocs === true && config.loading === true ); if (configsToLoad.length === 0) { return; @@ -308,7 +309,7 @@ export const Page: FC = () => { // Pass the field name, type and cardinality in the request. // Top values will be obtained on a sample if cardinality > 100000. - const existMetricFields: FieldRequestConfig[] = configsToLoad.map(config => { + const existMetricFields: FieldRequestConfig[] = configsToLoad.map((config) => { const props = { fieldName: config.fieldName, type: config.type, cardinality: 0 }; if (config.stats !== undefined && config.stats.cardinality !== undefined) { props.cardinality = config.stats.cardinality; @@ -347,7 +348,7 @@ export const Page: FC = () => { // Add the metric stats to the existing stats in the corresponding config. const configs: FieldVisConfig[] = []; - metricConfigs.forEach(config => { + metricConfigs.forEach((config) => { const configWithStats = { ...config }; if (config.fieldName !== undefined) { configWithStats.stats = { @@ -383,7 +384,7 @@ export const Page: FC = () => { } const configsToLoad = nonMetricConfigs.filter( - config => config.existsInDocs === true && config.loading === true + (config) => config.existsInDocs === true && config.loading === true ); if (configsToLoad.length === 0) { return; @@ -391,7 +392,7 @@ export const Page: FC = () => { // Pass the field name, type and cardinality in the request. // Top values will be obtained on a sample if cardinality > 100000. - const existNonMetricFields: FieldRequestConfig[] = configsToLoad.map(config => { + const existNonMetricFields: FieldRequestConfig[] = configsToLoad.map((config) => { const props = { fieldName: config.fieldName, type: config.type, cardinality: 0 }; if (config.stats !== undefined && config.stats.cardinality !== undefined) { props.cardinality = config.stats.cardinality; @@ -418,7 +419,7 @@ export const Page: FC = () => { // Add the field stats to the existing stats in the corresponding config. const configs: FieldVisConfig[] = []; - nonMetricConfigs.forEach(config => { + nonMetricConfigs.forEach((config) => { const configWithStats = { ...config }; if (config.fieldName !== undefined) { configWithStats.stats = { @@ -442,7 +443,7 @@ export const Page: FC = () => { const configs: FieldVisConfig[] = []; const aggregatableExistsFields: any[] = overallStats.aggregatableExistsFields || []; - let allMetricFields = indexPatternFields.filter(f => { + let allMetricFields = indexPatternFields.filter((f) => { return ( f.type === KBN_FIELD_TYPES.NUMBER && f.displayName !== undefined && @@ -451,14 +452,14 @@ export const Page: FC = () => { }); if (metricFieldQuery !== undefined) { const metricFieldRegexp = new RegExp(`(${metricFieldQuery})`, 'gi'); - allMetricFields = allMetricFields.filter(f => { + allMetricFields = allMetricFields.filter((f) => { const addField = f.displayName !== undefined && !!f.displayName.match(metricFieldRegexp); return addField; }); } - const metricExistsFields = allMetricFields.filter(f => { - return aggregatableExistsFields.find(existsF => { + const metricExistsFields = allMetricFields.filter((f) => { + return aggregatableExistsFields.find((existsF) => { return existsF.fieldName === f.displayName; }); }); @@ -493,8 +494,8 @@ export const Page: FC = () => { const metricFieldsToShow = showAllMetrics === true ? allMetricFields : metricExistsFields; - metricFieldsToShow.forEach(field => { - const fieldData = aggregatableFields.find(f => { + metricFieldsToShow.forEach((field) => { + const fieldData = aggregatableFields.find((f) => { return f.fieldName === field.displayName; }); @@ -517,7 +518,7 @@ export const Page: FC = () => { function createNonMetricCards() { let allNonMetricFields = []; if (nonMetricShowFieldType === '*') { - allNonMetricFields = indexPatternFields.filter(f => { + allNonMetricFields = indexPatternFields.filter((f) => { return ( f.type !== KBN_FIELD_TYPES.NUMBER && f.displayName !== undefined && @@ -531,7 +532,7 @@ export const Page: FC = () => { ) { const aggregatableCheck = nonMetricShowFieldType === ML_JOB_FIELD_TYPES.KEYWORD ? true : false; - allNonMetricFields = indexPatternFields.filter(f => { + allNonMetricFields = indexPatternFields.filter((f) => { return ( f.displayName !== undefined && dataLoader.isDisplayField(f.displayName) === true && @@ -540,7 +541,7 @@ export const Page: FC = () => { ); }); } else { - allNonMetricFields = indexPatternFields.filter(f => { + allNonMetricFields = indexPatternFields.filter((f) => { return ( f.type === nonMetricShowFieldType && f.displayName !== undefined && @@ -554,7 +555,7 @@ export const Page: FC = () => { if (nonMetricFieldQuery !== undefined) { const nonMetricFieldRegexp = new RegExp(`(${nonMetricFieldQuery})`, 'gi'); allNonMetricFields = allNonMetricFields.filter( - f => f.displayName !== undefined && f.displayName.match(nonMetricFieldRegexp) + (f) => f.displayName !== undefined && f.displayName.match(nonMetricFieldRegexp) ); } @@ -565,9 +566,9 @@ export const Page: FC = () => { const aggregatableExistsFields: any[] = overallStats.aggregatableExistsFields || []; const nonAggregatableExistsFields: any[] = overallStats.nonAggregatableExistsFields || []; - allNonMetricFields.forEach(f => { + allNonMetricFields.forEach((f) => { const checkAggregatableField = aggregatableExistsFields.find( - existsField => existsField.fieldName === f.displayName + (existsField) => existsField.fieldName === f.displayName ); if (checkAggregatableField !== undefined) { @@ -575,7 +576,7 @@ export const Page: FC = () => { nonMetricFieldData.push(checkAggregatableField); } else { const checkNonAggregatableField = nonAggregatableExistsFields.find( - existsField => existsField.fieldName === f.displayName + (existsField) => existsField.fieldName === f.displayName ); if (checkNonAggregatableField !== undefined) { @@ -606,8 +607,8 @@ export const Page: FC = () => { const configs: FieldVisConfig[] = []; - nonMetricFieldsToShow.forEach(field => { - const fieldData = nonMetricFieldData.find(f => f.fieldName === field.displayName); + nonMetricFieldsToShow.forEach((field) => { + const fieldData = nonMetricFieldData.find((f) => f.fieldName === field.displayName); const nonMetricConfig = { ...fieldData, diff --git a/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts b/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts index 994d67bfdb02c..24926960abd17 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts @@ -15,18 +15,18 @@ import { createJobs } from '../explorer_utils'; export function jobSelectionActionCreator(selectedJobIds: string[]) { return from(mlFieldFormatService.populateFormats(selectedJobIds)).pipe( - map(resp => { + map((resp) => { if (resp.err) { console.log('Error populating field formats:', resp.err); // eslint-disable-line no-console return null; } - const jobs = createJobs(mlJobService.jobs).map(job => { - job.selected = selectedJobIds.some(id => job.id === id); + const jobs = createJobs(mlJobService.jobs).map((job) => { + job.selected = selectedJobIds.some((id) => job.id === id); return job; }); - const selectedJobs = jobs.filter(job => job.selected); + const selectedJobs = jobs.filter((job) => job.selected); return { type: EXPLORER_ACTION.JOB_SELECTION_CHANGE, diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index 37794a250db34..590a69283a819 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -40,7 +40,7 @@ import { ExplorerState } from '../reducers'; // the original function. const memoizeIsEqual = (newArgs: any[], lastArgs: any[]) => isEqual(newArgs, lastArgs); const wrapWithLastRefreshArg = any>(func: T) => { - return function(lastRefresh: number, ...args: Parameters): ReturnType { + return function (lastRefresh: number, ...args: Parameters): ReturnType { return func.apply(null, args); }; }; @@ -265,5 +265,5 @@ const explorerData$ = loadExplorerData$.pipe( export const useExplorerData = (): [Partial | undefined, (d: any) => void] => { const explorerData = useObservable(explorerData$); - return [explorerData, c => loadExplorerData$.next(c)]; + return [explorerData, (c) => loadExplorerData$.next(c)]; }; diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx index 0263ad08b03cf..1c3c42b58b10e 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx @@ -35,7 +35,7 @@ export function getKqlQueryValues({ // if ast.type == 'function' then layout of ast.arguments: // [{ arguments: [ { type: 'literal', value: 'AAL' } ] },{ arguments: [ { type: 'literal', value: 'AAL' } ] }] if (ast && Array.isArray(ast.arguments)) { - ast.arguments.forEach(arg => { + ast.arguments.forEach((arg) => { if (arg.arguments !== undefined) { arg.arguments.forEach((nestedArg: { type: string; value: string }) => { if (typeof nestedArg.value === 'string') { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index 9c9c82a212472..5cebb6354c0db 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -84,7 +84,7 @@ import { MlTooltipComponent } from '../components/chart_tooltip'; import { hasMatchingPoints } from './has_matching_points'; function mapSwimlaneOptionsToEuiOptions(options) { - return options.map(option => ({ + return options.map((option) => ({ value: option, text: option, })); @@ -190,12 +190,12 @@ export class Explorer extends React.Component { this.anomaliesTablePreviousArgs = null; } - viewByChangeHandler = e => explorerService.setViewBySwimlaneFieldName(e.target.value); + viewByChangeHandler = (e) => explorerService.setViewBySwimlaneFieldName(e.target.value); isSwimlaneSelectActive = false; onSwimlaneEnterHandler = () => this.setSwimlaneSelectActive(true); onSwimlaneLeaveHandler = () => this.setSwimlaneSelectActive(false); - setSwimlaneSelectActive = active => { + setSwimlaneSelectActive = (active) => { if (this.isSwimlaneSelectActive && !active && this.disableDragSelectOnMouseLeave) { this.dragSelect.stop(); this.isSwimlaneSelectActive = active; @@ -210,7 +210,7 @@ export class Explorer extends React.Component { }; // Listener for click events in the swimlane to load corresponding anomaly data. - swimlaneCellClick = selectedCells => { + swimlaneCellClick = (selectedCells) => { // If selectedCells is an empty object we clear any existing selection, // otherwise we save the new selection in AppState and update the Explorer. if (Object.keys(selectedCells).length === 0) { @@ -276,7 +276,7 @@ export class Explorer extends React.Component { } }; - updateLanguage = language => this.setState({ language }); + updateLanguage = (language) => this.setState({ language }); render() { const { showCharts, severity } = this.props; @@ -416,7 +416,7 @@ export class Explorer extends React.Component { > {showOverallSwimlane && ( - {tooltipService => ( + {(tooltipService) => ( - {tooltipService => ( + {(tooltipService) => (  –  ); - const entityFieldBadges = entityFields.map(entity => ( + const entityFieldBadges = entityFields.map((entity) => ( )); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js index edb4b988277f9..b5e9daad7d1c1 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js @@ -60,7 +60,7 @@ export function buildConfig(record) { jobId: record.job_id, aggregationInterval: config.interval, chartFunction: functionLabel, - entityFields: config.entityFields.map(f => ({ + entityFields: config.entityFields.map((f) => ({ fieldName: f.fieldName, fieldValue: f.fieldValue, })), diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js index 2b577c978eb13..7a18914957ba9 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js @@ -25,6 +25,7 @@ import { getTickValues, numTicksForDateFormat, removeLabelOverlap, + chartExtendedLimits, } from '../../util/chart_utils'; import { LoadingIndicator } from '../../components/loading_indicator/loading_indicator'; import { getTimeBucketsFromCache } from '../../util/time_buckets'; @@ -92,13 +93,13 @@ export class ExplorerChartDistribution extends React.Component { const CHART_Y_ATTRIBUTE = chartType === CHART_TYPE.EVENT_DISTRIBUTION ? 'entity' : 'value'; - let highlight = config.chartData.find(d => d.anomalyScore !== undefined); + let highlight = config.chartData.find((d) => d.anomalyScore !== undefined); highlight = highlight && highlight.entity; const filteredChartData = init(config); drawRareChart(filteredChartData); - function init({ chartData }) { + function init({ chartData, functionDescription }) { const $el = $('.ml-explorer-chart'); // Clear any existing elements from the visualization, @@ -118,7 +119,7 @@ export class ExplorerChartDistribution extends React.Component { const categoryLimit = 30; const scaleCategories = d3 .nest() - .key(d => d.entity) + .key((d) => d.entity) .entries(chartData) .sort((a, b) => { return b.values.length - a.values.length; @@ -130,29 +131,31 @@ export class ExplorerChartDistribution extends React.Component { } return true; }) - .map(d => d.key); + .map((d) => d.key); - chartData = chartData.filter(d => { + chartData = chartData.filter((d) => { return scaleCategories.includes(d.entity); }); if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) { - const focusData = chartData - .filter(d => { - return d.entity === highlight; - }) - .map(d => d.value); - const focusExtent = d3.extent(focusData); - + const focusData = chartData.filter((d) => { + return d.entity === highlight; + }); + // calculate the max y domain based on value, typical, and actual + // also sets the min to be at least 0 if the series function type is `count` + const { min: yScaleDomainMin, max: yScaleDomainMax } = chartExtendedLimits( + focusData, + functionDescription + ); // now again filter chartData to include only the data points within the domain - chartData = chartData.filter(d => { - return d.value <= focusExtent[1]; + chartData = chartData.filter((d) => { + return d.value <= yScaleDomainMax; }); lineChartYScale = d3.scale .linear() .range([chartHeight, 0]) - .domain([0, focusExtent[1]]) + .domain([yScaleDomainMin < 0 ? yScaleDomainMin : 0, yScaleDomainMax]) .nice(); } else if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) { // avoid overflowing the border of the highlighted area @@ -184,7 +187,7 @@ export class ExplorerChartDistribution extends React.Component { .data(tempLabelTextData) .enter() .append('text') - .text(d => { + .text((d) => { if (fieldFormat !== undefined) { return fieldFormat.convert(d, 'text'); } else { @@ -195,7 +198,7 @@ export class ExplorerChartDistribution extends React.Component { } }) // Don't use an arrow function since we need access to `this`. - .each(function() { + .each(function () { maxYAxisLabelWidth = Math.max( this.getBBox().width + yAxis.tickPadding(), maxYAxisLabelWidth @@ -225,9 +228,9 @@ export class ExplorerChartDistribution extends React.Component { lineChartValuesLine = d3.svg .line() - .x(d => lineChartXScale(d.date)) - .y(d => lineChartYScale(d[CHART_Y_ATTRIBUTE])) - .defined(d => d.value !== null); + .x((d) => lineChartXScale(d.date)) + .y((d) => lineChartYScale(d[CHART_Y_ATTRIBUTE])) + .defined((d) => d.value !== null); lineChartGroup = svg .append('g') @@ -280,7 +283,7 @@ export class ExplorerChartDistribution extends React.Component { .innerTickSize(-chartHeight) .outerTickSize(0) .tickPadding(10) - .tickFormat(d => moment(d).format(xAxisTickFormat)); + .tickFormat((d) => moment(d).format(xAxisTickFormat)); // With tooManyBuckets the chart would end up with no x-axis labels // because the ticks are based on the span of the emphasis section, @@ -300,7 +303,7 @@ export class ExplorerChartDistribution extends React.Component { .tickPadding(10); if (fieldFormat !== undefined) { - yAxis.tickFormat(d => fieldFormat.convert(d, 'text')); + yAxis.tickFormat((d) => fieldFormat.convert(d, 'text')); } const axes = lineChartGroup.append('g'); @@ -311,17 +314,14 @@ export class ExplorerChartDistribution extends React.Component { .attr('transform', 'translate(0,' + chartHeight + ')') .call(xAxis); - axes - .append('g') - .attr('class', 'y axis') - .call(yAxis); + axes.append('g').attr('class', 'y axis').call(yAxis); // emphasize the y axis label this rare chart is actually about if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) { axes .select('.y') .selectAll('text') - .each(function(d) { + .each(function (d) { d3.select(this).classed('ml-explorer-chart-axis-emphasis', d === highlight); }); } @@ -345,10 +345,10 @@ export class ExplorerChartDistribution extends React.Component { .enter() .append('circle') .classed('values-dots-circle', true) - .classed('values-dots-circle-blur', d => { + .classed('values-dots-circle-blur', (d) => { return d.entity !== highlight; }) - .attr('r', d => (d.entity === highlight ? radius * 1.5 : radius)); + .attr('r', (d) => (d.entity === highlight ? radius * 1.5 : radius)); dots.attr('cx', rareChartValuesLine.x()).attr('cy', rareChartValuesLine.y()); @@ -382,7 +382,7 @@ export class ExplorerChartDistribution extends React.Component { .append('g') .attr('class', 'chart-markers') .selectAll('.metric-value') - .data(data.filter(d => d.value !== null)); + .data(data.filter((d) => d.value !== null)); // Remove dots that are no longer needed i.e. if number of chart points has decreased. dots.exit().remove(); @@ -392,16 +392,16 @@ export class ExplorerChartDistribution extends React.Component { .append('circle') .attr('r', LINE_CHART_ANOMALY_RADIUS) // Don't use an arrow function since we need access to `this`. - .on('mouseover', function(d) { + .on('mouseover', function (d) { showLineChartTooltip(d, this); }) .on('mouseout', () => tooltipService.hide()); // Update all dots to new positions. dots - .attr('cx', d => lineChartXScale(d.date)) - .attr('cy', d => lineChartYScale(d[CHART_Y_ATTRIBUTE])) - .attr('class', d => { + .attr('cx', (d) => lineChartXScale(d.date)) + .attr('cy', (d) => lineChartYScale(d[CHART_Y_ATTRIBUTE])) + .attr('class', (d) => { let markerClass = 'metric-value'; if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity) { markerClass += ' anomaly-marker '; @@ -414,7 +414,7 @@ export class ExplorerChartDistribution extends React.Component { const scheduledEventMarkers = lineChartGroup .select('.chart-markers') .selectAll('.scheduled-event-marker') - .data(data.filter(d => d.scheduledEvents !== undefined)); + .data(data.filter((d) => d.scheduledEvents !== undefined)); // Remove markers that are no longer needed i.e. if number of chart points has decreased. scheduledEventMarkers.exit().remove(); @@ -430,8 +430,11 @@ export class ExplorerChartDistribution extends React.Component { // Update all markers to new positions. scheduledEventMarkers - .attr('x', d => lineChartXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) - .attr('y', d => lineChartYScale(d[CHART_Y_ATTRIBUTE]) - SCHEDULED_EVENT_MARKER_HEIGHT / 2); + .attr('x', (d) => lineChartXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) + .attr( + 'y', + (d) => lineChartYScale(d[CHART_Y_ATTRIBUTE]) - SCHEDULED_EVENT_MARKER_HEIGHT / 2 + ); } function showLineChartTooltip(marker, circle) { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js index 06fd82204c1e1..53aca826f2dda 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js @@ -143,15 +143,9 @@ describe('ExplorerChart', () => { expect(+selectedInterval.getAttribute('y')).toBe(2); expect(+selectedInterval.getAttribute('height')).toBe(166); - const xAxisTicks = wrapper - .getDOMNode() - .querySelector('.x') - .querySelectorAll('.tick'); + const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick'); expect([...xAxisTicks]).toHaveLength(0); - const yAxisTicks = wrapper - .getDOMNode() - .querySelector('.y') - .querySelectorAll('.tick'); + const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(5); const emphasizedAxisLabel = wrapper .getDOMNode() @@ -164,10 +158,7 @@ describe('ExplorerChart', () => { expect(paths[1].getAttribute('class')).toBe('domain'); expect(paths[2]).toBe(undefined); - const dots = wrapper - .getDOMNode() - .querySelector('.values-dots') - .querySelectorAll('circle'); + const dots = wrapper.getDOMNode().querySelector('.values-dots').querySelectorAll('circle'); expect([...dots]).toHaveLength(5); expect(dots[0].getAttribute('r')).toBe('1.5'); @@ -176,7 +167,7 @@ describe('ExplorerChart', () => { .querySelector('.chart-markers') .querySelectorAll('circle'); expect([...chartMarkers]).toHaveLength(5); - expect([...chartMarkers].map(d => +d.getAttribute('r'))).toEqual([7, 7, 7, 7, 7]); + expect([...chartMarkers].map((d) => +d.getAttribute('r'))).toEqual([7, 7, 7, 7, 7]); }); it('Anomaly Explorer Chart with single data point', () => { @@ -192,10 +183,7 @@ describe('ExplorerChart', () => { ]; const wrapper = init(chartData); - const yAxisTicks = wrapper - .getDOMNode() - .querySelector('.y') - .querySelectorAll('.tick'); + const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(1); }); }); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.js index 0ee1eac19f64d..01f4626e222aa 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.js @@ -77,7 +77,7 @@ export const ExplorerChartInfoTooltip = ({ }, ]; - entityFields.forEach(entityField => { + entityFields.forEach((entityField) => { toolTipData.push({ title: entityField.fieldName, description: entityField.fieldValue, diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index 531a24493c961..63775c5ca312e 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -128,7 +128,7 @@ export class ExplorerChartSingleMetric extends React.Component { .data(lineChartYScale.ticks()) .enter() .append('text') - .text(d => { + .text((d) => { if (fieldFormat !== undefined) { return fieldFormat.convert(d, 'text'); } else { @@ -136,7 +136,7 @@ export class ExplorerChartSingleMetric extends React.Component { } }) // Don't use an arrow function since we need access to `this`. - .each(function() { + .each(function () { maxYAxisLabelWidth = Math.max( this.getBBox().width + yAxis.tickPadding(), maxYAxisLabelWidth @@ -158,9 +158,9 @@ export class ExplorerChartSingleMetric extends React.Component { lineChartValuesLine = d3.svg .line() - .x(d => lineChartXScale(d.date)) - .y(d => lineChartYScale(d.value)) - .defined(d => d.value !== null); + .x((d) => lineChartXScale(d.date)) + .y((d) => lineChartYScale(d.value)) + .defined((d) => d.value !== null); lineChartGroup = svg .append('g') @@ -212,7 +212,7 @@ export class ExplorerChartSingleMetric extends React.Component { .innerTickSize(-chartHeight) .outerTickSize(0) .tickPadding(10) - .tickFormat(d => moment(d).format(xAxisTickFormat)); + .tickFormat((d) => moment(d).format(xAxisTickFormat)); // With tooManyBuckets the chart would end up with no x-axis labels // because the ticks are based on the span of the emphasis section, @@ -232,7 +232,7 @@ export class ExplorerChartSingleMetric extends React.Component { .tickPadding(10); if (fieldFormat !== undefined) { - yAxis.tickFormat(d => fieldFormat.convert(d, 'text')); + yAxis.tickFormat((d) => fieldFormat.convert(d, 'text')); } const axes = lineChartGroup.append('g'); @@ -243,10 +243,7 @@ export class ExplorerChartSingleMetric extends React.Component { .attr('transform', 'translate(0,' + chartHeight + ')') .call(xAxis); - axes - .append('g') - .attr('class', 'y axis') - .call(yAxis); + axes.append('g').attr('class', 'y axis').call(yAxis); if (tooManyBuckets === false) { removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth); @@ -290,7 +287,7 @@ export class ExplorerChartSingleMetric extends React.Component { .selectAll('.metric-value') .data( data.filter( - d => + (d) => (d.value !== null || typeof d.anomalyScore === 'number') && !showMultiBucketAnomalyMarker(d) ) @@ -304,18 +301,19 @@ export class ExplorerChartSingleMetric extends React.Component { .append('circle') .attr('r', LINE_CHART_ANOMALY_RADIUS) // Don't use an arrow function since we need access to `this`. - .on('mouseover', function(d) { + .on('mouseover', function (d) { showLineChartTooltip(d, this); }) .on('mouseout', () => tooltipService.hide()); - const isAnomalyVisible = d => _.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity; + const isAnomalyVisible = (d) => + _.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity; // Update all dots to new positions. dots - .attr('cx', d => lineChartXScale(d.date)) - .attr('cy', d => lineChartYScale(d.value)) - .attr('class', d => { + .attr('cx', (d) => lineChartXScale(d.date)) + .attr('cy', (d) => lineChartYScale(d.value)) + .attr('class', (d) => { let markerClass = 'metric-value'; if (isAnomalyVisible(d)) { markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore).id}`; @@ -327,7 +325,7 @@ export class ExplorerChartSingleMetric extends React.Component { const multiBucketMarkers = lineChartGroup .select('.chart-markers') .selectAll('.multi-bucket') - .data(data.filter(d => isAnomalyVisible(d) && showMultiBucketAnomalyMarker(d) === true)); + .data(data.filter((d) => isAnomalyVisible(d) && showMultiBucketAnomalyMarker(d) === true)); // Remove multi-bucket markers that are no longer needed multiBucketMarkers.exit().remove(); @@ -336,20 +334,17 @@ export class ExplorerChartSingleMetric extends React.Component { multiBucketMarkers .enter() .append('path') + .attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross')) .attr( - 'd', - d3.svg - .symbol() - .size(MULTI_BUCKET_SYMBOL_SIZE) - .type('cross') + 'transform', + (d) => `translate(${lineChartXScale(d.date)}, ${lineChartYScale(d.value)})` ) .attr( - 'transform', - d => `translate(${lineChartXScale(d.date)}, ${lineChartYScale(d.value)})` + 'class', + (d) => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id}` ) - .attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id}`) // Don't use an arrow function since we need access to `this`. - .on('mouseover', function(d) { + .on('mouseover', function (d) { showLineChartTooltip(d, this); }) .on('mouseout', () => tooltipService.hide()); @@ -358,7 +353,7 @@ export class ExplorerChartSingleMetric extends React.Component { const scheduledEventMarkers = lineChartGroup .select('.chart-markers') .selectAll('.scheduled-event-marker') - .data(data.filter(d => d.scheduledEvents !== undefined)); + .data(data.filter((d) => d.scheduledEvents !== undefined)); // Remove markers that are no longer needed i.e. if number of chart points has decreased. scheduledEventMarkers.exit().remove(); @@ -374,8 +369,8 @@ export class ExplorerChartSingleMetric extends React.Component { // Update all markers to new positions. scheduledEventMarkers - .attr('x', d => lineChartXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) - .attr('y', d => lineChartYScale(d.value) - SCHEDULED_EVENT_SYMBOL_HEIGHT / 2); + .attr('x', (d) => lineChartXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) + .attr('y', (d) => lineChartYScale(d.value) - SCHEDULED_EVENT_SYMBOL_HEIGHT / 2); } function showLineChartTooltip(marker, circle) { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js index 54f541ceb7c3d..99f5c3eff6984 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -143,15 +143,9 @@ describe('ExplorerChart', () => { expect(+selectedInterval.getAttribute('y')).toBe(2); expect(+selectedInterval.getAttribute('height')).toBe(166); - const xAxisTicks = wrapper - .getDOMNode() - .querySelector('.x') - .querySelectorAll('.tick'); + const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick'); expect([...xAxisTicks]).toHaveLength(0); - const yAxisTicks = wrapper - .getDOMNode() - .querySelector('.y') - .querySelectorAll('.tick'); + const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(10); const paths = wrapper.getDOMNode().querySelectorAll('path'); @@ -162,10 +156,7 @@ describe('ExplorerChart', () => { 'MNaN,159.33024504444444ZMNaN,9.166257955555556LNaN,169.60736875555557' ); - const dots = wrapper - .getDOMNode() - .querySelector('.values-dots') - .querySelectorAll('circle'); + const dots = wrapper.getDOMNode().querySelector('.values-dots').querySelectorAll('circle'); expect([...dots]).toHaveLength(1); expect(dots[0].getAttribute('r')).toBe('1.5'); @@ -174,7 +165,7 @@ describe('ExplorerChart', () => { .querySelector('.chart-markers') .querySelectorAll('circle'); expect([...chartMarkers]).toHaveLength(4); - expect([...chartMarkers].map(d => +d.getAttribute('r'))).toEqual([7, 7, 7, 7]); + expect([...chartMarkers].map((d) => +d.getAttribute('r'))).toEqual([7, 7, 7, 7]); }); it('Anomaly Explorer Chart with single data point', () => { @@ -191,10 +182,7 @@ describe('ExplorerChart', () => { const wrapper = init(chartData); - const yAxisTicks = wrapper - .getDOMNode() - .querySelector('.y') - .querySelectorAll('.tick'); + const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(13); }); }); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js index 5b95931d31ab6..9988298c25c51 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js @@ -45,7 +45,7 @@ const textViewButton = i18n.translate( // from charts metadata for React's key attribute function getChartId(series) { const { jobId, detectorLabel, entityFields } = series; - const entities = entityFields.map(ef => `${ef.fieldName}/${ef.fieldValue}`).join(','); + const entities = entityFields.map((ef) => `${ef.fieldName}/${ef.fieldValue}`).join(','); const id = `${jobId}_${detectorLabel}_${entities}`; return id; } @@ -58,7 +58,7 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel }) let DetectorLabel = {detectorLabel}; if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) { - const byField = series.entityFields.find(d => d.fieldType === 'by'); + const byField = series.entityFields.find((d) => d.fieldType === 'by'); if (typeof byField !== 'undefined') { DetectorLabel = ( @@ -121,7 +121,7 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel }) ) { return ( - {tooltipService => ( + {(tooltipService) => ( - {tooltipService => ( + {(tooltipService) => ( isLabelLengthAboveThreshold(series)); + const wrapLabel = seriesToPlot.some((series) => isLabelLengthAboveThreshold(series)); return ( {seriesToPlot.length > 0 && - seriesToPlot.map(series => ( + seriesToPlot.map((series) => ( { + const filteredRecords = anomalyRecords.filter((record) => { return Number(record.record_score) >= severity; }); const allSeriesRecords = processRecordsForDisplay(filteredRecords); @@ -88,7 +88,7 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, data.tooManyBuckets = tooManyBuckets; // initialize the charts with loading indicators - data.seriesToPlot = seriesConfigs.map(config => ({ + data.seriesToPlot = seriesConfigs.map((config) => ({ ...config, loading: true, chartData: null, @@ -168,15 +168,15 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, return mlResultsService .getModelPlotOutput(jobId, detectorIndex, criteriaFields, range.min, range.max, interval) .toPromise() - .then(resp => { + .then((resp) => { // Return data in format required by the explorer charts. const results = resp.results; - Object.keys(results).forEach(time => { + Object.keys(results).forEach((time) => { obj.results[time] = results[time].actual; }); resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -225,11 +225,11 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, // Define splitField and filterField based on chartType if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) { - splitField = config.entityFields.find(f => f.fieldType === 'by'); - filterField = config.entityFields.find(f => f.fieldType === 'partition'); + splitField = config.entityFields.find((f) => f.fieldType === 'by'); + filterField = config.entityFields.find((f) => f.fieldType === 'partition'); } else if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) { - splitField = config.entityFields.find(f => f.fieldType === 'over'); - filterField = config.entityFields.find(f => f.fieldType === 'partition'); + splitField = config.entityFields.find((f) => f.fieldType === 'over'); + filterField = config.entityFields.find((f) => f.fieldType === 'partition'); } const datafeedQuery = _.get(config, 'datafeedConfig.query', null); @@ -251,7 +251,7 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, // only after that trigger data processing and page render. // TODO - if query returns no results e.g. source data has been deleted, // display a message saying 'No data between earliest/latest'. - const seriesPromises = seriesConfigs.map(seriesConfig => + const seriesPromises = seriesConfigs.map((seriesConfig) => Promise.all([ getMetricData(seriesConfig, chartRange), getRecordsForCriteria(seriesConfig, chartRange), @@ -280,7 +280,7 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, if (metricData !== undefined) { if (eventDistribution.length > 0 && records.length > 0) { const filterField = records[0].by_field_value || records[0].over_field_value; - chartData = eventDistribution.filter(d => d.entity !== filterField); + chartData = eventDistribution.filter((d) => d.entity !== filterField); _.map(metricData, (value, time) => { // The filtering for rare/event_distribution charts needs to be handled // differently because of how the source data is structured. @@ -310,7 +310,7 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, // Iterate through the anomaly records, adding anomalyScore properties // to the chartData entries for anomalous buckets. const chartDataForPointSearch = getChartDataForPointSearch(chartData, records[0], chartType); - _.each(records, record => { + _.each(records, (record) => { // Look for a chart point with the same time as the record. // If none found, insert a point for anomalies due to a gap in the data. const recordTime = record[ML_TIME_FIELD_NAME]; @@ -365,7 +365,7 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, chartType === CHART_TYPE.EVENT_DISTRIBUTION || chartType === CHART_TYPE.POPULATION_DISTRIBUTION ) { - return chartData.filter(d => { + return chartData.filter((d) => { return d.entity === (record && (record.by_field_value || record.over_field_value)); }); } @@ -374,17 +374,17 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, } function findChartPointForTime(chartData, time) { - return chartData.find(point => point.date === time); + return chartData.find((point) => point.date === time); } Promise.all(seriesPromises) - .then(response => { + .then((response) => { // calculate an overall min/max for all series const processedData = response.map(processChartData); const allDataPoints = _.reduce( processedData, (datapoints, series) => { - _.each(series, d => datapoints.push(d)); + _.each(series, (d) => datapoints.push(d)); return datapoints; }, [] @@ -403,7 +403,7 @@ export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, })); explorerService.setCharts({ ...data }); }) - .catch(error => { + .catch((error) => { console.error(error); }); }; @@ -416,7 +416,7 @@ function processRecordsForDisplay(anomalyRecords) { // Aggregate by job, detector, and analysis fields (partition, by, over). const aggregatedData = {}; - _.each(anomalyRecords, record => { + _.each(anomalyRecords, (record) => { // Check if we can plot a chart for this record, depending on whether the source data // is chartable, and if model plot is enabled for the job. const job = mlJobService.getJob(record.job_id); @@ -518,23 +518,22 @@ function processRecordsForDisplay(anomalyRecords) { } }); - console.log('explorer charts aggregatedData is:', aggregatedData); let recordsForSeries = []; // Convert to an array of the records with the highest record_score per unique series. - _.each(aggregatedData, detectorsForJob => { - _.each(detectorsForJob, groupsForDetector => { + _.each(aggregatedData, (detectorsForJob) => { + _.each(detectorsForJob, (groupsForDetector) => { if (groupsForDetector.maxScoreRecord !== undefined) { // Detector with no partition / by field. recordsForSeries.push(groupsForDetector.maxScoreRecord); } else { - _.each(groupsForDetector, valuesForGroup => { - _.each(valuesForGroup, dataForGroupValue => { + _.each(groupsForDetector, (valuesForGroup) => { + _.each(valuesForGroup, (dataForGroupValue) => { if (dataForGroupValue.maxScoreRecord !== undefined) { recordsForSeries.push(dataForGroupValue.maxScoreRecord); } else { // Second level of aggregation for partition and by/over. - _.each(dataForGroupValue, splitsForGroup => { - _.each(splitsForGroup, dataForSplitValue => { + _.each(dataForGroupValue, (splitsForGroup) => { + _.each(splitsForGroup, (dataForSplitValue) => { recordsForSeries.push(dataForSplitValue.maxScoreRecord); }); }); @@ -585,7 +584,7 @@ function calculateChartRange( let minMs = recordsToPlot[0][timeFieldName]; let maxMs = recordsToPlot[0][timeFieldName]; - _.each(recordsToPlot, record => { + _.each(recordsToPlot, (record) => { const diffMs = maxMs - minMs; if (diffMs < maxTimeSpan) { const recordTime = record[timeFieldName]; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js index 35261257ce625..6a9fd19180a4e 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js @@ -108,7 +108,7 @@ describe('explorerChartsContainerService', () => { explorerService.setCharts.mockClear(); }); - test('call anomalyChangeListener with empty series config', done => { + test('call anomalyChangeListener with empty series config', (done) => { anomalyDataChange([], 1486656000000, 1486670399999); setImmediate(() => { @@ -121,7 +121,7 @@ describe('explorerChartsContainerService', () => { }); }); - test('call anomalyChangeListener with actual series config', done => { + test('call anomalyChangeListener with actual series config', (done) => { anomalyDataChange(mockAnomalyChartRecords, 1486656000000, 1486670399999); setImmediate(() => { @@ -132,8 +132,8 @@ describe('explorerChartsContainerService', () => { }); }); - test('filtering should skip values of null', done => { - const mockAnomalyChartRecordsClone = _.cloneDeep(mockAnomalyChartRecords).map(d => { + test('filtering should skip values of null', (done) => { + const mockAnomalyChartRecordsClone = _.cloneDeep(mockAnomalyChartRecords).map((d) => { d.job_id = 'mock-job-id-distribution'; return d; }); @@ -150,13 +150,13 @@ describe('explorerChartsContainerService', () => { // it should remove the datapoint with `null` and keep the one with `0`. const chartData = explorerService.setCharts.mock.calls[1][0].seriesToPlot[0].chartData; expect(chartData).toHaveLength(114); - expect(chartData.filter(d => d.value === 0)).toHaveLength(1); - expect(chartData.filter(d => d.value === null)).toHaveLength(0); + expect(chartData.filter((d) => d.value === 0)).toHaveLength(1); + expect(chartData.filter((d) => d.value === null)).toHaveLength(0); done(); }); }); - test('field value with trailing dot should not throw an error', done => { + test('field value with trailing dot should not throw an error', (done) => { const mockAnomalyChartRecordsClone = _.cloneDeep(mockAnomalyChartRecords); mockAnomalyChartRecordsClone[1].partition_field_value = 'AAL.'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx index e8ea54d28f5a0..18b5de1d51f9c 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx @@ -118,7 +118,7 @@ export class ExplorerSwimlane extends React.Component { // immediately clear the selection, otherwise trigger // a reload with the updated selected cells. if (selectedData.bucketScore === 0) { - elements.map(e => d3.select(e).classed('ds-selected', false)); + elements.map((e) => d3.select(e).classed('ds-selected', false)); this.selectCell([], selectedData); previousSelectedData = null; } else { @@ -199,7 +199,7 @@ export class ExplorerSwimlane extends React.Component { highlightOverall(times: number[]) { const overallSwimlane = d3.select('.ml-swimlane-overall'); - times.forEach(time => { + times.forEach((time) => { const overallCell = overallSwimlane .selectAll(`div[data-time="${time}"]`) .selectAll('.sl-cell-inner,.sl-cell-inner-dragselect'); @@ -229,7 +229,7 @@ export class ExplorerSwimlane extends React.Component { .classed('sl-cell-inner-selected', true); const rootParent = d3.select(this.rootNode.current!.parentNode!); - rootParent.selectAll('.lane-label').classed('lane-label-masked', function(this: HTMLElement) { + rootParent.selectAll('.lane-label').classed('lane-label-masked', function (this: HTMLElement) { return laneLabels.indexOf(d3.select(this).text()) === -1; }); @@ -379,7 +379,7 @@ export class ExplorerSwimlane extends React.Component { function cellMouseOverFactory(time: number, i: number) { // Don't use an arrow function here because we need access to `this`, // which is where d3 supplies a reference to the corresponding DOM element. - return function(this: HTMLElement, lane: string) { + return function (this: HTMLElement, lane: string) { const bucketScore = getBucketScore(lane, time); if (bucketScore !== 0) { lane = lane === '' ? EMPTY_FIELD_VALUE_LABEL : lane; @@ -393,10 +393,7 @@ export class ExplorerSwimlane extends React.Component { }; const d3Lanes = swimlanes.selectAll('.lane').data(lanes); - const d3LanesEnter = d3Lanes - .enter() - .append('div') - .classed('lane', true); + const d3LanesEnter = d3Lanes.enter().append('div').classed('lane', true); const that = this; @@ -420,10 +417,10 @@ export class ExplorerSwimlane extends React.Component { swimlaneCellClick({}); } }) - .each(function(this: HTMLElement) { + .each(function (this: HTMLElement) { if (swimlaneData.fieldName !== undefined) { d3.select(this) - .on('mouseover', value => { + .on('mouseover', (value) => { that.props.tooltipService.show( [ { skipHeader: true } as ChartTooltipValue, @@ -447,7 +444,7 @@ export class ExplorerSwimlane extends React.Component { }) .attr( 'aria-label', - value => `${mlEscape(swimlaneData.fieldName!)}: ${mlEscape(value)}` + (value) => `${mlEscape(swimlaneData.fieldName!)}: ${mlEscape(value)}` ); } }); @@ -456,7 +453,7 @@ export class ExplorerSwimlane extends React.Component { function getBucketScore(lane: string, time: number): number { let bucketScore = 0; - const point = points.find(p => { + const point = points.find((p) => { return p.value > 0 && p.laneLabel === lane && p.time === time; }); if (typeof point !== 'undefined') { @@ -483,7 +480,7 @@ export class ExplorerSwimlane extends React.Component { // of this iteration to the event. .on('mouseover', cellMouseOverFactory(time, i)) .on('mouseleave', cellMouseleave) - .each(function(this: NodeWithData, laneLabel: string) { + .each(function (this: NodeWithData, laneLabel: string) { this.__clickData__ = { bucketScore: getBucketScore(laneLabel, time), laneLabel, @@ -493,13 +490,13 @@ export class ExplorerSwimlane extends React.Component { }); // calls itself with each() to get access to lane (= d3 data) - cell.append('div').each(function(this: HTMLElement, lane: string) { + cell.append('div').each(function (this: HTMLElement, lane: string) { const el = d3.select(this); let color = 'none'; let bucketScore = 0; - const point = points.find(p => { + const point = points.find((p) => { return p.value > 0 && p.laneLabel === lane && p.time === time; }); @@ -525,25 +522,19 @@ export class ExplorerSwimlane extends React.Component { // height of .time-tick-labels const svgHeight = 25; - const svg = laneTimes - .append('svg') - .attr('width', chartWidth) - .attr('height', svgHeight); + const svg = laneTimes.append('svg').attr('width', chartWidth).attr('height', svgHeight); const xAxis = d3.svg .axis() .scale(xAxisScale) .ticks(numTicksForDateFormat(chartWidth, xAxisTickFormat)) - .tickFormat(tick => moment(tick).format(xAxisTickFormat)); + .tickFormat((tick) => moment(tick).format(xAxisTickFormat)); - const gAxis = svg - .append('g') - .attr('class', 'x axis') - .call(xAxis); + const gAxis = svg.append('g').attr('class', 'x axis').call(xAxis); // remove overlapping labels let overlapCheck = 0; - gAxis.selectAll('g.tick').each(function(this: HTMLElement) { + gAxis.selectAll('g.tick').each(function (this: HTMLElement) { const tick = d3.select(this); const xTransform = d3.transform(tick.attr('transform')).translate[0]; const tickWidth = (tick.select('text').node() as SVGGraphicsElement).getBBox().width; @@ -595,7 +586,7 @@ export class ExplorerSwimlane extends React.Component { const selectedTimes = _.get(selectionState, 'times', []); const selectedTimeExtent = d3.extent(selectedTimes); - selectedLanes.forEach(selectedLane => { + selectedLanes.forEach((selectedLane) => { if ( lanes.indexOf(selectedLane) > -1 && selectedTimeExtent[0] >= startTime && @@ -607,7 +598,7 @@ export class ExplorerSwimlane extends React.Component { `div[data-lane-label="${mlEscape(selectedLane)}"]` ); - laneCells.each(function(this: HTMLElement) { + laneCells.each(function (this: HTMLElement) { const cell = d3.select(this); const cellTime = parseInt(cell.attr('data-time'), 10); if (cellTime >= selectedTimeExtent[0] && cellTime <= selectedTimeExtent[1]) { @@ -621,7 +612,7 @@ export class ExplorerSwimlane extends React.Component { return Math.max(maxBucketScore, +d3.select(cell).attr('data-bucket-score') || 0); }, 0); - const selectedCellTimes = cellsToSelect.map(e => { + const selectedCellTimes = cellsToSelect.map((e) => { return (d3.select(e).node() as NodeWithData).__clickData__.time; }); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index aaf9ff491ce32..bd6a7ee59c942 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -41,7 +41,7 @@ import { getSwimlaneContainerWidth } from './legacy_utils'; // create new job objects based on standard job config objects // new job objects just contain job id, bucket span in seconds and a selected flag. export function createJobs(jobs) { - return jobs.map(job => { + return jobs.map((job) => { const bucketSpan = parseInterval(job.analysis_config.bucket_span); return { id: job.job_id, selected: false, bucketSpanSeconds: bucketSpan.asSeconds() }; }); @@ -78,7 +78,7 @@ export async function loadFilteredTopInfluencers( // Add the specified influencer(s) to ensure they are used in the filter // even if their influencer score for the selected time range is zero. - influencers.forEach(influencer => { + influencers.forEach((influencer) => { const fieldName = influencer.fieldName; if (recordInfluencersByName[influencer.fieldName] === undefined) { recordInfluencersByName[influencer.fieldName] = []; @@ -87,9 +87,9 @@ export async function loadFilteredTopInfluencers( }); // Add the influencers from the top scoring anomalies. - records.forEach(record => { + records.forEach((record) => { const influencersByName = record.influencers || []; - influencersByName.forEach(influencer => { + influencersByName.forEach((influencer) => { const fieldName = influencer.influencer_field_name; const fieldValues = influencer.influencer_field_values; if (recordInfluencersByName[fieldName] === undefined) { @@ -100,15 +100,15 @@ export async function loadFilteredTopInfluencers( }); const uniqValuesByName = {}; - Object.keys(recordInfluencersByName).forEach(fieldName => { + Object.keys(recordInfluencersByName).forEach((fieldName) => { const fieldValues = recordInfluencersByName[fieldName]; uniqValuesByName[fieldName] = uniq(fieldValues); }); const filterInfluencers = []; - Object.keys(uniqValuesByName).forEach(fieldName => { + Object.keys(uniqValuesByName).forEach((fieldName) => { // Find record influencers with the same field name as the clicked on cell(s). - const matchingFieldName = influencers.find(influencer => { + const matchingFieldName = influencers.find((influencer) => { return influencer.fieldName === fieldName; }); @@ -117,7 +117,7 @@ export async function loadFilteredTopInfluencers( filterInfluencers.push(...influencers); } else { // For other field names, add values from all records. - uniqValuesByName[fieldName].forEach(fieldValue => { + uniqValuesByName[fieldName].forEach((fieldValue) => { filterInfluencers.push({ fieldName, fieldValue }); }); } @@ -135,7 +135,7 @@ export async function loadFilteredTopInfluencers( export function getInfluencers(selectedJobs = []) { const influencers = []; - selectedJobs.forEach(selectedJob => { + selectedJobs.forEach((selectedJob) => { const job = mlJobService.getJob(selectedJob.id); if (job !== undefined && job.analysis_config && job.analysis_config.influencers) { influencers.push(...job.analysis_config.influencers); @@ -212,7 +212,7 @@ export function getSelectionInfluencers(selectedCells, fieldName) { selectedCells.viewByFieldName !== undefined && selectedCells.viewByFieldName !== VIEW_BY_JOB_LABEL ) { - return selectedCells.lanes.map(laneLabel => ({ fieldName, fieldValue: laneLabel })); + return selectedCells.lanes.map((laneLabel) => ({ fieldName, fieldValue: laneLabel })); } return []; @@ -228,7 +228,7 @@ export function getSelectionJobIds(selectedCells, selectedJobs) { return selectedCells.lanes; } - return selectedJobs.map(d => d.id); + return selectedJobs.map((d) => d.id); } export function getSwimlaneBucketInterval(selectedJobs, swimlaneContainerWidth) { @@ -275,15 +275,15 @@ export function loadViewByTopFieldValuesForSelectedTime( swimlaneLimit, noInfluencersConfigured ) { - const selectedJobIds = selectedJobs.map(d => d.id); + const selectedJobIds = selectedJobs.map((d) => d.id); // Find the top field values for the selected time, and then load the 'view by' // swimlane over the full time range for those specific field values. - return new Promise(resolve => { + return new Promise((resolve) => { if (viewBySwimlaneFieldName !== VIEW_BY_JOB_LABEL) { mlResultsService .getTopInfluencers(selectedJobIds, earliestMs, latestMs, swimlaneLimit) - .then(resp => { + .then((resp) => { if (resp.influencers[viewBySwimlaneFieldName] === undefined) { resolve([]); } @@ -291,7 +291,7 @@ export function loadViewByTopFieldValuesForSelectedTime( const topFieldValues = []; const topInfluencers = resp.influencers[viewBySwimlaneFieldName]; if (Array.isArray(topInfluencers)) { - topInfluencers.forEach(influencerData => { + topInfluencers.forEach((influencerData) => { if (influencerData.maxAnomalyScore > 0) { topFieldValues.push(influencerData.influencerFieldValue); } @@ -311,7 +311,7 @@ export function loadViewByTopFieldValuesForSelectedTime( ).asSeconds() + 's', swimlaneLimit ) - .then(resp => { + .then((resp) => { const topFieldValues = Object.keys(resp.results); resolve(topFieldValues); }); @@ -328,19 +328,19 @@ export function getViewBySwimlaneOptions({ selectedCells, selectedJobs, }) { - const selectedJobIds = selectedJobs.map(d => d.id); + const selectedJobIds = selectedJobs.map((d) => d.id); // Unique influencers for the selected job(s). const viewByOptions = chain( mlJobService.jobs.reduce((reducedViewByOptions, job) => { - if (selectedJobIds.some(jobId => jobId === job.job_id)) { + if (selectedJobIds.some((jobId) => jobId === job.job_id)) { return reducedViewByOptions.concat(job.analysis_config.influencers || []); } return reducedViewByOptions; }, []) ) .uniq() - .sortBy(fieldName => fieldName.toLowerCase()) + .sortBy((fieldName) => fieldName.toLowerCase()) .value(); viewByOptions.push(VIEW_BY_JOB_LABEL); @@ -360,12 +360,12 @@ export function getViewBySwimlaneOptions({ } else if (mlJobService.jobs.length > 0 && selectedJobIds.length > 0) { // For a single job, default to the first partition, over, // by or influencer field of the first selected job. - const firstSelectedJob = mlJobService.jobs.find(job => { + const firstSelectedJob = mlJobService.jobs.find((job) => { return job.job_id === selectedJobIds[0]; }); const firstJobInfluencers = firstSelectedJob.analysis_config.influencers || []; - firstSelectedJob.analysis_config.detectors.forEach(detector => { + firstSelectedJob.analysis_config.detectors.forEach((detector) => { if ( detector.partition_field_name !== undefined && firstJobInfluencers.indexOf(detector.partition_field_name) !== -1 @@ -416,7 +416,7 @@ export function getViewBySwimlaneOptions({ Array.isArray(viewBySwimlaneOptions) && Array.isArray(filteredFields) ) { - const filteredOptions = viewBySwimlaneOptions.filter(option => { + const filteredOptions = viewBySwimlaneOptions.filter((option) => { return ( filteredFields.includes(option) || option === VIEW_BY_JOB_LABEL || @@ -538,10 +538,10 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval, const jobIds = selectedCells !== undefined && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL ? selectedCells.lanes - : selectedJobs.map(d => d.id); + : selectedJobs.map((d) => d.id); const timeRange = getSelectionTimeRange(selectedCells, interval, bounds); - return new Promise(resolve => { + return new Promise((resolve) => { ml.annotations .getAnnotations({ jobIds, @@ -550,13 +550,13 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval, maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE, }) .toPromise() - .then(resp => { + .then((resp) => { if (resp.error !== undefined || resp.annotations === undefined) { return resolve([]); } const annotationsData = []; - jobIds.forEach(jobId => { + jobIds.forEach((jobId) => { const jobAnnotations = resp.annotations[jobId]; if (jobAnnotations !== undefined) { annotationsData.push(...jobAnnotations); @@ -574,7 +574,7 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval, }) ); }) - .catch(resp => { + .catch((resp) => { console.log('Error loading list of annotations for jobs list:', resp); // Silently fail and just return an empty array for annotations to not break the UI. return resolve([]); @@ -613,10 +613,10 @@ export async function loadAnomaliesTableData( influencersFilterQuery ) .toPromise() - .then(resp => { + .then((resp) => { const anomalies = resp.anomalies; const detectorsByJob = mlJobService.detectorsByJob; - anomalies.forEach(anomaly => { + anomalies.forEach((anomaly) => { // Add a detector property to each anomaly. // Default to functionDescription if no description available. // TODO - when job_service is moved server_side, move this to server endpoint. @@ -662,7 +662,7 @@ export async function loadAnomaliesTableData( jobIds, }); }) - .catch(resp => { + .catch((resp) => { console.log('Explorer - error loading data for anomalies table:', resp); reject(); }); @@ -680,7 +680,7 @@ export async function loadDataForCharts( selectedCells, influencersFilterQuery ) { - return new Promise(resolve => { + return new Promise((resolve) => { // Just skip doing the request when this function // is called without the minimum required data. if ( @@ -705,7 +705,7 @@ export async function loadDataForCharts( 500, influencersFilterQuery ) - .then(resp => { + .then((resp) => { // Ignore this response if it's returned by an out of date promise if (newRequestCount < requestCount) { resolve([]); @@ -715,7 +715,6 @@ export async function loadDataForCharts( (selectedCells !== undefined && Object.keys(selectedCells).length > 0) || influencersFilterQuery !== undefined ) { - console.log('Explorer anomaly charts data set:', resp.records); resolve(resp.records); } @@ -725,7 +724,7 @@ export async function loadDataForCharts( } export function loadOverallData(selectedJobs, interval, bounds) { - return new Promise(resolve => { + return new Promise((resolve) => { // Loads the overall data components i.e. the overall swimlane and influencers list. if (selectedJobs === null) { resolve({ @@ -738,7 +737,7 @@ export function loadOverallData(selectedJobs, interval, bounds) { // Ensure the search bounds align to the bucketing interval used in the swimlane so // that the first and last buckets are complete. const searchBounds = getBoundsRoundedToInterval(bounds, interval, false); - const selectedJobIds = selectedJobs.map(d => d.id); + const selectedJobIds = selectedJobs.map((d) => d.id); // Load the overall bucket scores by time. // Pass the interval in seconds as the swimlane relies on a fixed number of seconds between buckets @@ -757,14 +756,13 @@ export function loadOverallData(selectedJobs, interval, bounds) { overallBucketsBounds.max.valueOf(), interval.asSeconds() + 's' ) - .then(resp => { + .then((resp) => { const overallSwimlaneData = processOverallResults( resp.results, searchBounds, interval.asSeconds() ); - console.log('Explorer overall swimlane data set:', overallSwimlaneData); resolve({ loading: false, overallSwimlaneData, @@ -782,8 +780,8 @@ export function loadViewBySwimlane( influencersFilterQuery, noInfluencersConfigured ) { - return new Promise(resolve => { - const finish = resp => { + return new Promise((resolve) => { + const finish = (resp) => { if (resp !== undefined) { const viewBySwimlaneData = processViewByResults( resp.results, @@ -795,7 +793,6 @@ export function loadViewBySwimlane( getSwimlaneContainerWidth(noInfluencersConfigured) ).asSeconds() ); - console.log('Explorer view by swimlane data set:', viewBySwimlaneData); resolve({ viewBySwimlaneData, @@ -819,7 +816,7 @@ export function loadViewBySwimlane( getSwimlaneBucketInterval(selectedJobs, getSwimlaneContainerWidth(noInfluencersConfigured)), false ); - const selectedJobIds = selectedJobs.map(d => d.id); + const selectedJobIds = selectedJobs.map((d) => d.id); // load scores by influencer/jobId value and time. // Pass the interval in seconds as the swimlane relies on a fixed number of seconds between buckets @@ -866,7 +863,7 @@ export async function loadTopInfluencers( noInfluencersConfigured, influencersFilterQuery ) { - return new Promise(resolve => { + return new Promise((resolve) => { if (noInfluencersConfigured !== true) { mlResultsService .getTopInfluencers( @@ -877,9 +874,8 @@ export async function loadTopInfluencers( influencers, influencersFilterQuery ) - .then(resp => { + .then((resp) => { // TODO - sort the influencers keys so that the partition field(s) are first. - console.log('Explorer top influencers data set:', resp.influencers); resolve(resp.influencers); }); } else { diff --git a/x-pack/plugins/ml/public/application/explorer/has_matching_points.ts b/x-pack/plugins/ml/public/application/explorer/has_matching_points.ts index 397615d68f189..1af399d9a70b5 100644 --- a/x-pack/plugins/ml/public/application/explorer/has_matching_points.ts +++ b/x-pack/plugins/ml/public/application/explorer/has_matching_points.ts @@ -17,13 +17,13 @@ export const hasMatchingPoints = ({ swimlaneData, }: HasMatchingPointsParams): boolean => { // If filtered fields includes a wildcard search maskAll only if there are no points matching the pattern - const wildCardField = filteredFields.find(field => /\@kuery-wildcard\@$/.test(field)); + const wildCardField = filteredFields.find((field) => /\@kuery-wildcard\@$/.test(field)); const substring = wildCardField !== undefined ? wildCardField.replace(/\@kuery-wildcard\@$/, '') : null; return ( substring !== null && - swimlaneData.points.some(point => { + swimlaneData.points.some((point) => { return point.laneLabel.includes(substring); }) ); diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/get_index_pattern.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/get_index_pattern.ts index 9b6c7e4fb99bc..98e630d0028f2 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/get_index_pattern.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/get_index_pattern.ts @@ -13,7 +13,7 @@ import { getInfluencers, ExplorerJob } from '../../explorer_utils'; export function getIndexPattern(selectedJobs: ExplorerJob[]) { return { title: ML_RESULTS_INDEX_PATTERN, - fields: getInfluencers(selectedJobs).map(influencer => ({ + fields: getInfluencers(selectedJobs).map((influencer) => ({ name: influencer, type: 'string', aggregatable: true, diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts index 0d84179c572d2..819f6ca1cac92 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts @@ -23,7 +23,7 @@ export function setInfluencerFilterSettings( const { selectedCells, viewBySwimlaneOptions } = state; let selectedViewByFieldName = state.viewBySwimlaneFieldName; - const filteredViewBySwimlaneOptions = viewBySwimlaneOptions.filter(d => + const filteredViewBySwimlaneOptions = viewBySwimlaneOptions.filter((d) => filteredFields.includes(d) ); diff --git a/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx b/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx index 03e3273b80832..7f7a8fc5a70bd 100644 --- a/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx +++ b/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx @@ -15,7 +15,7 @@ import { EuiSelect } from '@elastic/eui'; const limitOptions = [5, 10, 25, 50]; -const euiOptions = limitOptions.map(limit => ({ +const euiOptions = limitOptions.map((limit) => ({ value: limit, text: `${limit}`, })); diff --git a/x-pack/plugins/ml/public/application/formatters/format_value.ts b/x-pack/plugins/ml/public/application/formatters/format_value.ts index abafe65615156..1a696d6e01dde 100644 --- a/x-pack/plugins/ml/public/application/formatters/format_value.ts +++ b/x-pack/plugins/ml/public/application/formatters/format_value.ts @@ -39,7 +39,7 @@ export function formatValue( // Currently only multi-value response is for lat_long detectors. // Return with array style formatting, with items formatted as numbers, rather than // the default String format which is set for geo_point and geo_shape fields. - const values = value.map(val => formatSingleValue(val, mlFunction, undefined, record)); + const values = value.map((val) => formatSingleValue(val, mlFunction, undefined, record)); return `[${values}]`; } } else { @@ -73,20 +73,14 @@ function formatSingleValue( record !== undefined && record.timestamp !== undefined ? new Date(record.timestamp) : new Date(); - const utcMoment = moment - .utc(d) - .startOf('week') - .add(value, 's'); + const utcMoment = moment.utc(d).startOf('week').add(value, 's'); return moment(utcMoment.valueOf()).format('ddd HH:mm'); } else if (mlFunction === 'time_of_day') { const d = record !== undefined && record.timestamp !== undefined ? new Date(record.timestamp) : new Date(); - const utcMoment = moment - .utc(d) - .startOf('day') - .add(value, 's'); + const utcMoment = moment.utc(d).startOf('day').add(value, 's'); return moment(utcMoment.valueOf()).format('HH:mm'); } else { if (fieldFormat !== undefined) { diff --git a/x-pack/plugins/ml/public/application/formatters/number_as_ordinal.test.ts b/x-pack/plugins/ml/public/application/formatters/number_as_ordinal.test.ts index 5b378934ed5e8..d5f357106b735 100644 --- a/x-pack/plugins/ml/public/application/formatters/number_as_ordinal.test.ts +++ b/x-pack/plugins/ml/public/application/formatters/number_as_ordinal.test.ts @@ -21,7 +21,7 @@ describe('ML - numberAsOrdinal formatter', () => { { number: 100, asOrdinal: '100th' }, ]; test('returns the expected numeral format', () => { - tests.forEach(test => { + tests.forEach((test) => { expect(numberAsOrdinal(test.number)).toBe(test.asOrdinal); }); }); diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx index c4c32c1f4c5f2..4f6e520298efb 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx @@ -114,7 +114,7 @@ export const CustomUrlEditor: FC = ({ }; const onQueryEntitiesChange = (selectedOptions: EuiComboBoxOptionOption[]) => { - const selectedFieldNames = selectedOptions.map(option => option.label); + const selectedFieldNames = selectedOptions.map((option) => option.label); const kibanaSettings = customUrl.kibanaSettings; setEditCustomUrl({ @@ -159,22 +159,22 @@ export const CustomUrlEditor: FC = ({ const { label, type, timeRange, kibanaSettings, otherUrlSettings } = customUrl; - const dashboardOptions = dashboards.map(dashboard => { + const dashboardOptions = dashboards.map((dashboard) => { return { value: dashboard.id, text: dashboard.title }; }); - const indexPatternOptions = indexPatterns.map(indexPattern => { + const indexPatternOptions = indexPatterns.map((indexPattern) => { return { value: indexPattern.id, text: indexPattern.title }; }); - const entityOptions = queryEntityFieldNames.map(fieldName => ({ label: fieldName })); + const entityOptions = queryEntityFieldNames.map((fieldName) => ({ label: fieldName })); let selectedEntityOptions: EuiComboBoxOptionOption[] = []; if (kibanaSettings !== undefined && kibanaSettings.queryFieldNames !== undefined) { const queryFieldNames: string[] = kibanaSettings.queryFieldNames; - selectedEntityOptions = queryFieldNames.map(fieldName => ({ label: fieldName })); + selectedEntityOptions = queryFieldNames.map((fieldName) => ({ label: fieldName })); } - const timeRangeOptions = Object.values(TIME_RANGE_TYPE).map(timeRangeType => ({ + const timeRangeOptions = Object.values(TIME_RANGE_TYPE).map((timeRangeType) => ({ value: timeRangeType, text: timeRangeType, })); diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx index 1b18afaf2569f..7e228757dfd90 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx @@ -102,10 +102,10 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust const onTestButtonClick = (index: number) => { if (index < customUrls.length) { getTestUrl(job, customUrls[index]) - .then(testUrl => { + .then((testUrl) => { openCustomUrlWindow(testUrl, customUrls[index]); }) - .catch(resp => { + .catch((resp) => { // eslint-disable-next-line no-console console.error('Error obtaining URL for test:', resp); @@ -163,7 +163,7 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust onLabelChange(e, index)} + onChange={(e) => onLabelChange(e, index)} data-test-subj={`mlJobEditCustomUrlLabelInput_${index}`} /> @@ -184,7 +184,7 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust }} fullWidth={true} value={customUrl.url_value} - onChange={e => onUrlValueChange(e, index)} + onChange={(e) => onUrlValueChange(e, index)} onBlur={() => { setExpandedUrlIndex(null); }} @@ -216,7 +216,7 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust value={(customUrl as KibanaUrlConfig).time_range || ''} isInvalid={isInvalidTimeRange} placeholder={TIME_RANGE_TYPE.AUTO} - onChange={e => onTimeRangeChange(e, index)} + onChange={(e) => onTimeRangeChange(e, index)} /> diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js index 18873b3b6b6d3..0b33efa3f9ff1 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js @@ -47,7 +47,7 @@ export function getNewCustomUrlDefaults(job, dashboards, indexPatterns) { datafeedConfig.indices.length > 0 ) { const datafeedIndex = datafeedConfig.indices[0]; - let defaultIndexPattern = indexPatterns.find(indexPattern => { + let defaultIndexPattern = indexPatterns.find((indexPattern) => { return indexPattern.title === datafeedIndex; }); @@ -87,7 +87,7 @@ export function getQueryEntityFieldNames(job) { detectors.forEach((detector, detectorIndex) => { const partitioningFields = getPartitioningFieldNames(job, detectorIndex); - partitioningFields.forEach(fieldName => { + partitioningFields.forEach((fieldName) => { if (entityFieldNames.indexOf(fieldName) === -1) { entityFieldNames.push(fieldName); } @@ -139,7 +139,7 @@ function buildDashboardUrlFromSettings(settings) { const savedObjectsClient = getSavedObjectsClient(); savedObjectsClient .get('dashboard', dashboardId) - .then(response => { + .then((response) => { // Use the filters from the saved dashboard if there are any. let filters = []; @@ -176,7 +176,7 @@ function buildDashboardUrlFromSettings(settings) { // template to inject the time parameters. useHash: false, }) - .then(urlValue => { + .then((urlValue) => { const urlToAdd = { url_name: settings.label, url_value: decodeURIComponent(`dashboards${url.parse(urlValue).hash}`), @@ -190,7 +190,7 @@ function buildDashboardUrlFromSettings(settings) { resolve(urlToAdd); }); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -300,7 +300,7 @@ export function getTestUrl(job, customUrl) { rest_total_hits_as_int: true, body, }) - .then(resp => { + .then((resp) => { if (resp.hits.total > 0) { const record = resp.hits.hits[0]._source; testUrl = replaceTokensInUrlValue(customUrl, bucketSpanSecs, record, 'timestamp'); @@ -308,7 +308,7 @@ export function getTestUrl(job, customUrl) { } else { // No anomalies yet for this job, so do a preview of the search // configured in the job datafeed to obtain sample docs. - mlJobService.searchPreview(job).then(response => { + mlJobService.searchPreview(job).then((response) => { let testDoc; const docTimeFieldName = job.data_description.time_field; @@ -348,7 +348,7 @@ export function getTestUrl(job, customUrl) { }); } }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js index 0d88aa29d70e9..d05278c19b5a5 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js @@ -81,9 +81,9 @@ export class CreateWatchFlyoutUI extends Component { }); }; - showFlyout = jobId => { + showFlyout = (jobId) => { loadFullJob(jobId) - .then(job => { + .then((job) => { const bucketSpan = job.analysis_config.bucket_span; mlCreateWatchService.config.includeInfluencers = job.analysis_config.influencers.length > 0; @@ -94,7 +94,7 @@ export class CreateWatchFlyoutUI extends Component { isFlyoutVisible: true, }); }) - .catch(error => { + .catch((error) => { console.error(error); }); }; @@ -103,11 +103,11 @@ export class CreateWatchFlyoutUI extends Component { const { toasts } = this.props.kibana.services.notifications; mlCreateWatchService .createNewWatch(this.state.jobId) - .then(resp => { + .then((resp) => { toasts.addSuccess(getSuccessToast(resp.id, resp.url)); this.closeFlyout(true); }) - .catch(error => { + .catch((error) => { toasts.addDanger( i18n.translate( 'xpack.ml.jobsList.createWatchFlyout.watchNotSavedErrorNotificationMessage', diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js index 307fa79f5dea2..eeff91be130ea 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js @@ -80,7 +80,7 @@ class CreateWatchService { this.config.threshold = { display, val }; } - createNewWatch = function(jobId) { + createNewWatch = function (jobId) { return new Promise((resolve, reject) => { this.status.watch = this.STATUS.SAVING; if (jobId !== undefined) { @@ -167,13 +167,13 @@ class CreateWatchService { saveWatch(watchModel) .then(() => { this.status.watch = this.STATUS.SAVED; - this.config.watcherEditURL = `${basePath.get()}/app/kibana#/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; + this.config.watcherEditURL = `${basePath.get()}/app/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; resolve({ id, url: this.config.watcherEditURL, }); }) - .catch(resp => { + .catch((resp) => { this.status.watch = this.STATUS.SAVE_FAILED; reject(resp); }); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js index 0595ce5caf931..97520626783fa 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js @@ -66,7 +66,7 @@ export class CreateWatch extends Component { } // load elasticsearch settings to see if email has been configured - ml.getNotificationSettings().then(resp => { + ml.getNotificationSettings().then((resp) => { if (has(resp, 'defaults.xpack.notification.email')) { this.setState({ emailEnabled: true }); } @@ -82,27 +82,27 @@ export class CreateWatch extends Component { }); } - onThresholdChange = threshold => { + onThresholdChange = (threshold) => { this.setState({ threshold }, () => { this.config.threshold = threshold; }); }; - onIntervalChange = e => { + onIntervalChange = (e) => { const interval = e.target.value; this.setState({ interval }, () => { this.config.interval = interval; }); }; - onIncludeEmailChanged = e => { + onIncludeEmailChanged = (e) => { const includeEmail = e.target.checked; this.setState({ includeEmail }, () => { this.config.includeEmail = includeEmail; }); }; - onEmailChange = e => { + onEmailChange = (e) => { const email = e.target.value; this.setState({ email }, () => { this.config.email = email; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx index 727830a58bb41..ff930832bde3e 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx @@ -68,7 +68,7 @@ export const SEVERITY_OPTIONS: TableSeverity[] = [ function optionValueToThreshold(value: number) { // Get corresponding threshold object with required display and val properties from the specified value. - let threshold = SEVERITY_OPTIONS.find(opt => opt.val === value); + let threshold = SEVERITY_OPTIONS.find((opt) => opt.val === value); // Default to warning if supplied value doesn't map to one of the options. if (threshold === undefined) { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js index 3e129a174c9e0..1e3ec6241311b 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js @@ -56,7 +56,7 @@ export class DeleteJobModal extends Component { this.setState({ isModalVisible: false }); }; - showModal = jobs => { + showModal = (jobs) => { this.setState({ jobs, isModalVisible: true, @@ -74,7 +74,7 @@ export class DeleteJobModal extends Component { }, DELETING_JOBS_REFRESH_INTERVAL_MS); }; - setEL = el => { + setEL = (el) => { if (el) { this.el = el; } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index 9066e41fb8f23..b463322ea55db 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -108,17 +108,17 @@ export class EditJobFlyoutUI extends Component { ); } - showFlyout = jobLite => { + showFlyout = (jobLite) => { const hasDatafeed = jobLite.hasDatafeed; loadFullJob(jobLite.id) - .then(job => { + .then((job) => { this.extractJob(job, hasDatafeed); this.setState({ job, isFlyoutVisible: true, }); }) - .catch(error => { + .catch((error) => { console.error(error); }); }; @@ -147,7 +147,7 @@ export class EditJobFlyoutUI extends Component { jobGroups: job.groups !== undefined ? job.groups : [], jobModelMemoryLimit: mml, jobDetectors: detectors, - jobDetectorDescriptions: detectors.map(d => d.detector_description), + jobDetectorDescriptions: detectors.map((d) => d.detector_description), jobBucketSpan: bucketSpan, jobCustomUrls: customUrls, datafeedQuery: hasDatafeed ? JSON.stringify(datafeedConfig.query, null, 2) : '', @@ -171,7 +171,7 @@ export class EditJobFlyoutUI extends Component { }); } - setJobDetails = jobDetails => { + setJobDetails = (jobDetails) => { let { jobModelMemoryLimitValidationError, jobGroupsValidationError } = this.state; if (jobDetails.jobModelMemoryLimit !== undefined) { @@ -180,7 +180,7 @@ export class EditJobFlyoutUI extends Component { } if (jobDetails.jobGroups !== undefined) { - if (jobDetails.jobGroups.some(j => this.props.allJobIds.includes(j))) { + if (jobDetails.jobGroups.some((j) => this.props.allJobIds.includes(j))) { jobGroupsValidationError = i18n.translate( 'xpack.ml.jobsList.editJobFlyout.groupsAndJobsHasSameIdErrorMessage', { @@ -204,19 +204,19 @@ export class EditJobFlyoutUI extends Component { }); }; - setDetectorDescriptions = jobDetectorDescriptions => { + setDetectorDescriptions = (jobDetectorDescriptions) => { this.setState({ ...jobDetectorDescriptions, }); }; - setDatafeed = datafeed => { + setDatafeed = (datafeed) => { this.setState({ ...datafeed, }); }; - setCustomUrls = jobCustomUrls => { + setCustomUrls = (jobCustomUrls) => { const isValidJobCustomUrls = isValidCustomUrls(jobCustomUrls); this.setState({ jobCustomUrls, @@ -251,7 +251,7 @@ export class EditJobFlyoutUI extends Component { this.refreshJobs(); this.closeFlyout(true); }) - .catch(error => { + .catch((error) => { console.error(error); toasts.addDanger( i18n.translate('xpack.ml.jobsList.editJobFlyout.changesNotSavedNotificationMessage', { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js index a49a2af896be2..fcd2c09f72767 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js @@ -32,7 +32,7 @@ export function saveJob(job, newJobData, finish) { .then(() => { resolve(); }) - .catch(error => { + .catch((error) => { reject(error); }); }; @@ -41,14 +41,14 @@ export function saveJob(job, newJobData, finish) { if (Object.keys(jobData).length) { mlJobService .updateJob(job.job_id, jobData) - .then(resp => { + .then((resp) => { if (resp.success) { saveDatafeedWrapper(); } else { reject(resp); } }) - .catch(error => { + .catch((error) => { reject(error); }); } else { @@ -61,7 +61,7 @@ function saveDatafeed(datafeedData, job) { return new Promise((resolve, reject) => { if (Object.keys(datafeedData).length) { const datafeedId = job.datafeed_config.datafeed_id; - mlJobService.updateDatafeed(datafeedId, datafeedData).then(resp => { + mlJobService.updateDatafeed(datafeedId, datafeedData).then((resp) => { if (resp.success) { resolve(); } else { @@ -84,10 +84,10 @@ export function loadSavedDashboards(maxNumber) { fields: ['title'], perPage: maxNumber, }) - .then(resp => { + .then((resp) => { const savedObjects = resp.savedObjects; if (savedObjects !== undefined) { - const dashboards = savedObjects.map(savedObj => { + const dashboards = savedObjects.map((savedObj) => { return { id: savedObj.id, title: savedObj.attributes.title }; }); @@ -98,7 +98,7 @@ export function loadSavedDashboards(maxNumber) { resolve(dashboards); } }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -116,10 +116,10 @@ export function loadIndexPatterns(maxNumber) { fields: ['title'], perPage: maxNumber, }) - .then(resp => { + .then((resp) => { const savedObjects = resp.savedObjects; if (savedObjects !== undefined) { - const indexPatterns = savedObjects.map(savedObj => { + const indexPatterns = savedObjects.map((savedObj) => { return { id: savedObj.id, title: savedObj.attributes.title }; }); @@ -130,7 +130,7 @@ export function loadIndexPatterns(maxNumber) { resolve(indexPatterns); } }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -183,7 +183,7 @@ function extractDetectorDescriptions(job, newJobData) { })); const originalDetectors = job.analysis_config.detectors; - originalDetectors.forEach(d => { + originalDetectors.forEach((d) => { if (descriptions[d.detector_index].description !== d.detector_description) { detectors.push(descriptions[d.detector_index]); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index 6cb9dde056c5c..7af27fc22e34c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -84,10 +84,10 @@ class CustomUrlsUI extends Component { componentDidMount() { const { toasts } = this.props.kibana.services.notifications; loadSavedDashboards(MAX_NUMBER_DASHBOARDS) - .then(dashboards => { + .then((dashboards) => { this.setState({ dashboards }); }) - .catch(resp => { + .catch((resp) => { // eslint-disable-next-line no-console console.error('Error loading list of dashboards:', resp); toasts.addDanger( @@ -101,10 +101,10 @@ class CustomUrlsUI extends Component { }); loadIndexPatterns(MAX_NUMBER_INDEX_PATTERNS) - .then(indexPatterns => { + .then((indexPatterns) => { this.setState({ indexPatterns }); }) - .catch(resp => { + .catch((resp) => { // eslint-disable-next-line no-console console.error('Error loading list of dashboards:', resp); toasts.addDanger( @@ -120,7 +120,7 @@ class CustomUrlsUI extends Component { editNewCustomUrl = () => { // Opens the editor for configuring a new custom URL. - this.setState(prevState => { + this.setState((prevState) => { const { dashboards, indexPatterns } = prevState; return { @@ -138,7 +138,7 @@ class CustomUrlsUI extends Component { addNewCustomUrl = () => { buildCustomUrlFromSettings(this.state.editorSettings as CustomUrlSettings) - .then(customUrl => { + .then((customUrl) => { const customUrls = [...this.state.customUrls, customUrl]; this.props.setCustomUrls(customUrls); this.setState({ editorOpen: false }); @@ -163,12 +163,12 @@ class CustomUrlsUI extends Component { const { toasts } = this.props.kibana.services.notifications; const job = this.props.job; buildCustomUrlFromSettings(this.state.editorSettings as CustomUrlSettings) - .then(customUrl => { + .then((customUrl) => { getTestUrl(job, customUrl) - .then(testUrl => { + .then((testUrl) => { openCustomUrlWindow(testUrl, customUrl); }) - .catch(resp => { + .catch((resp) => { // eslint-disable-next-line no-console console.error('Error obtaining URL for test:', resp); toasts.addWarning( @@ -181,7 +181,7 @@ class CustomUrlsUI extends Component { ); }); }) - .catch(resp => { + .catch((resp) => { // eslint-disable-next-line no-console console.error('Error building custom URL from settings:', resp); toasts.addWarning( diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js index 3d81b767021a0..a038f761d47a0 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js @@ -61,19 +61,19 @@ export class Datafeed extends Component { }; } - onQueryChange = query => { + onQueryChange = (query) => { this.setDatafeed({ datafeedQuery: query }); }; - onQueryDelayChange = e => { + onQueryDelayChange = (e) => { this.setDatafeed({ datafeedQueryDelay: e.target.value }); }; - onFrequencyChange = e => { + onFrequencyChange = (e) => { this.setDatafeed({ datafeedFrequency: e.target.value }); }; - onScrollSizeChange = e => { + onScrollSizeChange = (e) => { this.setDatafeed({ datafeedScrollSize: +e.target.value }); }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/detectors.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/detectors.js index 0296004f736a2..e9ba65f14138b 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/detectors.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/detectors.js @@ -16,7 +16,7 @@ export class Detectors extends Component { constructor(props) { super(props); - this.detectors = mlJobService.getJobGroups().map(g => ({ label: g.id })); + this.detectors = mlJobService.getJobGroups().map((g) => ({ label: g.id })); this.state = { detectors: [], @@ -47,7 +47,7 @@ export class Detectors extends Component { {detectorDescriptions.map((d, i) => ( - this.onDescriptionChange(e, i)} /> + this.onDescriptionChange(e, i)} /> ))} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js index 672fd8cefaaba..974afafc08b6b 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js @@ -33,18 +33,18 @@ export class JobDetails extends Component { // load groups to populate the select options ml.jobs .groups() - .then(resp => { - const groups = resp.map(g => ({ label: g.id })); + .then((resp) => { + const groups = resp.map((g) => ({ label: g.id })); this.setState({ groups }); }) - .catch(error => { + .catch((error) => { console.error('Could not load groups', error); }); } static getDerivedStateFromProps(props) { const selectedGroups = - props.jobGroups !== undefined ? props.jobGroups.map(g => ({ label: g })) : []; + props.jobGroups !== undefined ? props.jobGroups.map((g) => ({ label: g })) : []; return { description: props.jobDescription, @@ -55,16 +55,16 @@ export class JobDetails extends Component { }; } - onDescriptionChange = e => { + onDescriptionChange = (e) => { this.setJobDetails({ jobDescription: e.target.value }); }; - onMmlChange = e => { + onMmlChange = (e) => { this.setJobDetails({ jobModelMemoryLimit: e.target.value }); }; - onGroupsChange = selectedGroups => { - this.setJobDetails({ jobGroups: selectedGroups.map(g => g.label) }); + onGroupsChange = (selectedGroups) => { + this.setJobDetails({ jobGroups: selectedGroups.map((g) => g.label) }); }; onCreateGroup = (input, flattenedOptions) => { @@ -82,7 +82,7 @@ export class JobDetails extends Component { // Create the option if it doesn't exist. if ( flattenedOptions.findIndex( - option => option.label.trim().toLowerCase() === normalizedSearchValue + (option) => option.label.trim().toLowerCase() === normalizedSearchValue ) === -1 ) { groups.push(newGroup); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js index bb4bed93f922e..254c546df65bc 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js @@ -33,9 +33,9 @@ export function actionsMenuContent( defaultMessage: 'Start datafeed', }), icon: 'play', - enabled: item => item.deleting !== true && canStartStopDatafeed, - available: item => isStartable([item]), - onClick: item => { + enabled: (item) => item.deleting !== true && canStartStopDatafeed, + available: (item) => isStartable([item]), + onClick: (item) => { showStartDatafeedModal([item]); closeMenu(); }, @@ -49,9 +49,9 @@ export function actionsMenuContent( defaultMessage: 'Stop datafeed', }), icon: 'stop', - enabled: item => item.deleting !== true && canStartStopDatafeed, - available: item => isStoppable([item]), - onClick: item => { + enabled: (item) => item.deleting !== true && canStartStopDatafeed, + available: (item) => isStoppable([item]), + onClick: (item) => { stopDatafeeds([item], refreshJobs); closeMenu(true); }, @@ -65,9 +65,9 @@ export function actionsMenuContent( defaultMessage: 'Close job', }), icon: 'cross', - enabled: item => item.deleting !== true && canCloseJob, - available: item => isClosable([item]), - onClick: item => { + enabled: (item) => item.deleting !== true && canCloseJob, + available: (item) => isClosable([item]), + onClick: (item) => { closeJobs([item], refreshJobs); closeMenu(true); }, @@ -81,19 +81,19 @@ export function actionsMenuContent( defaultMessage: 'Clone job', }), icon: 'copy', - enabled: item => { + enabled: (item) => { // We only allow cloning of a job if the user has the right permissions and can still access // the indexPattern the job was created for. An indexPattern could either have been deleted // since the the job was created or the current user doesn't have the required permissions to // access the indexPattern. const indexPatternNames = getIndexPatternNames(); - const jobIndicesAvailable = item.datafeedIndices.every(dfiName => { - return indexPatternNames.some(ipName => ipName === dfiName); + const jobIndicesAvailable = item.datafeedIndices.every((dfiName) => { + return indexPatternNames.some((ipName) => ipName === dfiName); }); return item.deleting !== true && canCreateJob && jobIndicesAvailable; }, - onClick: item => { + onClick: (item) => { cloneJob(item.id); closeMenu(true); }, @@ -107,8 +107,8 @@ export function actionsMenuContent( defaultMessage: 'Edit job', }), icon: 'pencil', - enabled: item => item.deleting !== true && canUpdateJob && canUpdateDatafeed, - onClick: item => { + enabled: (item) => item.deleting !== true && canUpdateJob && canUpdateDatafeed, + onClick: (item) => { showEditJobFlyout(item); closeMenu(); }, @@ -124,7 +124,7 @@ export function actionsMenuContent( icon: 'trash', color: 'danger', enabled: () => canDeleteJob, - onClick: item => { + onClick: (item) => { showDeleteJobModal([item]); closeMenu(); }, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js index 9406f1b3456cf..ff314c237f5d7 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js @@ -62,10 +62,10 @@ export class DatafeedPreviewPane extends Component { this.setState({ canPreviewDatafeed }); updateDatafeedPreview(this.props.job, canPreviewDatafeed) - .then(previewJson => { + .then((previewJson) => { this.setState({ previewJson, loading: false }); }) - .catch(error => { + .catch((error) => { console.log('Datafeed preview could not be loaded', error); this.setState({ loading: false }); }); @@ -89,7 +89,7 @@ function updateDatafeedPreview(job, canPreviewDatafeed) { if (canPreviewDatafeed) { mlJobService .getDatafeedPreview(job.datafeed_config.datafeed_id) - .then(resp => { + .then((resp) => { if (Array.isArray(resp)) { resolve(JSON.stringify(resp.slice(0, ML_DATA_PREVIEW_COUNT), null, 2)); } else { @@ -97,7 +97,7 @@ function updateDatafeedPreview(job, canPreviewDatafeed) { console.log('Datafeed preview could not be loaded', resp); } }) - .catch(error => { + .catch((error) => { reject(error); }); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index fa36a0626d632..8f89c4a049189 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -34,7 +34,7 @@ export function extractJobDetails(job) { }; if (job.custom_settings && job.custom_settings.custom_urls) { customUrl.items.push( - ...job.custom_settings.custom_urls.map(cu => [cu.url_name, cu.url_value, cu.time_range]) + ...job.custom_settings.custom_urls.map((cu) => [cu.url_name, cu.url_value, cu.time_range]) ); } @@ -59,13 +59,13 @@ export function extractJobDetails(job) { items: [], }; if (job.calendars) { - calendars.items = job.calendars.map(c => [ + calendars.items = job.calendars.map((c) => [ '', {c}, ]); // remove the calendars list from the general section // so not to show it twice. - const i = general.items.findIndex(item => item[0] === 'calendars'); + const i = general.items.findIndex((item) => item[0] === 'calendars'); if (i >= 0) { general.items.splice(i, 1); } @@ -81,7 +81,7 @@ export function extractJobDetails(job) { }; if (job.analysis_config && job.analysis_config.detectors) { detectors.items.push( - ...job.analysis_config.detectors.map(d => { + ...job.analysis_config.detectors.map((d) => { const stringifiedDtr = detectorToString(d); return [ stringifiedDtr, @@ -97,7 +97,7 @@ export function extractJobDetails(job) { defaultMessage: 'Influencers', }), position: 'left', - items: job.analysis_config.influencers.map(i => ['', i]), + items: job.analysis_config.influencers.map((i) => ['', i]), }; const analysisConfig = { @@ -133,7 +133,7 @@ export function extractJobDetails(job) { defaultMessage: 'Datafeed', }), position: 'left', - items: filterObjects(job.datafeed_config, true, true), + items: filterObjects(job.datafeed_config || {}, true, true), }; if (job.node) { datafeed.items.push(['node', JSON.stringify(job.node)]); @@ -141,7 +141,7 @@ export function extractJobDetails(job) { if (job.datafeed_config && job.datafeed_config.timing_stats) { // remove the timing_stats list from the datafeed section // so not to show it twice. - const i = datafeed.items.findIndex(item => item[0] === 'timing_stats'); + const i = datafeed.items.findIndex((item) => item[0] === 'timing_stats'); if (i >= 0) { datafeed.items.splice(i, 1); } @@ -183,7 +183,7 @@ export function extractJobDetails(job) { items: job.datafeed_config && job.datafeed_config.timing_stats ? filterObjects(job.datafeed_config.timing_stats) - .filter(o => o[0] !== 'total_search_time_ms') // remove total_search_time_ms as average_search_time_per_bucket_ms is better + .filter((o) => o[0] !== 'total_search_time_ms') // remove total_search_time_ms as average_search_time_per_bucket_ms is better .map(formatValues) : [], }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js index 41dfdb0dcfeed..817715dbf6413 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js @@ -56,13 +56,13 @@ export class ForecastsTable extends Component { dataCounts.earliest_record_timestamp, MAX_FORECASTS ) - .then(resp => { + .then((resp) => { this.setState({ isLoading: false, forecasts: resp.forecasts, }); }) - .catch(resp => { + .catch((resp) => { console.log('Error loading list of forecasts for jobs list:', resp); this.setState({ isLoading: false, @@ -201,7 +201,7 @@ export class ForecastsTable extends Component { defaultMessage: 'Created', }), dataType: 'date', - render: date => formatDate(date, TIME_FORMAT), + render: (date) => formatDate(date, TIME_FORMAT), textOnly: true, sortable: true, scope: 'row', @@ -212,7 +212,7 @@ export class ForecastsTable extends Component { defaultMessage: 'From', }), dataType: 'date', - render: date => formatDate(date, TIME_FORMAT), + render: (date) => formatDate(date, TIME_FORMAT), textOnly: true, sortable: true, }, @@ -222,7 +222,7 @@ export class ForecastsTable extends Component { defaultMessage: 'To', }), dataType: 'date', - render: date => formatDate(date, TIME_FORMAT), + render: (date) => formatDate(date, TIME_FORMAT), textOnly: true, sortable: true, }, @@ -238,7 +238,7 @@ export class ForecastsTable extends Component { name: i18n.translate('xpack.ml.jobsList.jobDetails.forecastsTable.memorySizeLabel', { defaultMessage: 'Memory size', }), - render: bytes => formatNumber(bytes, '0b'), + render: (bytes) => formatNumber(bytes, '0b'), sortable: true, }, { @@ -246,7 +246,7 @@ export class ForecastsTable extends Component { name: i18n.translate('xpack.ml.jobsList.jobDetails.forecastsTable.processingTimeLabel', { defaultMessage: 'Processing time', }), - render: ms => + render: (ms) => i18n.translate('xpack.ml.jobsList.jobDetails.forecastsTable.msTimeUnitLabel', { defaultMessage: '{ms} ms', values: { @@ -260,7 +260,7 @@ export class ForecastsTable extends Component { name: i18n.translate('xpack.ml.jobsList.jobDetails.forecastsTable.expiresLabel', { defaultMessage: 'Expires', }), - render: date => formatDate(date, TIME_FORMAT), + render: (date) => formatDate(date, TIME_FORMAT), textOnly: true, sortable: true, }, @@ -270,7 +270,7 @@ export class ForecastsTable extends Component { defaultMessage: 'Messages', }), sortable: false, - render: messages => { + render: (messages) => { return (
{messages.map((message, index) => { @@ -286,7 +286,7 @@ export class ForecastsTable extends Component { defaultMessage: 'View', }), width: '60px', - render: forecast => { + render: (forecast) => { const viewForecastAriaLabel = i18n.translate( 'xpack.ml.jobsList.jobDetails.forecastsTable.viewAriaLabel', { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js index 246a476517ace..9194f7537cf3d 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js @@ -84,9 +84,9 @@ export function formatValues([key, value]) { export function filterObjects(obj, allowArrays, allowObjects) { return Object.keys(obj) .filter( - k => allowObjects || typeof obj[k] !== 'object' || (allowArrays && Array.isArray(obj[k])) + (k) => allowObjects || typeof obj[k] !== 'object' || (allowArrays && Array.isArray(obj[k])) ) - .map(k => { + .map((k) => { let item = obj[k]; if (Array.isArray(item)) { item = item.join(', '); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 0375997b86bb8..56da4f1e0ff84 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -125,7 +125,7 @@ export class JobDetails extends Component { }, ]; - if (showFullDetails) { + if (showFullDetails && datafeed.items.length) { // Datafeed should be at index 2 in tabs array for full details tabs.splice(2, 0, { id: 'datafeed', diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js index e7df2c089dca2..606210dd87593 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js @@ -79,14 +79,14 @@ export class JobDetailsPane extends Component {
{sections - .filter(s => s.position === 'left') + .filter((s) => s.position === 'left') .map((s, i) => (
))}
{sections - .filter(s => s.position === 'right') + .filter((s) => s.position === 'right') .map((s, i) => (
))} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx index fbb64db94cd56..486de90d2299c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState } from 'react'; - +import React, { FC, useCallback, useEffect, useState } from 'react'; import { ml } from '../../../../services/ml_api_service'; import { JobMessages } from '../../../../components/job_messages'; import { JobMessage } from '../../../../../../common/types/audit_message'; - interface JobMessagesPaneProps { jobId: string; } @@ -32,9 +30,18 @@ export const JobMessagesPane: FC = ({ jobId }) => { } }; + const refreshMessage = useCallback(fetchMessages, [jobId]); + useEffect(() => { fetchMessages(); }, []); - return ; + return ( + + ); }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.js index a91df3cce01f2..b274a8d572adb 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.js @@ -18,8 +18,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; function loadGroups() { return ml.jobs .groups() - .then(groups => { - return groups.map(g => ({ + .then((groups) => { + return groups.map((g) => ({ value: g.id, view: (
@@ -36,7 +36,7 @@ function loadGroups() { ), })); }) - .catch(error => { + .catch((error) => { console.log(error); return []; }); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js index 0cccca722557d..dbaa1ff59a487 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js @@ -14,7 +14,7 @@ export function JobDescription({ job }) {
{job.description}   - {job.groups.map(group => ( + {job.groups.map((group) => ( ))}
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js index 9874ac56577d3..0afaca3ec12e1 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -61,7 +61,7 @@ export class JobsList extends Component { }); }; - toggleRow = item => { + toggleRow = (item) => { this.props.toggleRow(item.id); }; @@ -76,7 +76,7 @@ export class JobsList extends Component { getPageOfJobs(index, size, sortField, sortDirection) { let list = this.state.jobsSummaryList; - list = sortBy(this.state.jobsSummaryList, item => item[sortField]); + list = sortBy(this.state.jobsSummaryList, (item) => item[sortField]); list = sortDirection === 'asc' ? list : list.reverse(); const listLength = list.length; @@ -101,7 +101,7 @@ export class JobsList extends Component { render() { const { loading, isManagementTable } = this.props; const selectionControls = { - selectable: job => job.deleting !== true, + selectable: (job) => job.deleting !== true, selectableMessage: (selectable, rowItem) => selectable === false ? i18n.translate('xpack.ml.jobsList.cannotSelectRowForJobMessage', { @@ -134,7 +134,7 @@ export class JobsList extends Component {

), - render: item => ( + render: (item) => ( this.toggleRow(item)} isDisabled={item.deleting === true} @@ -166,7 +166,7 @@ export class JobsList extends Component { truncateText: false, width: '20%', scope: 'row', - render: isManagementTable ? id => this.getJobIdLink(id) : undefined, + render: isManagementTable ? (id) => this.getJobIdLink(id) : undefined, }, { field: 'auditMessage', @@ -180,7 +180,7 @@ export class JobsList extends Component {

), - render: item => , + render: (item) => , }, { name: i18n.translate('xpack.ml.jobsList.descriptionLabel', { @@ -202,7 +202,7 @@ export class JobsList extends Component { sortable: true, truncateText: false, dataType: 'number', - render: count => toLocaleString(count), + render: (count) => toLocaleString(count), width: '10%', }, { @@ -239,7 +239,7 @@ export class JobsList extends Component { name: i18n.translate('xpack.ml.jobsList.actionsLabel', { defaultMessage: 'Actions', }), - render: item => , + render: (item) => , }, ]; @@ -344,7 +344,7 @@ export class JobsList extends Component { isExpandable={true} sorting={sorting} hasActions={true} - rowProps={item => ({ + rowProps={(item) => ({ 'data-test-subj': `mlJobListRow row-${item.id}`, })} /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 6999f4c591eac..276ff5468b1d9 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -94,7 +94,7 @@ export class JobsListView extends Component { } } - toggleRow = jobId => { + toggleRow = (jobId) => { if (this.state.itemIdToExpandedRowMap[jobId]) { const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap }; delete itemIdToExpandedRowMap[jobId]; @@ -125,7 +125,7 @@ export class JobsListView extends Component { this.setState({ itemIdToExpandedRowMap }, () => { loadFullJob(jobId) - .then(job => { + .then((job) => { const fullJobsList = { ...this.state.fullJobsList }; fullJobsList[jobId] = job; this.setState({ fullJobsList }, () => { @@ -147,7 +147,7 @@ export class JobsListView extends Component { this.setState({ itemIdToExpandedRowMap }); }); }) - .catch(error => { + .catch((error) => { console.error(error); }); }); @@ -157,32 +157,32 @@ export class JobsListView extends Component { addUpdateFunction = (id, f) => { this.updateFunctions[id] = f; }; - removeUpdateFunction = id => { + removeUpdateFunction = (id) => { delete this.updateFunctions[id]; }; - setShowEditJobFlyoutFunction = func => { + setShowEditJobFlyoutFunction = (func) => { this.showEditJobFlyout = func; }; unsetShowEditJobFlyoutFunction = () => { this.showEditJobFlyout = () => {}; }; - setShowDeleteJobModalFunction = func => { + setShowDeleteJobModalFunction = (func) => { this.showDeleteJobModal = func; }; unsetShowDeleteJobModalFunction = () => { this.showDeleteJobModal = () => {}; }; - setShowStartDatafeedModalFunction = func => { + setShowStartDatafeedModalFunction = (func) => { this.showStartDatafeedModal = func; }; unsetShowStartDatafeedModalFunction = () => { this.showStartDatafeedModal = () => {}; }; - setShowCreateWatchFlyoutFunction = func => { + setShowCreateWatchFlyoutFunction = (func) => { this.showCreateWatchFlyout = func; }; unsetShowCreateWatchFlyoutFunction = () => { @@ -192,24 +192,24 @@ export class JobsListView extends Component { return this.showCreateWatchFlyout; }; - selectJobChange = selectedJobs => { + selectJobChange = (selectedJobs) => { this.setState({ selectedJobs }); }; refreshSelectedJobs() { - const selectedJobsIds = this.state.selectedJobs.map(j => j.id); - const filteredJobIds = this.state.filteredJobsSummaryList.map(j => j.id); + const selectedJobsIds = this.state.selectedJobs.map((j) => j.id); + const filteredJobIds = this.state.filteredJobsSummaryList.map((j) => j.id); // refresh the jobs stored as selected // only select those which are also in the filtered list const selectedJobs = this.state.jobsSummaryList - .filter(j => selectedJobsIds.find(id => id === j.id)) - .filter(j => filteredJobIds.find(id => id === j.id)); + .filter((j) => selectedJobsIds.find((id) => id === j.id)) + .filter((j) => filteredJobIds.find((id) => id === j.id)); this.setState({ selectedJobs }); } - setFilters = filterClauses => { + setFilters = (filterClauses) => { const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses); this.setState({ filteredJobsSummaryList, filterClauses }, () => { this.refreshSelectedJobs(); @@ -235,7 +235,7 @@ export class JobsListView extends Component { try { const jobs = await ml.jobs.jobsSummary(expandedJobsIds); const fullJobsList = {}; - const jobsSummaryList = jobs.map(job => { + const jobsSummaryList = jobs.map((job) => { if (job.fullJob !== undefined) { fullJobsList[job.id] = job.fullJob; delete job.fullJob; @@ -251,18 +251,18 @@ export class JobsListView extends Component { } ); - Object.keys(this.updateFunctions).forEach(j => { + Object.keys(this.updateFunctions).forEach((j) => { this.updateFunctions[j].setState({ job: fullJobsList[j] }); }); - jobs.forEach(job => { + jobs.forEach((job) => { if (job.deleting && this.state.itemIdToExpandedRowMap[job.id]) { this.toggleRow(job.id); } }); this.isDoneRefreshing(); - if (jobsSummaryList.some(j => j.deleting === true)) { + if (jobsSummaryList.some((j) => j.deleting === true)) { // if there are some jobs in a deleting state, start polling for // deleting jobs so we can update the jobs list once the // deleting tasks are over @@ -351,7 +351,7 @@ export class JobsListView extends Component { renderJobsListComponents() { const { isRefreshing, loading, jobsSummaryList } = this.state; - const jobIds = jobsSummaryList.map(j => j.id); + const jobIds = jobsSummaryList.map((j) => j.id); return ( diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js index 3c791ff658978..63c29e9df328f 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js @@ -65,7 +65,7 @@ function createJobStats(jobsSummaryList) { const mlNodes = {}; let failedJobs = 0; - jobsSummaryList.forEach(job => { + jobsSummaryList.forEach((job) => { if (job.jobState === JOB_STATE.OPENED) { jobStats.open.value++; } else if (job.jobState === JOB_STATE.CLOSED) { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js index 2e530a66cd83d..a011f21fddfef 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js @@ -29,7 +29,7 @@ class MultiJobActionsMenuUI extends Component { } onButtonClick = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isOpen: !prevState.isOpen, })); }; @@ -41,7 +41,7 @@ class MultiJobActionsMenuUI extends Component { }; render() { - const anyJobsDeleting = this.props.jobs.some(j => j.deleting); + const anyJobsDeleting = this.props.jobs.some((j) => j.deleting); const button = ( { + selectGroup = (group) => { this.props.selectGroup(group); }; @@ -91,11 +91,11 @@ export class GroupList extends Component { {groups.map((g, index) => (
this.handleKeyDown(event, g, index)} + onKeyDown={(event) => this.handleKeyDown(event, g, index)} key={g.id} className="group-item" onClick={() => this.selectGroup(g)} - ref={ref => this.setRef(ref, index)} + ref={(ref) => this.setRef(ref, index)} > diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js index e7b6e3a771a85..7792893305326 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js @@ -30,10 +30,10 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; function createSelectedGroups(jobs, groups) { - const jobIds = jobs.map(j => j.id); + const jobIds = jobs.map((j) => j.id); const groupCounts = {}; - jobs.forEach(j => { - j.groups.forEach(g => { + jobs.forEach((j) => { + j.groups.forEach((g) => { if (groupCounts[g] === undefined) { groupCounts[g] = 0; } @@ -42,7 +42,7 @@ function createSelectedGroups(jobs, groups) { }); const selectedGroups = groups.reduce((p, c) => { - if (c.jobIds.some(j => jobIds.includes(j))) { + if (c.jobIds.some((j) => jobIds.includes(j))) { p[c.id] = { partial: groupCounts[c.id] !== jobIds.length, }; @@ -89,7 +89,7 @@ export class GroupSelector extends Component { } else { ml.jobs .groups() - .then(groups => { + .then((groups) => { const selectedGroups = createSelectedGroups(this.props.jobs, groups); this.setState({ @@ -99,7 +99,7 @@ export class GroupSelector extends Component { groups, }); }) - .catch(error => { + .catch((error) => { console.error(error); }); } @@ -112,7 +112,7 @@ export class GroupSelector extends Component { }); }; - selectGroup = group => { + selectGroup = (group) => { const newSelectedGroups = cloneDeep(this.state.selectedGroups); if (newSelectedGroups[group.id] === undefined) { @@ -134,7 +134,7 @@ export class GroupSelector extends Component { applyChanges = () => { const { selectedGroups } = this.state; const { jobs } = this.props; - const newJobs = jobs.map(j => ({ + const newJobs = jobs.map((j) => ({ id: j.id, oldGroups: j.groups, newGroups: [], @@ -143,7 +143,7 @@ export class GroupSelector extends Component { for (const gId in selectedGroups) { if (selectedGroups.hasOwnProperty(gId)) { const group = selectedGroups[gId]; - newJobs.forEach(j => { + newJobs.forEach((j) => { if (group.partial === false || (group.partial === true && j.oldGroups.includes(gId))) { j.newGroups.push(gId); } @@ -151,10 +151,10 @@ export class GroupSelector extends Component { } } - const tempJobs = newJobs.map(j => ({ job_id: j.id, groups: j.newGroups })); + const tempJobs = newJobs.map((j) => ({ job_id: j.id, groups: j.newGroups })); ml.jobs .updateGroups(tempJobs) - .then(resp => { + .then((resp) => { let success = true; for (const jobId in resp) { // check success of each job update @@ -174,13 +174,13 @@ export class GroupSelector extends Component { console.error(resp); } }) - .catch(error => { + .catch((error) => { mlMessageBarService.notify.error(error); console.error(error); }); }; - addNewGroup = id => { + addNewGroup = (id) => { const newGroup = { id, calendarIds: [], @@ -188,7 +188,7 @@ export class GroupSelector extends Component { }; const groups = this.state.groups; - if (groups.some(g => g.id === newGroup.id) === false) { + if (groups.some((g) => g.id === newGroup.id) === false) { groups.push(newGroup); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js index f92f9c2fa4a3d..6a97d32f8cf0c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js @@ -35,7 +35,7 @@ export class NewGroupInput extends Component { }; } - changeTempNewGroup = e => { + changeTempNewGroup = (e) => { const tempNewGroupName = e.target.value; let groupsValidationError = ''; @@ -59,7 +59,7 @@ export class NewGroupInput extends Component { }); }; - newGroupKeyPress = e => { + newGroupKeyPress = (e) => { if ( e.keyCode === keyCodes.ENTER && this.state.groupsValidationError === '' && diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js index 3ea25fb2f44eb..9ce15fb881bd8 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js @@ -62,15 +62,15 @@ export class StartDatafeedModal extends Component { } } - setStartTime = time => { + setStartTime = (time) => { this.setState({ startTime: time }); }; - setEndTime = time => { + setEndTime = (time) => { this.setState({ endTime: time }); }; - setCreateWatch = e => { + setCreateWatch = (e) => { this.setState({ createWatch: e.target.checked }); }; @@ -78,7 +78,7 @@ export class StartDatafeedModal extends Component { this.setState({ isModalVisible: false }); }; - setTimeRangeValid = timeRangeValid => { + setTimeRangeValid = (timeRangeValid) => { this.setState({ timeRangeValid }); }; @@ -130,7 +130,7 @@ export class StartDatafeedModal extends Component { now, timeRangeValid, } = this.state; - const startableJobs = jobs !== undefined ? jobs.filter(j => j.hasDatafeed) : []; + const startableJobs = jobs !== undefined ? jobs.filter((j) => j.hasDatafeed) : []; // disable start button if the start and end times are the same const startDisabled = timeRangeValid === false || (startTime !== undefined && startTime === endTime); @@ -222,6 +222,6 @@ StartDatafeedModal.propTypes = { }; function getLowestLatestTime(jobs) { - const times = jobs.map(j => j.latestTimestampSortValue); + const times = jobs.map((j) => j.latestTimestampSortValue); return moment(Math.min(...times)); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js index d30ec83acdede..55c87bbc90b10 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js @@ -28,7 +28,7 @@ export class TimeRangeSelector extends Component { this.now = this.props.now; } - setStartTab = tab => { + setStartTab = (tab) => { this.setState({ startTab: tab }); switch (tab) { case 0: @@ -42,7 +42,7 @@ export class TimeRangeSelector extends Component { } }; - setEndTab = tab => { + setEndTab = (tab) => { this.setState({ endTab: tab }); switch (tab) { case 0: @@ -56,11 +56,11 @@ export class TimeRangeSelector extends Component { } }; - setStartTime = time => { + setStartTime = (time) => { this.props.setStartTime(time); }; - setEndTime = time => { + setEndTime = (time) => { this.props.setEndTime(time); }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 4f77004b91f99..15c54fc5b3a46 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -19,45 +19,45 @@ import { mlCalendarService } from '../../../services/calendar_service'; export function loadFullJob(jobId) { return new Promise((resolve, reject) => { ml.jobs - .jobs(jobId) - .then(jobs => { + .jobs([jobId]) + .then((jobs) => { if (jobs.length) { resolve(jobs[0]); } else { throw new Error(`Could not find job ${jobId}`); } }) - .catch(error => { + .catch((error) => { reject(error); }); }); } export function isStartable(jobs) { - return jobs.some(j => j.datafeedState === DATAFEED_STATE.STOPPED); + return jobs.some((j) => j.datafeedState === DATAFEED_STATE.STOPPED); } export function isStoppable(jobs) { return jobs.some( - j => j.datafeedState === DATAFEED_STATE.STARTED || j.datafeedState === DATAFEED_STATE.STARTING + (j) => j.datafeedState === DATAFEED_STATE.STARTED || j.datafeedState === DATAFEED_STATE.STARTING ); } export function isClosable(jobs) { return jobs.some( - j => j.datafeedState === DATAFEED_STATE.STOPPED && j.jobState !== JOB_STATE.CLOSED + (j) => j.datafeedState === DATAFEED_STATE.STOPPED && j.jobState !== JOB_STATE.CLOSED ); } export function forceStartDatafeeds(jobs, start, end, finish = () => {}) { - const datafeedIds = jobs.filter(j => j.hasDatafeed).map(j => j.datafeedId); + const datafeedIds = jobs.filter((j) => j.hasDatafeed).map((j) => j.datafeedId); mlJobService .forceStartDatafeeds(datafeedIds, start, end) - .then(resp => { + .then((resp) => { showResults(resp, DATAFEED_STATE.STARTED); finish(); }) - .catch(error => { + .catch((error) => { mlMessageBarService.notify.error(error); const toastNotifications = getToastNotifications(); toastNotifications.addDanger( @@ -71,14 +71,14 @@ export function forceStartDatafeeds(jobs, start, end, finish = () => {}) { } export function stopDatafeeds(jobs, finish = () => {}) { - const datafeedIds = jobs.filter(j => j.hasDatafeed).map(j => j.datafeedId); + const datafeedIds = jobs.filter((j) => j.hasDatafeed).map((j) => j.datafeedId); mlJobService .stopDatafeeds(datafeedIds) - .then(resp => { + .then((resp) => { showResults(resp, DATAFEED_STATE.STOPPED); finish(); }) - .catch(error => { + .catch((error) => { mlMessageBarService.notify.error(error); const toastNotifications = getToastNotifications(); toastNotifications.addDanger( @@ -156,7 +156,7 @@ function showResults(resp, action) { ); if (failures.length > 0) { - failures.forEach(f => { + failures.forEach((f) => { mlMessageBarService.notify.error(f.result.error); toastNotifications.addDanger( i18n.translate('xpack.ml.jobsList.actionFailedNotificationMessage', { @@ -228,14 +228,14 @@ export async function cloneJob(jobId) { } export function closeJobs(jobs, finish = () => {}) { - const jobIds = jobs.map(j => j.id); + const jobIds = jobs.map((j) => j.id); mlJobService .closeJobs(jobIds) - .then(resp => { + .then((resp) => { showResults(resp, JOB_STATE.CLOSED); finish(); }) - .catch(error => { + .catch((error) => { mlMessageBarService.notify.error(error); const toastNotifications = getToastNotifications(); toastNotifications.addDanger( @@ -249,14 +249,14 @@ export function closeJobs(jobs, finish = () => {}) { } export function deleteJobs(jobs, finish = () => {}) { - const jobIds = jobs.map(j => j.id); + const jobIds = jobs.map((j) => j.id); mlJobService .deleteJobs(jobIds) - .then(resp => { + .then((resp) => { showResults(resp, JOB_STATE.DELETED); finish(); }) - .catch(error => { + .catch((error) => { mlMessageBarService.notify.error(error); const toastNotifications = getToastNotifications(); toastNotifications.addDanger( @@ -284,7 +284,7 @@ export function filterJobs(jobs, clauses) { return p; }, {}); - clauses.forEach(c => { + clauses.forEach((c) => { // the search term could be negated with a minus, e.g. -bananas const bool = c.match === 'must'; let js = []; @@ -295,14 +295,14 @@ export function filterJobs(jobs, clauses) { // if the term has been negated, AND the matches if (bool === true) { js = jobs.filter( - job => + (job) => stringMatch(job.id, c.value) === bool || stringMatch(job.description, c.value) === bool || stringMatch(job.memory_status, c.value) === bool ); } else { js = jobs.filter( - job => + (job) => stringMatch(job.id, c.value) === bool && stringMatch(job.description, c.value) === bool && stringMatch(job.memory_status, c.value) === bool @@ -312,18 +312,18 @@ export function filterJobs(jobs, clauses) { // filter other clauses, i.e. the toggle group buttons if (Array.isArray(c.value)) { // the groups value is an array of group ids - js = jobs.filter(job => jobProperty(job, c.field).some(g => c.value.indexOf(g) >= 0)); + js = jobs.filter((job) => jobProperty(job, c.field).some((g) => c.value.indexOf(g) >= 0)); } else { - js = jobs.filter(job => jobProperty(job, c.field) === c.value); + js = jobs.filter((job) => jobProperty(job, c.field) === c.value); } } - js.forEach(j => matches[j.id].count++); + js.forEach((j) => matches[j.id].count++); }); // loop through the matches and return only those jobs which have match all the clauses const filteredJobs = []; - each(matches, m => { + each(matches, (m) => { if (m.count >= clauses.length) { filteredJobs.push(m.job); } @@ -369,7 +369,7 @@ function jobProperty(job, prop) { function getUrlVars(url) { const vars = {}; - url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(_, key, value) { + url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (_, key, value) { vars[key] = value; }); return vars; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx index 103db5707eed5..d738c8aa3d615 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx @@ -18,7 +18,7 @@ interface JobsPageProps { lastRefresh?: number; } -export const JobsPage: FC = props => { +export const JobsPage: FC = (props) => { return (
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/components/job_groups_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/common/components/job_groups_input.tsx index 131e313e7c9e5..06317375d8bc6 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/components/job_groups_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/components/job_groups_input.tsx @@ -20,18 +20,18 @@ export interface JobGroupsInputProps { export const JobGroupsInput: FC = memo( ({ existingGroups, selectedGroups, onChange, validation }) => { - const options = existingGroups.map(g => ({ + const options = existingGroups.map((g) => ({ label: g, color: tabColor(g), })); - const selectedOptions = selectedGroups.map(g => ({ + const selectedOptions = selectedGroups.map((g) => ({ label: g, color: tabColor(g), })); function onChangeCallback(optionsIn: EuiComboBoxOptionOption[]) { - onChange(optionsIn.map(g => g.label)); + onChange(optionsIn.map((g) => g.label)); } function onCreateGroup(input: string, flattenedOptions: EuiComboBoxOptionOption[]) { @@ -48,7 +48,7 @@ export const JobGroupsInput: FC = memo( if ( flattenedOptions.findIndex( - option => option.label.trim().toLowerCase() === normalizedSearchValue + (option) => option.label.trim().toLowerCase() === normalizedSearchValue ) === -1 ) { options.push(newGroup); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts index 9fa0eb901c61f..ad7197cc2478f 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts @@ -193,7 +193,7 @@ export class AdvancedJobCreator extends JobCreator { const detectors = getRichDetectors(job, datafeed, this.additionalFields, true); // keep track of the custom rules for each detector - const customRules = this._detectors.map(d => d.custom_rules); + const customRules = this._detectors.map((d) => d.custom_rules); this.removeAllDetectors(); this._richDetectors.length = 0; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index ca982304bd4f3..89a0c45828737 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -617,7 +617,7 @@ export class JobCreator { } if (this._job_config.analysis_config.influencers !== undefined) { - this._job_config.analysis_config.influencers.forEach(i => this.addInfluencer(i)); + this._job_config.analysis_config.influencers.forEach((i) => this.addInfluencer(i)); } if ( @@ -630,7 +630,7 @@ export class JobCreator { this._scriptFields = []; if (this._datafeed_config.script_fields !== undefined) { - this._scriptFields = Object.keys(this._datafeed_config.script_fields).map(f => ({ + this._scriptFields = Object.keys(this._datafeed_config.script_fields).map((f) => ({ id: f, name: f, type: ES_FIELD_TYPES.KEYWORD, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts index 10d306c37d114..95141f31cdea6 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts @@ -49,7 +49,7 @@ export class MultiMetricJobCreator extends JobCreator { } public removeSplitField() { - this._detectors.forEach(d => { + this._detectors.forEach((d) => { delete d.partition_field_name; }); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts index 276f16c9e76b7..de6cc855e4963 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts @@ -78,7 +78,7 @@ export class PopulationJobCreator extends JobCreator { // remove over field from all detectors public removeSplitField() { - this._detectors.forEach(d => { + this._detectors.forEach((d) => { delete d.over_field_name; }); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index 6d061c2df9ad9..92e65e580fc01 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -35,7 +35,7 @@ const getFieldByIdFactory = (additionalFields: Field[]) => (id: string) => { if (id === MLCATEGORY) { field = mlCategory; } else if (additionalFields.length) { - field = additionalFields.find(f => f.id === id) || null; + field = additionalFields.find((f) => f.id === id) || null; } } return field; @@ -52,7 +52,7 @@ export function getRichDetectors( const getFieldById = getFieldByIdFactory(additionalFields); - return detectors.map(d => { + return detectors.map((d) => { let field = null; let byField = null; let overField = null; @@ -86,13 +86,13 @@ export function getRichDetectors( export function createFieldOptions(fields: Field[], additionalFields: Field[]) { return [ ...fields - .filter(f => f.id !== EVENT_RATE_FIELD_ID) - .map(f => ({ + .filter((f) => f.id !== EVENT_RATE_FIELD_ID) + .map((f) => ({ label: f.name, })), ...additionalFields - .filter(f => fields.some(f2 => f2.id === f.id) === false) - .map(f => ({ + .filter((f) => fields.some((f2) => f2.id === f.id) === false) + .map((f) => ({ label: f.id, })), ].sort((a, b) => a.label.localeCompare(b.label)); @@ -150,7 +150,7 @@ function getDetectors(job: Job, datafeed: Datafeed) { } else { // all other detectors. detectors = processFieldlessAggs(detectors); - detectors = detectors.map(d => { + detectors = detectors.map((d) => { switch (d.function) { // if sparse data functions were used, replace them with their non-sparse versions // the sparse data flag has already been determined and set, so this information is not being lost. @@ -182,7 +182,7 @@ function getDetectors(job: Job, datafeed: Datafeed) { // if a fieldless function is used, add EVENT_RATE_FIELD_ID as its field function processFieldlessAggs(detectors: Detector[]) { - return detectors.map(d => { + return detectors.map((d) => { switch (d.function) { case ML_JOB_AGGREGATION.COUNT: case ML_JOB_AGGREGATION.HIGH_COUNT: @@ -287,7 +287,7 @@ export function advancedStartDatafeed(jobCreator: JobCreatorType) { } export function aggFieldPairsCanBeCharted(afs: AggFieldPair[]) { - return afs.some(a => a.agg.dslName === null) === false; + return afs.some((a) => a.agg.dslName === null) === false; } export function getJobCreatorTitle(jobCreator: JobCreatorType) { @@ -324,7 +324,7 @@ export function collectAggs(o: any, aggFields: Field[]) { for (const i in o) { if (o[i] !== null && typeof o[i] === 'object') { if (i === 'aggregations' || i === 'aggs') { - Object.keys(o[i]).forEach(k => { + Object.keys(o[i]).forEach((k) => { if (k !== 'aggregations' && k !== 'aggs') { aggFields.push({ id: k, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts index eb563e8b36107..0011c88d2b524 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts @@ -45,7 +45,7 @@ export const modelMemoryEstimatorProvider = ( get updates$(): Observable { return combineLatest([ jobCreator.wizardInitialized$.pipe( - skipWhile(wizardInitialized => wizardInitialized === false) + skipWhile((wizardInitialized) => wizardInitialized === false) ), modelMemoryCheck$, ]).pipe( @@ -58,10 +58,10 @@ export const modelMemoryEstimatorProvider = ( distinctUntilChanged(isEqual), // don't call the endpoint with invalid payload filter(() => jobValidator.isModelMemoryEstimationPayloadValid), - switchMap(payload => { + switchMap((payload) => { return ml.calculateModelMemoryLimit$(payload).pipe( pluck('modelMemoryLimit'), - catchError(error => { + catchError((error) => { // eslint-disable-next-line no-console console.error('Model memory limit could not be calculated', error.body); error$.next(error.body); @@ -115,7 +115,7 @@ export const useModelMemoryEstimator = ( ); subscription.add( - modelMemoryEstimator.error$.subscribe(error => { + modelMemoryEstimator.error$.subscribe((error) => { notifications.toasts.addWarning({ title: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryError', { defaultMessage: 'Model memory limit could not be calculated', diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts index 2571fe70f4a83..9afc6e5bfca93 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts @@ -81,7 +81,7 @@ export class JobRunner { // link the _subscribers list from the JobCreator // to the progress BehaviorSubject. const subscriptions = - pollProgress === true ? this._subscribers.map(s => this._progress$.subscribe(s)) : []; + pollProgress === true ? this._subscribers.map((s) => this._progress$.subscribe(s)) : []; await this.openJob(); const { started } = await mlJobService.startDatafeed( @@ -118,7 +118,7 @@ export class JobRunner { // than the end date supplied to the datafeed this._progress$.next(100); // unsubscribe everyone - subscriptions.forEach(s => s.unsubscribe()); + subscriptions.forEach((s) => s.unsubscribe()); } }; // wait for the first check to run and then return success. diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts index a942603d7f9d4..242a643ddd3ce 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts @@ -103,7 +103,7 @@ export class JobValidator { this._asyncValidators$ = [cardinalityValidator(this._jobCreatorSubject$)]; this._asyncValidatorsResult$ = combineLatest(this._asyncValidators$).pipe( - map(res => { + map((res) => { return res.reduce((acc, curr) => { return { ...acc, @@ -124,7 +124,7 @@ export class JobValidator { ...asyncValidatorsResult, }; }), - tap(latestValidationResult => { + tap((latestValidationResult) => { this.latestValidationResult = latestValidationResult; }) ); @@ -168,7 +168,7 @@ export class JobValidator { private _resetBasicValidations() { this._validationSummary.basic = true; - Object.values(this._basicValidations).forEach(v => { + Object.values(this._basicValidations).forEach((v) => { v.valid = true; delete v.message; }); @@ -214,7 +214,7 @@ export class JobValidator { } private _isOverallBasicValid() { - return Object.values(this._basicValidations).some(v => v.valid === false) === false; + return Object.values(this._basicValidations).some((v) => v.valid === false) === false; } public get validationSummary(): ValidationSummary { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts index 2ab58714830e3..d5cc1cf535a78 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts @@ -189,16 +189,16 @@ export function checkForExistingJobAndGroupIds( } // check that groups that have been newly added in this job do not already exist as job ids - const newGroups = groupIds.filter(g => !existingJobsAndGroups.groupIds.includes(g)); - if (existingJobsAndGroups.jobIds.some(g => newGroups.includes(g))) { + const newGroups = groupIds.filter((g) => !existingJobsAndGroups.groupIds.includes(g)); + if (existingJobsAndGroups.jobIds.some((g) => newGroups.includes(g))) { messages.push({ id: 'job_group_id_already_exists' }); } return { messages, valid: messages.length === 0, - contains: (id: string) => messages.some(m => id === m.id), - find: (id: string) => messages.find(m => id === m.id), + contains: (id: string) => messages.some((m) => id === m.id), + find: (id: string) => messages.find((m) => id === m.id), }; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/validators.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/validators.ts index ea7ba21699f60..eabf5588579c5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/validators.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/validators.ts @@ -41,10 +41,10 @@ export function cardinalityValidator( ): Observable { return jobCreator$.pipe( // Perform a cardinality check only with enabled model plot. - filter(jobCreator => { + filter((jobCreator) => { return jobCreator?.modelPlot; }), - map(jobCreator => { + map((jobCreator) => { return { jobCreator, analysisConfigString: JSON.stringify(jobCreator.jobConfig.analysis_config), @@ -60,7 +60,7 @@ export function cardinalityValidator( datafeed_config: jobCreator.datafeedConfig, } as CombinedJob); }), - map(validationResults => { + map((validationResults) => { for (const validationResult of validationResults) { if (isCardinalityModelPlotHigh(validationResult)) { return { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts index 5048f44586a38..110b031cd1dc0 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts @@ -236,7 +236,7 @@ export class ResultsLoader { const anomalies: Record = {}; Object.entries(resp.results).forEach(([dtrIdx, results]) => { anomalies[+dtrIdx] = results.map( - r => ({ ...r, severity: getSeverityType(r.value as number) } as Anomaly) + (r) => ({ ...r, severity: getSeverityType(r.value as number) } as Anomaly) ); }); return anomalies; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/anomalies.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/anomalies.tsx index 1596177daaf03..ab2098ca27135 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/anomalies.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/anomalies.tsx @@ -48,7 +48,7 @@ function splitAnomalySeverities(anomalies: Anomaly[]) { unknown: [], low: [], }; - anomalies.forEach(a => { + anomalies.forEach((a) => { if (a.value !== 0) { severities[a.severity].push({ dataValue: a.time }); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx index 54fb19d868cdc..3909a7a31fb2f 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx @@ -44,7 +44,7 @@ export const ModelMemoryLimitInput: FC = () => { setModelMemoryLimit(e.target.value)} + onChange={(e) => setModelMemoryLimit(e.target.value)} isInvalid={validation.valid === false} data-test-subj="mlJobWizardInputModelMemoryLimit" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx index 6328366626894..7a75a17a870c9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx @@ -50,7 +50,7 @@ export const FrequencyInput: FC = () => { setFrequency(e.target.value)} + onChange={(e) => setFrequency(e.target.value)} isInvalid={validation.valid === false} data-test-subj="mlJobWizardInputFrequency" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx index 0e6dd81fb91a9..22573fb215cea 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx @@ -36,7 +36,7 @@ export const QueryDelayInput: FC = () => { setQueryDelay(e.target.value)} + onChange={(e) => setQueryDelay(e.target.value)} isInvalid={validation.valid === false} data-test-subj="mlJobWizardInputQueryDelay" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx index ea03d16fcccca..508811731ae3e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx @@ -41,7 +41,7 @@ export const ScrollSizeInput: FC = () => { min={0} placeholder={scrollSizeDefault} value={scrollSizeString === '' ? scrollSizeString : +scrollSizeString} - onChange={e => setScrollSize(e.target.value)} + onChange={(e) => setScrollSize(e.target.value)} isInvalid={validation.valid === false} data-test-subj="mlJobWizardInputScrollSize" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx index 597fe42543301..60b034b516939 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx @@ -37,10 +37,10 @@ export const CalendarsSelection: FC = () => { async function loadCalendars() { setIsLoading(true); const calendars = (await ml.calendars()).filter( - c => c.job_ids.includes(GLOBAL_CALENDAR) === false + (c) => c.job_ids.includes(GLOBAL_CALENDAR) === false ); - setOptions(calendars.map(c => ({ label: c.calendar_id, value: c }))); - setSelectedOptions(selectedCalendars.map(c => ({ label: c.calendar_id, value: c }))); + setOptions(calendars.map((c) => ({ label: c.calendar_id, value: c }))); + setSelectedOptions(selectedCalendars.map((c) => ({ label: c.calendar_id, value: c }))); setIsLoading(false); } @@ -58,9 +58,9 @@ export const CalendarsSelection: FC = () => { options, selectedOptions, isLoading, - onChange: optionsIn => { + onChange: (optionsIn) => { setSelectedOptions(optionsIn); - setSelectedCalendars(optionsIn.map(o => o.value!)); + setSelectedCalendars(optionsIn.map((o) => o.value!)); }, }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx index a034bdcc2900f..b63f3004efdc5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx @@ -29,7 +29,7 @@ export const ModelPlotSwitch: FC = () => { // and a rare detector is being used. const isRareCategoryJob = isCategorizationJobCreator(jobCreator) && - jobCreator.aggregations.some(agg => aggs.includes(agg.id)); + jobCreator.aggregations.some((agg) => aggs.includes(agg.id)); setEnabled(isRareCategoryJob === false); }, [jobCreatorUpdated]); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx index 841ccfdce0958..a693127e07f48 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx @@ -35,7 +35,7 @@ export const GroupsInput: FC = () => { })); function onChange(optionsIn: EuiComboBoxOptionOption[]) { - setSelectedGroups(optionsIn.map(g => g.label)); + setSelectedGroups(optionsIn.map((g) => g.label)); } function onCreateGroup(input: string, flattenedOptions: EuiComboBoxOptionOption[]) { @@ -52,13 +52,13 @@ export const GroupsInput: FC = () => { if ( flattenedOptions.findIndex( - option => option.label.trim().toLowerCase() === normalizedSearchValue + (option) => option.label.trim().toLowerCase() === normalizedSearchValue ) === -1 ) { options.push(newGroup); } - setSelectedGroups([...selectedOptions, newGroup].map(g => g.label)); + setSelectedGroups([...selectedOptions, newGroup].map((g) => g.label)); } useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_description/job_description_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_description/job_description_input.tsx index 1c027b6c81ff9..bfddae0e79ea1 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_description/job_description_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_description/job_description_input.tsx @@ -22,7 +22,7 @@ export const JobDescriptionInput: FC = () => { setJobDescription(e.target.value)} + onChange={(e) => setJobDescription(e.target.value)} data-test-subj="mlJobWizardInputJobDescription" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx index c77024a4d329f..43468d83844ed 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx @@ -33,7 +33,7 @@ export const JobIdInput: FC = () => { setJobId(e.target.value)} + onChange={(e) => setJobId(e.target.value)} isInvalid={validation.valid === false} data-test-subj="mlJobWizardInputJobId" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx index 9e784a20c4f5f..7d71583977204 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx @@ -91,7 +91,7 @@ export const AdvancedDetectorModal: FC = ({ // list of aggregation combobox options. const aggOptions: EuiComboBoxOptionOption[] = aggs - .filter(agg => filterAggs(agg, usingScriptFields)) + .filter((agg) => filterAggs(agg, usingScriptFields)) .map(createAggOption); // fields available for the selected agg @@ -110,7 +110,7 @@ export const AdvancedDetectorModal: FC = ({ ...createMlcategoryFieldOption(jobCreator.categorizationFieldName), ].sort(comboBoxOptionsSort); - const eventRateField = fields.find(f => f.id === EVENT_RATE_FIELD_ID); + const eventRateField = fields.find((f) => f.id === EVENT_RATE_FIELD_ID); const onOptionChange = (func: (p: EuiComboBoxOptionOption) => any) => ( selectedOptions: EuiComboBoxOptionOption[] @@ -119,15 +119,15 @@ export const AdvancedDetectorModal: FC = ({ }; function getAgg(title: string) { - return aggs.find(a => a.id === title) || null; + return aggs.find((a) => a.id === title) || null; } function getField(title: string) { if (title === mlCategory.id) { return mlCategory; } return ( - fields.find(f => f.id === title) || - jobCreator.additionalFields.find(f => f.id === title) || + fields.find((f) => f.id === title) || + jobCreator.additionalFields.find((f) => f.id === title) || null ); } @@ -301,7 +301,7 @@ export const AdvancedDetectorModal: FC = ({ fullWidth={true} placeholder={descriptionPlaceholder} value={descriptionOption} - onChange={e => setDescriptionOption(e.target.value)} + onChange={(e) => setDescriptionOption(e.target.value)} data-test-subj="mlAdvancedDetectorDescriptionInput" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx index e4eccb5f01423..7f108da217035 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx @@ -42,20 +42,20 @@ export const AggSelect: FC = ({ fields, changeHandler, selectedOptions, r // so they can be removed from the dropdown list const removeLabels = removeOptions.map(createLabel); - const options: EuiComboBoxOptionOption[] = fields.map(f => { + const options: EuiComboBoxOptionOption[] = fields.map((f) => { const aggOption: DropDownOption = { label: f.name, options: [] }; if (typeof f.aggs !== 'undefined') { aggOption.options = f.aggs - .filter(a => a.dslName !== null) // don't include aggs which have no ES equivalent + .filter((a) => a.dslName !== null) // don't include aggs which have no ES equivalent .map( - a => + (a) => ({ label: `${a.title}(${f.name})`, agg: a, field: f, } as DropDownLabel) ) - .filter(o => removeLabels.includes(o.label) === false); + .filter((o) => removeLabels.includes(o.label) === false); } return aggOption; }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span_input.tsx index 35c1cfa9ce65d..740a8614ee205 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span_input.tsx @@ -23,7 +23,7 @@ export const BucketSpanInput: FC = ({ bucketSpan, setBucketSpan, isInvali defaultMessage: 'Bucket span', })} value={bucketSpan} - onChange={e => setBucketSpan(e.target.value)} + onChange={(e) => setBucketSpan(e.target.value)} isInvalid={isInvalid} data-test-subj="mlJobWizardInputBucketSpan" /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/bucket_span_estimator.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/bucket_span_estimator.tsx index 72ebbb0b438a2..2be0f67f3944f 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/bucket_span_estimator.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/bucket_span_estimator.tsx @@ -35,7 +35,7 @@ export const BucketSpanEstimator: FC = ({ setEstimating }) => { function checkIsUsingMlCategory() { return ( isAdvancedJobCreator(jobCreator) && - jobCreator.detectors.some(d => { + jobCreator.detectors.some((d) => { if ( d.partition_field_name === MLCATEGORY || d.over_field_name === MLCATEGORY || diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index 5064ba9df9bee..0ec3b609b604f 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -29,12 +29,12 @@ export function useEstimateBucketSpan() { const [status, setStatus] = useState(ESTIMATE_STATUS.NOT_RUNNING); const data: BucketSpanEstimatorData = { - aggTypes: jobCreator.aggregations.map(a => a.dslName), + aggTypes: jobCreator.aggregations.map((a) => a.dslName), duration: { start: jobCreator.start, end: jobCreator.end, }, - fields: jobCreator.fields.map(f => (f.id === EVENT_RATE_FIELD_ID ? null : f.id)), + fields: jobCreator.fields.map((f) => (f.id === EVENT_RATE_FIELD_ID ? null : f.id)), index: mlContext.currentIndexPattern.title, query: mlContext.combinedQuery, splitField: undefined, @@ -47,7 +47,7 @@ export function useEstimateBucketSpan() { ) { data.splitField = jobCreator.splitField.id; } else if (isAdvancedJobCreator(jobCreator)) { - jobCreator.richDetectors.some(d => { + jobCreator.richDetectors.some((d) => { if (d.partitionField !== null) { data.splitField = d.partitionField.id; return true; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx index f139a8013725a..0ca8320eec859 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx @@ -123,7 +123,7 @@ const AllValidationChecks: FC<{ validationChecks: FieldExampleCheck[] }> = ({ validationChecks, }) => { const list: EuiListGroupItemProps[] = Object.keys(VALIDATION_CHECK_DESCRIPTION).map((k, i) => { - const failedCheck = validationChecks.find(vc => vc.id === i); + const failedCheck = validationChecks.find((vc) => vc.id === i); if ( failedCheck !== undefined && failedCheck?.valid !== CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/top_categories.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/top_categories.tsx index 227c93dc2d86b..07ae18ae2f810 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/top_categories.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/top_categories.tsx @@ -27,7 +27,7 @@ export const TopCategories: FC = () => { async function loadTopCats() { const results = await ml.jobs.topCategories(jobCreator.jobId, NUMBER_OF_CATEGORY_EXAMPLES); setTableRow( - results.categories.map(c => ({ + results.categories.map((c) => ({ count: c.count, example: c.category.examples?.length ? c.category.examples[0] : '', })) diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx index c55bdeef4dde8..f80827da03a19 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx @@ -24,7 +24,7 @@ export const Influencers: FC = () => { useEffect(() => { jobCreator.removeAllInfluencers(); - influencers.forEach(i => jobCreator.addInfluencer(i)); + influencers.forEach((i) => jobCreator.addInfluencer(i)); jobCreatorUpdate(); }, [influencers.join()]); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx index 25c924ee0b42f..f3e2e22c871e6 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx @@ -27,10 +27,10 @@ export const InfluencersSelect: FC = ({ fields, changeHandler, selectedIn ...createMlcategoryFieldOption(jobCreator.categorizationFieldName), ]; - const selection: EuiComboBoxOptionOption[] = selectedInfluencers.map(i => ({ label: i })); + const selection: EuiComboBoxOptionOption[] = selectedInfluencers.map((i) => ({ label: i })); function onChange(selectedOptions: EuiComboBoxOptionOption[]) { - changeHandler(selectedOptions.map(o => o.label)); + changeHandler(selectedOptions.map((o) => o.label)); } return ( diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index cbd7c0ab72ffc..684cb5b4e0dda 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -76,7 +76,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { // watch for changes in detector list length useEffect(() => { jobCreator.removeAllDetectors(); - aggFieldPairList.forEach(pair => { + aggFieldPairList.forEach((pair) => { jobCreator.addDetector(pair.agg, pair.field); }); jobCreatorUpdate(); @@ -108,7 +108,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { chartLoader .loadFieldExampleValues(splitField) .then(setFieldValues) - .catch(error => { + .catch((error) => { mlMessageBarService.notify.error(error); }); } else { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx index 70a3d1c7d616c..e5f5ba48900d9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx @@ -49,7 +49,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { const [splitField, setSplitField] = useState(jobCreator.splitField); const [fieldValuesPerDetector, setFieldValuesPerDetector] = useState({}); const [byFieldsUpdated, setByFieldsUpdated] = useReducer<(s: number, action: any) => number>( - s => s + 1, + (s) => s + 1, 0 ); const [pageReady, setPageReady] = useState(false); @@ -203,7 +203,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { function allDataReady() { let ready = aggFieldPairList.length > 0; - aggFieldPairList.forEach(af => { + aggFieldPairList.forEach((af) => { if (af.by !== undefined && af.by.field !== null) { // if a by field is set, it's only ready when the value is loaded ready = ready && af.by.value !== null; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx index 4474a2d6b5413..06f7092e8ac06 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx @@ -125,7 +125,7 @@ export const PopulationDetectorsSummary: FC = () => { function allDataReady() { let ready = aggFieldPairList.length > 0; - aggFieldPairList.forEach(af => { + aggFieldPairList.forEach((af) => { if (af.by !== undefined && af.by.field !== null) { // if a by field is set, it's only ready when the value is loaded ready = ready && af.by.value !== null; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/sparse_data_switch.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/sparse_data_switch.tsx index 2884bce4d89ad..faa505fae22a1 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/sparse_data_switch.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/sparse_data_switch.tsx @@ -24,7 +24,7 @@ export const SparseDataSwitch: FC = () => { useEffect(() => { const aggs = [ES_AGGREGATION.COUNT, ES_AGGREGATION.SUM]; const isCountOrSum = jobCreator.aggregations.some( - agg => agg.dslName !== null && aggs.includes(agg.dslName) + (agg) => agg.dslName !== null && aggs.includes(agg.dslName) ); setEnabled(isCountOrSum); if (isCountOrSum === false && sparseData === true) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_cards/split_cards.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_cards/split_cards.tsx index 118923aa203e1..184a851a8d078 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_cards/split_cards.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_cards/split_cards.tsx @@ -48,7 +48,7 @@ export const SplitCards: FC = memo( if (animate === true) { setTimeout(() => { - panels.forEach(p => (p.panel.style.marginBottom = `${p.marginBottom}px`)); + panels.forEach((p) => (p.panel.style.marginBottom = `${p.marginBottom}px`)); }, 100); } @@ -68,7 +68,7 @@ export const SplitCards: FC = memo( ...(animate ? { transition: 'margin 0.5s' } : {}), }; return ( -
storePanels(ref, marginBottom)} style={style}> +
storePanels(ref, marginBottom)} style={style}> { const sf = jobCreator.splitField; if (sf !== null) { - setFields(allCategoryFields.filter(f => f.name !== sf.name)); + setFields(allCategoryFields.filter((f) => f.name !== sf.name)); } else { setFields(allCategoryFields); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field_select.tsx index 816614fb2a772..af9644f4770d3 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field_select.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field_select.tsx @@ -32,7 +32,7 @@ export const SplitFieldSelect: FC = ({ placeholder, }) => { const options: EuiComboBoxOptionOption[] = fields.map( - f => + (f) => ({ label: f.name, field: f, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx index bfb34b977ec97..2e1a187daa736 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx @@ -43,14 +43,14 @@ export const Wizard: FC = ({ firstWizardStep = WIZARD_STEPS.TIME_RANGE, }) => { const [jobCreatorUpdated, setJobCreatorUpdate] = useReducer<(s: number, action: any) => number>( - s => s + 1, + (s) => s + 1, 0 ); const jobCreatorUpdate = () => setJobCreatorUpdate(jobCreatorUpdated); const [jobValidatorUpdated, setJobValidatorUpdate] = useReducer< (s: number, action: any) => number - >(s => s + 1, 0); + >((s) => s + 1, 0); const jobCreatorContext: JobCreatorContextValue = { jobCreatorUpdated, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx index 0dd222a1726ef..433327497fe05 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx @@ -51,8 +51,8 @@ export const EditJob: FC = ({ job, jobOverride, existingGroupIds, const handleValidation = () => { const jobGroupsValidationResult = formState.jobGroups - .map(group => groupValidator(group)) - .filter(result => result !== null); + .map((group) => groupValidator(group)) + .filter((result) => result !== null); setValidationResult({ jobGroups: jobGroupsValidationResult, @@ -92,7 +92,7 @@ export const EditJob: FC = ({ job, jobOverride, existingGroupIds, { + onChange={(value) => { setFormState({ jobGroups: value, }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx index 2a15a42ba04f8..8dab25bf492f4 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx @@ -85,7 +85,7 @@ export const JobItem: FC = memo( - {jobGroups.map(group => ( + {jobGroups.map((group) => ( {group} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx index bae9c592b94c4..63dec536ea487 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx @@ -195,7 +195,7 @@ export const JobSettingsForm: FC = ({ <> { + setTimeRange={(value) => { setFormState({ timeRange: value, }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index 50c35ec426acb..da1bbbc7ae67c 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -113,7 +113,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { setSaveState(SAVE_STATE.NOT_SAVED); // mix existing groups from the server with the groups used across all jobs in the module. - const moduleGroups = [...response.jobs.map(j => j.config.groups || [])].flat(); + const moduleGroups = [...response.jobs.map((j) => j.config.groups || [])].flat(); setExistingGroups([...new Set([...existingGroups, ...moduleGroups])]); } catch (e) { // eslint-disable-next-line no-console @@ -176,7 +176,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const { datafeeds: datafeedsResponse, jobs: jobsResponse, kibana: kibanaResponse } = response; setJobs( - jobs.map(job => { + jobs.map((job) => { return { ...job, datafeedResult: datafeedsResponse.find(({ id }) => id.endsWith(job.id)), @@ -297,7 +297,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { {isFormVisible && ( { + onChange={(formValues) => { setJobPrefix(formValues.jobPrefix); }} saveState={saveState} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts index 9c60b84b16bdf..e3b0fd4cefe0c 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts @@ -66,8 +66,8 @@ export const checkForSavedObjects = async (objects: KibanaObjects): Promise { - const find = savedObjects.find(savedObject => savedObject.attributes.title === obj.title); + acc[type] = objects[type].map((obj) => { + const find = savedObjects.find((savedObject) => savedObject.attributes.title === obj.title); return { ...obj, exists: !!find, diff --git a/x-pack/plugins/ml/public/application/management/index.ts b/x-pack/plugins/ml/public/application/management/index.ts index f15cdb12afb21..480e2fe488980 100644 --- a/x-pack/plugins/ml/public/application/management/index.ts +++ b/x-pack/plugins/ml/public/application/management/index.ts @@ -28,7 +28,7 @@ export function initManagementSection( core: CoreSetup ) { const licensing = pluginsSetup.licensing.license$.pipe(take(1)); - licensing.subscribe(license => { + licensing.subscribe((license) => { const management = pluginsSetup.management; if ( management !== undefined && diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts index cfe37ce14bb78..5d1fc6f0a3c92 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts +++ b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts @@ -11,12 +11,14 @@ import { ManagementAppMountParams } from '../../../../../../../src/plugins/manag import { MlStartDependencies } from '../../../plugin'; import { JobsListPage } from './components'; import { getJobsListBreadcrumbs } from '../breadcrumbs'; +import { setDependencyCache, clearCache } from '../../util/dependency_cache'; const renderApp = (element: HTMLElement, coreStart: CoreStart) => { const I18nContext = coreStart.i18n.Context; ReactDOM.render(React.createElement(JobsListPage, { I18nContext }), element); return () => { unmountComponentAtNode(element); + clearCache(); }; }; @@ -25,6 +27,15 @@ export async function mountApp( params: ManagementAppMountParams ) { const [coreStart] = await core.getStartServices(); + + setDependencyCache({ + docLinks: coreStart.docLinks!, + basePath: coreStart.http.basePath, + http: coreStart.http, + i18n: coreStart.i18n, + }); + params.setBreadcrumbs(getJobsListBreadcrumbs()); + return renderApp(params.element, coreStart); } diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index dac39b1a2071d..03b66f5c369c1 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -43,7 +43,7 @@ const createJobLink = '#/jobs/new_job/step/index_or_search'; function getDefaultAnomalyScores(groups: Group[]): MaxScoresByGroup { const anomalyScores: MaxScoresByGroup = {}; - groups.forEach(group => { + groups.forEach((group) => { anomalyScores[group.id] = { maxScore: 0 }; }); @@ -96,7 +96,7 @@ export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => { try { const promises = groupsList - .filter(group => group.jobIds.length > 0) + .filter((group) => group.jobIds.length > 0) .map((group, i) => { scores[group.id].index = i; const latestTimestamp = group.latest_timestamp; @@ -108,7 +108,7 @@ export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => { const results = await Promise.all(promises); const tempGroups = { ...groupsObject }; // Check results for each group's promise index and update state - Object.keys(scores).forEach(groupId => { + Object.keys(scores).forEach((groupId) => { const resultsIndex = scores[groupId] && scores[groupId].index; // maxScore will be null if it was not loaded correctly const { maxScore } = resultsIndex !== undefined && results[resultsIndex]; diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts index b030a1ef45ab0..5372862219a89 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts @@ -156,7 +156,7 @@ export function getStatsBarData(jobsList: any) { } export function getJobsFromGroup(group: Group, jobs: any) { - return group.jobIds.map(jobId => jobs[jobId]).filter(id => id !== undefined); + return group.jobIds.map((jobId) => jobs[jobId]).filter((id) => id !== undefined); } export function getJobsWithTimerange(jobsList: any) { diff --git a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx index 87a7156b6f52e..119346ec8035a 100644 --- a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx @@ -42,7 +42,7 @@ export const OverviewSideBar: FC = ({ createAnomalyDetectionJobDisabled } const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const docsLink = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-ml.html`; - const transformsLink = `${basePath.get()}/app/kibana#/management/data/transform`; + const transformsLink = `${basePath.get()}/app/management/data/transform`; return ( diff --git a/x-pack/plugins/ml/public/application/routing/router.tsx b/x-pack/plugins/ml/public/application/routing/router.tsx index f4d6fec5e6ee3..281493c4e31b7 100644 --- a/x-pack/plugins/ml/public/application/routing/router.tsx +++ b/x-pack/plugins/ml/public/application/routing/router.tsx @@ -54,7 +54,7 @@ export const MlRouter: FC<{ pageDeps: PageDependencies }> = ({ pageDeps }) => { key={name} path={route.path} exact - render={props => { + render={(props) => { window.setTimeout(() => { setBreadcrumbs(route.breadcrumbs); }); diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx new file mode 100644 index 0000000000000..68af9a2a49cab --- /dev/null +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { parse } from 'query-string'; +import { MlRoute, PageLoader, PageProps } from '../../router'; +import { useResolver } from '../../use_resolver'; +import { basicResolvers } from '../../resolvers'; +import { Page } from '../../../data_frame_analytics/pages/analytics_creation'; +import { ML_BREADCRUMB } from '../../breadcrumbs'; + +const breadcrumbs = [ + ML_BREADCRUMB, + { + text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameManagementLabel', { + defaultMessage: 'Data Frame Analytics', + }), + href: '#/data_frame_analytics', + }, +]; + +export const analyticsJobsCreationRoute: MlRoute = { + path: '/data_frame_analytics/new_job', + render: (props, deps) => , + breadcrumbs, +}; + +const PageWrapper: FC = ({ location, deps }) => { + const { index, savedSearchId }: Record = parse(location.search, { sort: false }); + const { context } = useResolver(index, savedSearchId, deps.config, basicResolvers(deps)); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts index 552c15a408b65..9b6bcc25c8c7e 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts @@ -6,3 +6,4 @@ export * from './analytics_jobs_list'; export * from './analytics_job_exploration'; +export * from './analytics_job_creation'; diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index a41a6c83615d3..fdf29406893ad 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -230,7 +230,7 @@ export const TimeSeriesExplorerUrlStateManager: FC { + .then((resp) => { if (autoZoomDuration === undefined) { return; } @@ -248,7 +248,7 @@ export const TimeSeriesExplorerUrlStateManager: FC { + .catch((resp) => { // eslint-disable-next-line no-console console.error( 'Time series explorer - error loading time range of forecast from elasticsearch:', diff --git a/x-pack/plugins/ml/public/application/routing/use_refresh.ts b/x-pack/plugins/ml/public/application/routing/use_refresh.ts index f9f3bb66f14f3..f0b93c876526b 100644 --- a/x-pack/plugins/ml/public/application/routing/use_refresh.ts +++ b/x-pack/plugins/ml/public/application/routing/use_refresh.ts @@ -22,7 +22,7 @@ export interface Refresh { const refresh$: Observable = merge( mlTimefilterRefresh$, mlTimefilterTimeChange$, - annotationsRefresh$.pipe(map(d => ({ lastRefresh: d }))) + annotationsRefresh$.pipe(map((d) => ({ lastRefresh: d }))) ); export const useRefresh = () => { diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.ts index 8e94f8d77fbb2..4967e3a684a6b 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.ts @@ -38,7 +38,7 @@ export const useResolver = ( useEffect(() => { (async () => { try { - const res = await Promise.all(funcs.map(r => r())); + const res = await Promise.all(funcs.map((r) => r())); res.forEach((r, i) => (tempResults[funcNames[i]] = r)); setResults(tempResults); } catch (error) { diff --git a/x-pack/plugins/ml/public/application/services/anomaly_detector_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_detector_service.ts index 26fcc25492612..6381f1f8eae89 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_detector_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_detector_service.ts @@ -24,7 +24,7 @@ export class AnomalyDetectorService { .http$<{ count: number; jobs: Job[] }>({ path: `${this.apiBasePath}/${jobId}`, }) - .pipe(map(response => response.jobs[0])); + .pipe(map((response) => response.jobs[0])); } /** @@ -36,7 +36,7 @@ export class AnomalyDetectorService { .http$<{ count: number; jobs: Job[] }>({ path: `${this.apiBasePath}/${jobIds.join(',')}`, }) - .pipe(map(response => response.jobs)); + .pipe(map((response) => response.jobs)); } /** diff --git a/x-pack/plugins/ml/public/application/services/explorer_service.ts b/x-pack/plugins/ml/public/application/services/explorer_service.ts index 8914fd17d1226..717ed3ba64c37 100644 --- a/x-pack/plugins/ml/public/application/services/explorer_service.ts +++ b/x-pack/plugins/ml/public/application/services/explorer_service.ts @@ -6,7 +6,11 @@ import { IUiSettingsClient } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { TimefilterContract, TimeRange } from '../../../../../../src/plugins/data/public'; +import { + TimefilterContract, + TimeRange, + UI_SETTINGS, +} from '../../../../../../src/plugins/data/public'; import { getBoundsRoundedToInterval, TimeBuckets, TimeRangeBounds } from '../util/time_buckets'; import { ExplorerJob, OverallSwimlaneData, SwimlaneData } from '../explorer/explorer_utils'; import { VIEW_BY_JOB_LABEL } from '../explorer/explorer_constants'; @@ -25,8 +29,8 @@ export class ExplorerService { private mlResultsService: MlResultsService ) { this.timeBuckets = new TimeBuckets({ - 'histogram:maxBars': uiSettings.get('histogram:maxBars'), - 'histogram:barTarget': uiSettings.get('histogram:barTarget'), + 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), dateFormat: uiSettings.get('dateFormat'), 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), }); @@ -96,7 +100,7 @@ export class ExplorerService { // Ensure the search bounds align to the bucketing interval used in the swimlane so // that the first and last buckets are complete. const searchBounds = getBoundsRoundedToInterval(bounds, interval, false); - const selectedJobIds = selectedJobs.map(d => d.id); + const selectedJobIds = selectedJobs.map((d) => d.id); // Load the overall bucket scores by time. // Pass the interval in seconds as the swimlane relies on a fixed number of seconds between buckets @@ -152,7 +156,7 @@ export class ExplorerService { false ); - const selectedJobIds = selectedJobs.map(d => d.id); + const selectedJobIds = selectedJobs.map((d) => d.id); // load scores by influencer/jobId value and time. // Pass the interval in seconds as the swimlane relies on a fixed number of seconds between buckets // which wouldn't be the case if e.g. '1M' was used. diff --git a/x-pack/plugins/ml/public/application/services/field_format_service.ts b/x-pack/plugins/ml/public/application/services/field_format_service.ts index 343dc2ea564ba..1a5d22e7320a5 100644 --- a/x-pack/plugins/ml/public/application/services/field_format_service.ts +++ b/x-pack/plugins/ml/public/application/services/field_format_service.ts @@ -30,7 +30,7 @@ class FieldFormatService { // pattern with a title attribute which matches the index configured in the datafeed. // If a Kibana index pattern has not been created // for this index, then no custom field formatting will occur. - jobIds.forEach(jobId => { + jobIds.forEach((jobId) => { const jobObj = mlJobService.getJob(jobId); const datafeedIndices = jobObj.datafeed_config.indices; const id = getIndexPatternIdFromName(datafeedIndices.length ? datafeedIndices[0] : ''); @@ -39,17 +39,17 @@ class FieldFormatService { } }); - const promises = jobIds.map(jobId => Promise.all([this.getFormatsForJob(jobId)])); + const promises = jobIds.map((jobId) => Promise.all([this.getFormatsForJob(jobId)])); Promise.all(promises) - .then(fmtsByJobByDetector => { + .then((fmtsByJobByDetector) => { fmtsByJobByDetector.forEach((formatsByDetector, i) => { this.formatsByJob[jobIds[i]] = formatsByDetector[0]; }); resolve(this.formatsByJob); }) - .catch(err => { + .catch((err) => { reject({ formats: {}, err }); }); }); @@ -94,10 +94,10 @@ class FieldFormatService { if (indexPatternId !== undefined) { // Load the full index pattern configuration to obtain the formats of each field. getIndexPatternById(indexPatternId) - .then(indexPatternData => { + .then((indexPatternData) => { // Store the FieldFormat for each job by detector_index. const fieldList = indexPatternData.fields; - detectors.forEach(dtr => { + detectors.forEach((dtr) => { const esAgg = mlFunctionToESAggregation(dtr.function); // distinct_count detectors should fall back to the default // formatter as the values are just counts. @@ -111,7 +111,7 @@ class FieldFormatService { resolve(formatsByDetector); }) - .catch(err => { + .catch((err) => { reject(err); }); } else { diff --git a/x-pack/plugins/ml/public/application/services/forecast_service.js b/x-pack/plugins/ml/public/application/services/forecast_service.js index 5c51839b012bd..c3d593c3347df 100644 --- a/x-pack/plugins/ml/public/application/services/forecast_service.js +++ b/x-pack/plugins/ml/public/application/services/forecast_service.js @@ -61,14 +61,14 @@ function getForecastsSummary(job, query, earliestMs, maxResults) { sort: [{ forecast_create_timestamp: { order: 'desc' } }], }, }) - .then(resp => { + .then((resp) => { if (resp.hits.total !== 0) { - obj.forecasts = resp.hits.hits.map(hit => hit._source); + obj.forecasts = resp.hits.hits.map((hit) => hit._source); } resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -129,7 +129,7 @@ function getForecastDateRange(job, forecastId) { }, }, }) - .then(resp => { + .then((resp) => { obj.earliest = _.get(resp, 'aggregations.earliest.value', null); obj.latest = _.get(resp, 'aggregations.latest.value', null); if (obj.earliest === null || obj.latest === null) { @@ -138,7 +138,7 @@ function getForecastDateRange(job, forecastId) { resolve(obj); } }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -223,7 +223,7 @@ function getForecastData( ]; // Add in term queries for each of the specified criteria. - _.each(criteriaFields, criteria => { + _.each(criteriaFields, (criteria) => { filterCriteria.push({ term: { [criteria.fieldName]: criteria.fieldValue, @@ -282,9 +282,9 @@ function getForecastData( }, }) .pipe( - map(resp => { + map((resp) => { const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); - _.each(aggregationsByTime, dataForTime => { + _.each(aggregationsByTime, (dataForTime) => { const time = dataForTime.key; obj.results[time] = { prediction: _.get(dataForTime, ['prediction', 'value']), @@ -306,10 +306,10 @@ function runForecast(jobId, duration) { jobId, duration, }) - .then(resp => { + .then((resp) => { resolve(resp); }) - .catch(err => { + .catch((err) => { reject(err); }); }); @@ -355,13 +355,13 @@ function getForecastRequestStats(job, forecastId) { }, }, }) - .then(resp => { + .then((resp) => { if (resp.hits.total !== 0) { obj.stats = _.first(resp.hits.hits)._source; } resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); diff --git a/x-pack/plugins/ml/public/application/services/http_service.ts b/x-pack/plugins/ml/public/application/services/http_service.ts index ff60c67b3c9e6..34dc89dd5f89b 100644 --- a/x-pack/plugins/ml/public/application/services/http_service.ts +++ b/x-pack/plugins/ml/public/application/services/http_service.ts @@ -56,7 +56,7 @@ export function http$(options: HttpFetchOptionsWithPath): Observable { * Creates an Observable from Kibana's HttpHandler. */ export function fromHttpHandler(input: string, init?: RequestInit): Observable { - return new Observable(subscriber => { + return new Observable((subscriber) => { const controller = new AbortController(); const signal = controller.signal; @@ -82,12 +82,12 @@ export function fromHttpHandler(input: string, init?: RequestInit): Observabl getHttp() .fetch(input, perSubscriberInit) - .then(response => { + .then((response) => { abortable = false; subscriber.next(response); subscriber.complete(); }) - .catch(err => { + .catch((err) => { abortable = false; if (!unsubscribed) { subscriber.error(err); @@ -139,7 +139,7 @@ export class HttpService { * Creates an Observable from Kibana's HttpHandler. */ private fromHttpHandler(input: string, init?: RequestInit): Observable { - return new Observable(subscriber => { + return new Observable((subscriber) => { const controller = new AbortController(); const signal = controller.signal; @@ -165,12 +165,12 @@ export class HttpService { this.httpStart .fetch(input, perSubscriberInit) - .then(response => { + .then((response) => { abortable = false; subscriber.next(response); subscriber.complete(); }) - .catch(err => { + .catch((err) => { abortable = false; if (!unsubscribed) { subscriber.error(err); diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js index fb75476c48fa3..a3be479571702 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.js +++ b/x-pack/plugins/ml/public/application/services/job_service.js @@ -114,12 +114,12 @@ class JobService { datafeedIds = {}; ml.getJobs() - .then(resp => { + .then((resp) => { jobs = resp.jobs; // load jobs stats ml.getJobStats() - .then(statsResp => { + .then((statsResp) => { // merge jobs stats into jobs for (let i = 0; i < jobs.length; i++) { const job = jobs[i]; @@ -143,7 +143,7 @@ class JobService { } } } - this.loadDatafeeds().then(datafeedsResp => { + this.loadDatafeeds().then((datafeedsResp) => { for (let i = 0; i < jobs.length; i++) { for (let j = 0; j < datafeedsResp.datafeeds.length; j++) { if (jobs[i].job_id === datafeedsResp.datafeeds[j].job_id) { @@ -159,11 +159,11 @@ class JobService { resolve({ jobs: this.jobs }); }); }) - .catch(err => { + .catch((err) => { error(err); }); }) - .catch(err => { + .catch((err) => { error(err); }); @@ -182,10 +182,10 @@ class JobService { loadJobsWrapper = () => { return this.loadJobs() - .then(function(resp) { + .then(function (resp) { return resp; }) - .catch(function(error) { + .catch(function (error) { console.log('Error loading jobs in route resolve.', error); // Always resolve to ensure tab still works. Promise.resolve([]); @@ -195,13 +195,13 @@ class JobService { refreshJob(jobId) { return new Promise((resolve, reject) => { ml.getJobs({ jobId }) - .then(resp => { + .then((resp) => { if (resp.jobs && resp.jobs.length) { const newJob = resp.jobs[0]; // load jobs stats ml.getJobStats({ jobId }) - .then(statsResp => { + .then((statsResp) => { // merge jobs stats into jobs for (let j = 0; j < statsResp.jobs.length; j++) { if (newJob.job_id === statsResp.jobs[j].job_id) { @@ -230,7 +230,7 @@ class JobService { const datafeedId = this.getDatafeedId(jobId); - this.loadDatafeeds(datafeedId).then(datafeedsResp => { + this.loadDatafeeds(datafeedId).then((datafeedsResp) => { for (let i = 0; i < jobs.length; i++) { for (let j = 0; j < datafeedsResp.datafeeds.length; j++) { if (jobs[i].job_id === datafeedsResp.datafeeds[j].job_id) { @@ -245,12 +245,12 @@ class JobService { resolve({ jobs: this.jobs }); }); }) - .catch(err => { + .catch((err) => { error(err); }); } }) - .catch(err => { + .catch((err) => { error(err); }); @@ -272,7 +272,7 @@ class JobService { const sId = datafeedId !== undefined ? { datafeed_id: datafeedId } : undefined; ml.getDatafeeds(sId) - .then(resp => { + .then((resp) => { // console.log('loadDatafeeds query response:', resp); // make deep copy of datafeeds @@ -280,7 +280,7 @@ class JobService { // load datafeeds stats ml.getDatafeedStats() - .then(statsResp => { + .then((statsResp) => { // merge datafeeds stats into datafeeds for (let i = 0; i < datafeeds.length; i++) { const datafeed = datafeeds[i]; @@ -292,11 +292,11 @@ class JobService { } resolve({ datafeeds }); }) - .catch(err => { + .catch((err) => { error(err); }); }) - .catch(err => { + .catch((err) => { error(err); }); @@ -318,7 +318,7 @@ class JobService { const datafeedId = this.getDatafeedId(jobId); ml.getDatafeedStats({ datafeedId }) - .then(resp => { + .then((resp) => { // console.log('updateSingleJobCounts controller query response:', resp); const datafeeds = resp.datafeeds; let state = 'UNKNOWN'; @@ -327,7 +327,7 @@ class JobService { } resolve(state); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -342,10 +342,7 @@ class JobService { } // return the promise chain - return ml - .addJob({ jobId: job.job_id, job }) - .then(func) - .catch(func); + return ml.addJob({ jobId: job.job_id, job }).then(func).catch(func); } cloneJob(job) { @@ -371,10 +368,11 @@ class JobService { delete tempJob.calendars; delete tempJob.timing_stats; delete tempJob.forecasts_stats; + delete tempJob.assignment_explanation; delete tempJob.analysis_config.use_per_partition_normalization; - _.each(tempJob.analysis_config.detectors, d => { + _.each(tempJob.analysis_config.detectors, (d) => { delete d.detector_index; }); @@ -414,18 +412,17 @@ class JobService { // return the promise chain return ml .updateJob({ jobId, job }) - .then(resp => { - console.log('update job', resp); + .then(() => { return { success: true }; }) - .catch(err => { + .catch((err) => { msgs.notify.error( i18n.translate('xpack.ml.jobService.couldNotUpdateJobErrorMessage', { defaultMessage: 'Could not update job: {jobId}', values: { jobId }, }) ); - console.log('update job', err); + console.error('update job', err); return { success: false, message: err.message }; }); } @@ -434,10 +431,10 @@ class JobService { // return the promise chain return ml .validateJob(obj) - .then(messages => { + .then((messages) => { return { success: true, messages }; }) - .catch(err => { + .catch((err) => { msgs.notify.error( i18n.translate('xpack.ml.jobService.jobValidationErrorMessage', { defaultMessage: 'Job Validation Error: {errorMessage}', @@ -459,7 +456,7 @@ class JobService { // find a job based on the id getJob(jobId) { - const job = _.find(jobs, j => { + const job = _.find(jobs, (j) => { return j.job_id === jobId; }); @@ -491,7 +488,7 @@ class JobService { timeFieldName: job.data_description.time_field, query, }) - .then(timeRange => { + .then((timeRange) => { const bucketSpan = parseInterval(job.analysis_config.bucket_span); const earliestMs = timeRange.start.epoch; const latestMs = +timeRange.start.epoch + 10 * bucketSpan.asMilliseconds(); @@ -540,7 +537,7 @@ class JobService { // get fields from detectors if (job.analysis_config.detectors) { - _.each(job.analysis_config.detectors, dtr => { + _.each(job.analysis_config.detectors, (dtr) => { if (dtr.by_field_name) { fields[dtr.by_field_name] = {}; } @@ -558,7 +555,7 @@ class JobService { // get fields from influencers if (job.analysis_config.influencers) { - _.each(job.analysis_config.influencers, inf => { + _.each(job.analysis_config.influencers, (inf) => { fields[inf] = {}; }); } @@ -592,14 +589,14 @@ class JobService { }; ml.esSearch(data) - .then(resp => { + .then((resp) => { resolve(resp); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); } @@ -627,11 +624,11 @@ class JobService { updateDatafeed(datafeedId, datafeedConfig) { return ml .updateDatafeed({ datafeedId, datafeedConfig }) - .then(resp => { + .then((resp) => { console.log('update datafeed', resp); return { success: true }; }) - .catch(err => { + .catch((err) => { msgs.notify.error( i18n.translate('xpack.ml.jobService.couldNotUpdateDatafeedErrorMessage', { defaultMessage: 'Could not update datafeed: {datafeedId}', @@ -658,10 +655,10 @@ class JobService { start, end, }) - .then(resp => { + .then((resp) => { resolve(resp); }) - .catch(err => { + .catch((err) => { console.log('jobService error starting datafeed:', err); msgs.notify.error( i18n.translate('xpack.ml.jobService.couldNotStartDatafeedErrorMessage', { @@ -682,10 +679,10 @@ class JobService { ml.stopDatafeed({ datafeedId, }) - .then(resp => { + .then((resp) => { resolve(resp); }) - .catch(err => { + .catch((err) => { console.log('jobService error stopping datafeed:', err); const couldNotStopDatafeedErrorMessage = i18n.translate( 'xpack.ml.jobService.couldNotStopDatafeedErrorMessage', @@ -731,10 +728,10 @@ class JobService { return new Promise((resolve, reject) => { if (detector) { ml.validateDetector({ detector }) - .then(resp => { + .then((resp) => { resolve(resp); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); } else { @@ -759,9 +756,9 @@ class JobService { getJobGroups() { const groups = []; const tempGroups = {}; - this.jobs.forEach(job => { + this.jobs.forEach((job) => { if (Array.isArray(job.groups)) { - job.groups.forEach(group => { + job.groups.forEach((group) => { if (tempGroups[group] === undefined) { tempGroups[group] = [job]; } else { @@ -829,7 +826,7 @@ function processBasicJobInfo(localJobService, jobsList) { // use cloned copy of jobs list so not to alter the original const jobsListCopy = _.cloneDeep(jobsList); - _.each(jobsListCopy, jobObj => { + _.each(jobsListCopy, (jobObj) => { const analysisConfig = jobObj.analysis_config; const bucketSpan = parseInterval(analysisConfig.bucket_span); @@ -850,7 +847,7 @@ function processBasicJobInfo(localJobService, jobsList) { if (_.has(jobObj, 'custom_settings.custom_urls')) { job.customUrls = []; - _.each(jobObj.custom_settings.custom_urls, url => { + _.each(jobObj.custom_settings.custom_urls, (url) => { if (_.has(url, 'url_name') && _.has(url, 'url_value') && isWebUrl(url.url_value)) { // Only make web URLs (i.e. http or https) available in dashboard drilldowns. job.customUrls.push(url); @@ -887,7 +884,7 @@ function createJobStats(jobsList, jobStats) { const mlNodes = {}; let failedJobs = 0; - _.each(jobsList, job => { + _.each(jobsList, (job) => { if (job.state === 'opened') { jobStats.open.value++; } else if (job.state === 'closed') { @@ -925,10 +922,10 @@ function createResultsUrlForJobs(jobsList, resultsPage) { from = jobsList[0].earliestTimestampMs; to = jobsList[0].latestResultsTimestampMs; // Will be max(latest source data, latest bucket results) } else { - const jobsWithData = jobsList.filter(j => j.earliestTimestampMs !== undefined); + const jobsWithData = jobsList.filter((j) => j.earliestTimestampMs !== undefined); if (jobsWithData.length > 0) { - from = Math.min(...jobsWithData.map(j => j.earliestTimestampMs)); - to = Math.max(...jobsWithData.map(j => j.latestResultsTimestampMs)); + from = Math.min(...jobsWithData.map((j) => j.earliestTimestampMs)); + to = Math.max(...jobsWithData.map((j) => j.latestResultsTimestampMs)); } } @@ -937,12 +934,12 @@ function createResultsUrlForJobs(jobsList, resultsPage) { const fromString = moment(from).format(timeFormat); // Defaults to 'now' if 'from' is undefined const toString = moment(to).format(timeFormat); // Defaults to 'now' if 'to' is undefined - const jobIds = jobsList.map(j => j.id); + const jobIds = jobsList.map((j) => j.id); return createResultsUrl(jobIds, fromString, toString, resultsPage); } function createResultsUrl(jobIds, start, end, resultsPage) { - const idString = jobIds.map(j => `'${j}'`).join(','); + const idString = jobIds.map((j) => `'${j}'`).join(','); const from = moment(start).toISOString(); const to = moment(end).toISOString(); let path = ''; diff --git a/x-pack/plugins/ml/public/application/services/mapping_service.js b/x-pack/plugins/ml/public/application/services/mapping_service.js index 36363cfa3d713..52aa5ed7413cb 100644 --- a/x-pack/plugins/ml/public/application/services/mapping_service.js +++ b/x-pack/plugins/ml/public/application/services/mapping_service.js @@ -14,10 +14,10 @@ export function getFieldTypeFromMapping(index, fieldName) { return new Promise((resolve, reject) => { if (index !== '') { ml.getFieldCaps({ index, fields: [fieldName] }) - .then(resp => { + .then((resp) => { let fieldType = ''; - _.each(resp.fields, field => { - _.each(field, type => { + _.each(resp.fields, (field) => { + _.each(field, (type) => { if (fieldType === '') { fieldType = type.type; } @@ -25,7 +25,7 @@ export function getFieldTypeFromMapping(index, fieldName) { }); resolve(fieldType); }) - .catch(error => { + .catch((error) => { reject(error); }); } else { diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts index 89950a659f609..7cdd5478e3983 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts @@ -10,6 +10,7 @@ import { basePath } from './index'; import { DataFrameAnalyticsStats } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; import { DataFrameAnalyticsConfig } from '../../data_frame_analytics/common'; import { DeepPartial } from '../../../../common/types/common'; +import { DeleteDataFrameAnalyticsWithIndexStatus } from '../../../../common/types/data_frame_analytics'; export interface GetDataFrameAnalyticsStatsResponseOk { node_failures?: object; @@ -32,6 +33,13 @@ interface GetDataFrameAnalyticsResponse { data_frame_analytics: DataFrameAnalyticsConfig[]; } +interface DeleteDataFrameAnalyticsWithIndexResponse { + acknowledged: boolean; + analyticsJobDeleted: DeleteDataFrameAnalyticsWithIndexStatus; + destIndexDeleted: DeleteDataFrameAnalyticsWithIndexStatus; + destIndexPatternDeleted: DeleteDataFrameAnalyticsWithIndexStatus; +} + export const dataFrameAnalytics = { getDataFrameAnalytics(analyticsId?: string) { const analyticsIdString = analyticsId !== undefined ? `/${analyticsId}` : ''; @@ -86,6 +94,17 @@ export const dataFrameAnalytics = { method: 'DELETE', }); }, + deleteDataFrameAnalyticsAndDestIndex( + analyticsId: string, + deleteDestIndex: boolean, + deleteDestIndexPattern: boolean + ) { + return http({ + path: `${basePath()}/data_frame/analytics/${analyticsId}`, + query: { deleteDestIndex, deleteDestIndexPattern }, + method: 'DELETE', + }); + }, startDataFrameAnalytics(analyticsId: string) { return http({ path: `${basePath()}/data_frame/analytics/${analyticsId}/_start`, diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts index 16e25067fd91e..e2569f6217b34 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts @@ -95,7 +95,6 @@ export const jobs = { body, }); }, - closeJobs(jobIds: string[]) { const body = JSON.stringify({ jobIds }); return http({ diff --git a/x-pack/plugins/ml/public/application/services/ml_server_info.test.ts b/x-pack/plugins/ml/public/application/services/ml_server_info.test.ts index 2b6fb50538020..bd91995b6efc3 100644 --- a/x-pack/plugins/ml/public/application/services/ml_server_info.test.ts +++ b/x-pack/plugins/ml/public/application/services/ml_server_info.test.ts @@ -27,7 +27,7 @@ describe('ml_server_info initial state', () => { }); describe('ml_server_info', () => { - beforeEach(async done => { + beforeEach(async (done) => { await loadMlServerInfo(); done(); }); @@ -40,7 +40,7 @@ describe('ml_server_info', () => { }); describe('defaults', () => { - it('can get defaults', async done => { + it('can get defaults', async (done) => { const defaults = getNewJobDefaults(); expect(defaults.anomaly_detectors.model_memory_limit).toBe('128mb'); @@ -52,7 +52,7 @@ describe('ml_server_info', () => { }); describe('limits', () => { - it('can get limits', async done => { + it('can get limits', async (done) => { const limits = getNewJobLimits(); expect(limits.max_model_memory_limit).toBe('128mb'); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities._service.test.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities._service.test.ts index 792d5222bc6cb..81f05065b5139 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities._service.test.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities._service.test.ts @@ -26,14 +26,14 @@ const indexPattern = ({ describe('new_job_capabilities_service', () => { describe('cloudwatch newJobCaps()', () => { - it('can construct job caps objects from endpoint json', async done => { + it('can construct job caps objects from endpoint json', async (done) => { await newJobCapsService.initializeFromIndexPattern(indexPattern); const { fields, aggs } = await newJobCapsService.newJobCaps; - const networkOutField = fields.find(f => f.id === 'NetworkOut') || { aggs: [] }; - const regionField = fields.find(f => f.id === 'region') || { aggs: [] }; - const meanAgg = aggs.find(a => a.id === 'mean') || { fields: [] }; - const distinctCountAgg = aggs.find(a => a.id === 'distinct_count') || { fields: [] }; + const networkOutField = fields.find((f) => f.id === 'NetworkOut') || { aggs: [] }; + const regionField = fields.find((f) => f.id === 'region') || { aggs: [] }; + const meanAgg = aggs.find((a) => a.id === 'mean') || { fields: [] }; + const distinctCountAgg = aggs.find((a) => a.id === 'distinct_count') || { fields: [] }; expect(fields).toHaveLength(12); expect(aggs).toHaveLength(35); @@ -46,7 +46,7 @@ describe('new_job_capabilities_service', () => { done(); }); - it('job caps including text fields', async done => { + it('job caps including text fields', async (done) => { await newJobCapsService.initializeFromIndexPattern(indexPattern, true, false); const { fields, aggs } = await newJobCapsService.newJobCaps; @@ -56,7 +56,7 @@ describe('new_job_capabilities_service', () => { done(); }); - it('job caps excluding event rate', async done => { + it('job caps excluding event rate', async (done) => { await newJobCapsService.initializeFromIndexPattern(indexPattern, false, true); const { fields, aggs } = await newJobCapsService.newJobCaps; diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts index f5f1bd3d4c541..bc65ebe7a5fac 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts @@ -85,7 +85,7 @@ class NewJobCapsService { } public get categoryFields(): Field[] { - return this._fields.filter(f => categoryFieldTypes.includes(f.type)); + return this._fields.filter((f) => categoryFieldTypes.includes(f.type)); } public async initializeFromIndexPattern( @@ -108,9 +108,9 @@ class NewJobCapsService { allFields ); const catFields = fieldsPreferringText.filter( - f => f.type === ES_FIELD_TYPES.KEYWORD || f.type === ES_FIELD_TYPES.TEXT + (f) => f.type === ES_FIELD_TYPES.KEYWORD || f.type === ES_FIELD_TYPES.TEXT ); - const dateFields = fieldsPreferringText.filter(f => f.type === ES_FIELD_TYPES.DATE); + const dateFields = fieldsPreferringText.filter((f) => f.type === ES_FIELD_TYPES.DATE); const fields = this._removeTextFields ? fieldsPreferringKeyword : allFields; // set the main fields list to contain fields which have been filtered to prefer @@ -127,12 +127,12 @@ class NewJobCapsService { } public getFieldById(id: string): Field | null { - const field = this._fields.find(f => f.id === id); + const field = this._fields.find((f) => f.id === id); return field === undefined ? null : field; } public getAggById(id: string): Aggregation | null { - const agg = this._aggs.find(f => f.id === id); + const agg = this._aggs.find((f) => f.id === id); return agg === undefined ? null : agg; } } @@ -190,8 +190,8 @@ function createObjects(resp: any, indexPatternTitle: string) { // the aggIds and fieldIds lists are no longer needed as we've created // lists of real fields and aggs - fields.forEach(f => delete f.aggIds); - aggs.forEach(a => delete a.fieldIds); + fields.forEach((f) => delete f.aggIds); + aggs.forEach((a) => delete a.fieldIds); return { fields, @@ -219,7 +219,7 @@ function addEventRateField(aggs: Aggregation[], fields: Field[]) { aggs: [], }; - aggs.forEach(a => { + aggs.forEach((a) => { if (eventRateField.aggs !== undefined && a.fields === undefined) { // if the agg's field list is undefined, it is a fieldless aggregation and // so can only be used with the event rate field. @@ -232,17 +232,17 @@ function addEventRateField(aggs: Aggregation[], fields: Field[]) { // create two lists, one removing text fields if there are keyword equivalents and vice versa function processTextAndKeywordFields(fields: Field[]) { - const keywordIds = fields.filter(f => f.type === ES_FIELD_TYPES.KEYWORD).map(f => f.id); - const textIds = fields.filter(f => f.type === ES_FIELD_TYPES.TEXT).map(f => f.id); + const keywordIds = fields.filter((f) => f.type === ES_FIELD_TYPES.KEYWORD).map((f) => f.id); + const textIds = fields.filter((f) => f.type === ES_FIELD_TYPES.TEXT).map((f) => f.id); const fieldsPreferringKeyword = fields.filter( - f => + (f) => f.type !== ES_FIELD_TYPES.TEXT || (f.type === ES_FIELD_TYPES.TEXT && keywordIds.includes(`${f.id}.keyword`) === false) ); const fieldsPreferringText = fields.filter( - f => + (f) => f.type !== ES_FIELD_TYPES.KEYWORD || (f.type === ES_FIELD_TYPES.KEYWORD && textIds.includes(f.id.replace(/\.keyword$/, '')) === false) diff --git a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts index 8f701a9ebe057..a21d0caaedd33 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -74,7 +74,7 @@ export function getMetricData( ...(query ? [query] : []), ]; - entityFields.forEach(entity => { + entityFields.forEach((entity) => { if (entity.fieldValue.length !== 0) { mustCriteria.push({ term: { @@ -227,7 +227,7 @@ export function getModelPlotOutput( ]; // Add in term queries for each of the specified criteria. - _.each(criteriaFields, criteria => { + _.each(criteriaFields, (criteria) => { mustCriteria.push({ term: { [criteria.fieldName]: criteria.fieldValue, @@ -305,7 +305,7 @@ export function getModelPlotOutput( }, }) .pipe( - map(resp => { + map((resp) => { const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); _.each(aggregationsByTime, (dataForTime: any) => { const time = dataForTime.key; @@ -385,7 +385,7 @@ export function getRecordsForCriteria( } // Add in term queries for each of the specified criteria. - _.each(criteriaFields, criteria => { + _.each(criteriaFields, (criteria) => { boolCriteria.push({ term: { [criteria.fieldName]: criteria.fieldValue, @@ -420,7 +420,7 @@ export function getRecordsForCriteria( }, }) .pipe( - map(resp => { + map((resp) => { if (resp.hits.total !== 0) { _.each(resp.hits.hits, (hit: any) => { obj.records.push(hit._source); @@ -533,7 +533,7 @@ export function getScheduledEventsByBucket( }, }) .pipe( - map(resp => { + map((resp) => { const dataByJobId = _.get(resp, ['aggregations', 'jobs', 'buckets'], []); _.each(dataByJobId, (dataForJob: any) => { const jobId: string = dataForJob.key; diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.js b/x-pack/plugins/ml/public/application/services/results_service/results_service.js index b7aa5edc88638..4fccc4d789370 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.js +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.js @@ -116,15 +116,15 @@ export function getScoresByBucket(jobIds, earliestMs, latestMs, interval, maxRes }, }, }) - .then(resp => { + .then((resp) => { const dataByJobId = _.get(resp, ['aggregations', 'jobId', 'buckets'], []); - _.each(dataByJobId, dataForJob => { + _.each(dataByJobId, (dataForJob) => { const jobId = dataForJob.key; const resultsForTime = {}; const dataByTime = _.get(dataForJob, ['byTime', 'buckets'], []); - _.each(dataByTime, dataForTime => { + _.each(dataByTime, (dataForTime) => { const value = _.get(dataForTime, ['anomalyScore', 'value']); if (value !== undefined) { const time = dataForTime.key; @@ -136,7 +136,7 @@ export function getScoresByBucket(jobIds, earliestMs, latestMs, interval, maxRes resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -205,7 +205,7 @@ export function getTopInfluencers( if (influencers.length > 0) { boolCriteria.push({ bool: { - should: influencers.map(influencer => { + should: influencers.map((influencer) => { return { bool: { must: [ @@ -282,18 +282,18 @@ export function getTopInfluencers( }, }, }) - .then(resp => { + .then((resp) => { const fieldNameBuckets = _.get( resp, ['aggregations', 'influencerFieldNames', 'buckets'], [] ); - _.each(fieldNameBuckets, nameBucket => { + _.each(fieldNameBuckets, (nameBucket) => { const fieldName = nameBucket.key; const fieldValues = []; const fieldValueBuckets = _.get(nameBucket, ['influencerFieldValues', 'buckets'], []); - _.each(fieldValueBuckets, valueBucket => { + _.each(fieldValueBuckets, (valueBucket) => { const fieldValueResult = { influencerFieldValue: valueBucket.key, maxAnomalyScore: valueBucket.maxAnomalyScore.value, @@ -307,7 +307,7 @@ export function getTopInfluencers( resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -407,9 +407,9 @@ export function getTopInfluencerValues( }, }, }) - .then(resp => { + .then((resp) => { const buckets = _.get(resp, ['aggregations', 'influencerFieldValues', 'buckets'], []); - _.each(buckets, bucket => { + _.each(buckets, (bucket) => { const result = { influencerFieldValue: bucket.key, maxAnomalyScore: bucket.maxAnomalyScore.value, @@ -420,7 +420,7 @@ export function getTopInfluencerValues( resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -440,9 +440,9 @@ export function getOverallBucketScores(jobIds, topN, earliestMs, latestMs, inter start: earliestMs, end: latestMs, }) - .then(resp => { + .then((resp) => { const dataByTime = _.get(resp, ['overall_buckets'], []); - _.each(dataByTime, dataForTime => { + _.each(dataByTime, (dataForTime) => { const value = _.get(dataForTime, ['overall_score']); if (value !== undefined) { obj.results[dataForTime.timestamp] = value; @@ -451,7 +451,7 @@ export function getOverallBucketScores(jobIds, topN, earliestMs, latestMs, inter resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -594,18 +594,18 @@ export function getInfluencerValueMaxScoreByTime( }, }, }) - .then(resp => { + .then((resp) => { const fieldValueBuckets = _.get( resp, ['aggregations', 'influencerFieldValues', 'buckets'], [] ); - _.each(fieldValueBuckets, valueBucket => { + _.each(fieldValueBuckets, (valueBucket) => { const fieldValue = valueBucket.key; const fieldValues = {}; const timeBuckets = _.get(valueBucket, ['byTime', 'buckets'], []); - _.each(timeBuckets, timeBucket => { + _.each(timeBuckets, (timeBucket) => { const time = timeBucket.key; const score = timeBucket.maxAnomalyScore.value; fieldValues[time] = score; @@ -616,7 +616,7 @@ export function getInfluencerValueMaxScoreByTime( resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -710,15 +710,15 @@ export function getRecordInfluencers(jobIds, threshold, earliestMs, latestMs, ma sort: [{ record_score: { order: 'desc' } }], }, }) - .then(resp => { + .then((resp) => { if (resp.hits.total !== 0) { - _.each(resp.hits.hits, hit => { + _.each(resp.hits.hits, (hit) => { obj.records.push(hit._source); }); } resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -788,7 +788,7 @@ export function getRecordsForInfluencer( if (influencers.length > 0) { boolCriteria.push({ bool: { - should: influencers.map(influencer => { + should: influencers.map((influencer) => { return { nested: { path: 'influencers', @@ -841,15 +841,15 @@ export function getRecordsForInfluencer( sort: [{ record_score: { order: 'desc' } }], }, }) - .then(resp => { + .then((resp) => { if (resp.hits.total !== 0) { - _.each(resp.hits.hits, hit => { + _.each(resp.hits.hits, (hit) => { obj.records.push(hit._source); }); } resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -966,15 +966,15 @@ export function getRecordsForDetector( sort: [{ record_score: { order: 'desc' } }], }, }) - .then(resp => { + .then((resp) => { if (resp.hits.total !== 0) { - _.each(resp.hits.hits, hit => { + _.each(resp.hits.hits, (hit) => { obj.records.push(hit._source); }); } resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -1045,9 +1045,9 @@ export function getEventRateData(index, query, timeFieldName, earliestMs, latest }, }, }) - .then(resp => { + .then((resp) => { const dataByTimeBucket = _.get(resp, ['aggregations', 'eventRate', 'buckets'], []); - _.each(dataByTimeBucket, dataForTime => { + _.each(dataByTimeBucket, (dataForTime) => { const time = dataForTime.key; obj.results[time] = dataForTime.doc_count; }); @@ -1055,7 +1055,7 @@ export function getEventRateData(index, query, timeFieldName, earliestMs, latest resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -1192,7 +1192,7 @@ export function getEventDistributionData( body, rest_total_hits_as_int: true, }) - .then(resp => { + .then((resp) => { // Because of the sampling, results of metricFunctions which use sum or count // can be significantly skewed. Taking into account totalHits we calculate a // a factor to normalize results for these metricFunctions. @@ -1208,7 +1208,7 @@ export function getEventDistributionData( const data = dataByTime.reduce((d, dataForTime) => { const date = +dataForTime.key; const entities = _.get(dataForTime, ['entities', 'buckets'], []); - entities.forEach(entity => { + entities.forEach((entity) => { let value = metricFunction === 'count' ? entity.doc_count : entity.metric.value; if ( @@ -1229,7 +1229,7 @@ export function getEventDistributionData( }, []); resolve(data); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -1260,7 +1260,7 @@ export function getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, lates { term: { job_id: jobId } }, ]; - _.each(criteriaFields, criteria => { + _.each(criteriaFields, (criteria) => { mustCriteria.push({ term: { [criteria.fieldName]: criteria.fieldValue, @@ -1307,9 +1307,9 @@ export function getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, lates }, }, }) - .then(resp => { + .then((resp) => { const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); - _.each(aggregationsByTime, dataForTime => { + _.each(aggregationsByTime, (dataForTime) => { const time = dataForTime.key; obj.results[time] = { score: _.get(dataForTime, ['recordScore', 'value']), @@ -1318,7 +1318,7 @@ export function getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, lates resolve(obj); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js index bd3c8779c9d43..dbff27dceff4d 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js @@ -70,7 +70,7 @@ export const EventsTable = ({ defaultMessage: 'Start', }), sortable: true, - render: timeMs => { + render: (timeMs) => { const time = moment(timeMs); return time.format(TIME_FORMAT); }, @@ -81,7 +81,7 @@ export const EventsTable = ({ defaultMessage: 'End', }), sortable: true, - render: timeMs => { + render: (timeMs) => { const time = moment(timeMs); return time.format(TIME_FORMAT); }, @@ -89,7 +89,7 @@ export const EventsTable = ({ { field: '', name: '', - render: event => ( + render: (event) => ( { + handleImport = async (loadedFile) => { const incomingFile = loadedFile[0]; const errorMessage = i18n.translate( 'xpack.ml.calendarsEdit.importModal.couldNotParseICSFileErrorMessage', @@ -82,14 +82,14 @@ export class ImportModal extends Component { } }; - onEventDelete = eventId => { - this.setState(prevState => ({ - allImportedEvents: prevState.allImportedEvents.filter(event => event.event_id !== eventId), - selectedEvents: prevState.selectedEvents.filter(event => event.event_id !== eventId), + onEventDelete = (eventId) => { + this.setState((prevState) => ({ + allImportedEvents: prevState.allImportedEvents.filter((event) => event.event_id !== eventId), + selectedEvents: prevState.selectedEvents.filter((event) => event.event_id !== eventId), })); }; - onCheckboxToggle = e => { + onCheckboxToggle = (e) => { this.setState({ includePastEvents: e.target.checked, }); @@ -99,7 +99,7 @@ export class ImportModal extends Component { const { allImportedEvents, selectedEvents, includePastEvents } = this.state; const eventsToImport = includePastEvents ? allImportedEvents : selectedEvents; - const events = eventsToImport.map(event => ({ + const events = eventsToImport.map((event) => ({ description: event.description, start_time: event.start_time, end_time: event.end_time, @@ -135,7 +135,7 @@ export class ImportModal extends Component { importedEvents = selectedEvents; } - if (importedEvents.find(e => e.asterisk) !== undefined) { + if (importedEvents.find((e) => e.asterisk) !== undefined) { showRecurringWarning = true; } diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/utils.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/utils.js index d24577826838e..07bf49ea6d7db 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/utils.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/utils.js @@ -12,7 +12,7 @@ function createEvents(ical) { const events = ical.events(); const mlEvents = []; - events.forEach(e => { + events.forEach((e) => { if (e.element === 'VEVENT') { const description = e.properties.SUMMARY; const start = e.properties.DTSTART; @@ -38,7 +38,7 @@ function createEvents(ical) { export function filterEvents(events) { const now = moment().valueOf(); - return events.filter(e => e.start_time > now); + return events.filter((e) => e.start_time > now); } export function parseICSFile(data) { diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js index 67570e2c7c54f..7efc37d4bf8ce 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js @@ -59,8 +59,8 @@ class NewCalendarUI extends Component { try { const { jobIds, groupIds, calendars } = await getCalendarSettingsData(); - const jobIdOptions = jobIds.map(jobId => ({ label: jobId })); - const groupIdOptions = groupIds.map(groupId => ({ label: groupId })); + const jobIdOptions = jobIds.map((jobId) => ({ label: jobId })); + const groupIdOptions = groupIds.map((groupId) => ({ label: groupId })); const selectedJobOptions = []; const selectedGroupOptions = []; @@ -71,7 +71,7 @@ class NewCalendarUI extends Component { // Editing existing calendar. if (this.props.calendarId !== undefined) { - selectedCalendar = calendars.find(cal => cal.calendar_id === this.props.calendarId); + selectedCalendar = calendars.find((cal) => cal.calendar_id === this.props.calendarId); if (selectedCalendar) { formCalendarId = selectedCalendar.calendar_id; @@ -80,10 +80,10 @@ class NewCalendarUI extends Component { if (selectedCalendar.job_ids.includes(GLOBAL_CALENDAR)) { isGlobalCalendar = true; } else { - selectedCalendar.job_ids.forEach(id => { - if (jobIds.find(jobId => jobId === id)) { + selectedCalendar.job_ids.forEach((id) => { + if (jobIds.find((jobId) => jobId === id)) { selectedJobOptions.push({ label: id }); - } else if (groupIds.find(groupId => groupId === id)) { + } else if (groupIds.find((groupId) => groupId === id)) { selectedGroupOptions.push({ label: id }); } }); @@ -195,12 +195,12 @@ class NewCalendarUI extends Component { const allIds = isGlobalCalendar ? [GLOBAL_CALENDAR] : [ - ...selectedJobOptions.map(option => option.label), - ...selectedGroupOptions.map(option => option.label), + ...selectedJobOptions.map((option) => option.label), + ...selectedGroupOptions.map((option) => option.label), ]; // Reduce events to fields expected by api - const eventsToSave = events.map(event => ({ + const eventsToSave = events.map((event) => ({ description: event.description, start_time: event.start_time, end_time: event.end_time, @@ -217,12 +217,12 @@ class NewCalendarUI extends Component { return calendar; }; - onCreateGroupOption = newGroup => { + onCreateGroupOption = (newGroup) => { const newOption = { label: newGroup, }; // Select the option. - this.setState(prevState => ({ + this.setState((prevState) => ({ selectedGroupOptions: prevState.selectedGroupOptions.concat(newOption), })); }; @@ -233,19 +233,19 @@ class NewCalendarUI extends Component { }); }; - onJobSelection = selectedJobOptions => { + onJobSelection = (selectedJobOptions) => { this.setState({ selectedJobOptions, }); }; - onGroupSelection = selectedGroupOptions => { + onGroupSelection = (selectedGroupOptions) => { this.setState({ selectedGroupOptions, }); }; - onCalendarIdChange = e => { + onCalendarIdChange = (e) => { const isValid = validateCalendarId(e.target.value); this.setState({ @@ -254,14 +254,14 @@ class NewCalendarUI extends Component { }); }; - onDescriptionChange = e => { + onDescriptionChange = (e) => { this.setState({ description: e.target.value, }); }; showImportModal = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isImportModalVisible: !prevState.isImportModalVisible, })); }; @@ -272,9 +272,9 @@ class NewCalendarUI extends Component { }); }; - onEventDelete = eventId => { - this.setState(prevState => ({ - events: prevState.events.filter(event => event.event_id !== eventId), + onEventDelete = (eventId) => { + this.setState((prevState) => ({ + events: prevState.events.filter((event) => event.event_id !== eventId), })); }; @@ -286,15 +286,15 @@ class NewCalendarUI extends Component { this.setState({ isNewEventModalVisible: true }); }; - addEvent = event => { - this.setState(prevState => ({ + addEvent = (event) => { + this.setState((prevState) => ({ events: [...prevState.events, event], isNewEventModalVisible: false, })); }; - addImportedEvents = events => { - this.setState(prevState => ({ + addImportedEvents = (events) => { + this.setState((prevState) => ({ events: [...prevState.events, ...events], isImportModalVisible: false, })); diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js index 7f5ade64e7f14..0a6e8916fa657 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js @@ -38,7 +38,7 @@ jest.mock('../../../services/ml_api_service', () => ({ jest.mock('./utils', () => ({ getCalendarSettingsData: jest.fn().mockImplementation( () => - new Promise(resolve => { + new Promise((resolve) => { resolve({ jobIds: ['test-job-one', 'test-job-2'], groupIds: ['test-group-one', 'test-group-two'], @@ -48,7 +48,7 @@ jest.mock('./utils', () => ({ ), })); jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => ({ - withKibana: comp => { + withKibana: (comp) => { return comp; }, })); diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js index 814f30a70db54..8380fd36b458c 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js @@ -42,9 +42,7 @@ export class NewEventModal extends Component { super(props); const startDate = moment().startOf('day'); - const endDate = moment() - .startOf('day') - .add(1, 'days'); + const endDate = moment().startOf('day').add(1, 'days'); this.state = { startDate, @@ -55,7 +53,7 @@ export class NewEventModal extends Component { }; } - onDescriptionChange = e => { + onDescriptionChange = (e) => { this.setState({ description: e.target.value, }); @@ -76,7 +74,7 @@ export class NewEventModal extends Component { this.props.addEvent(event); }; - handleChangeStart = date => { + handleChangeStart = (date) => { let start = null; let end = this.state.endDate; @@ -96,7 +94,7 @@ export class NewEventModal extends Component { }); }; - handleChangeEnd = date => { + handleChangeEnd = (date) => { let start = this.state.startDate; let end = null; @@ -116,7 +114,7 @@ export class NewEventModal extends Component { }); }; - handleTimeStartChange = event => { + handleTimeStartChange = (event) => { const dateString = event.target.value; let isValidDate = false; @@ -136,7 +134,7 @@ export class NewEventModal extends Component { } }; - handleTimeEndChange = event => { + handleTimeEndChange = (event) => { const dateString = event.target.value; let isValidDate = false; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js index e91dce6124cef..f8b9c97db09e3 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js @@ -42,10 +42,7 @@ describe('NewEventModal', () => { // trigger handleChangeStart directly with startMoment instance.handleChangeStart(startMoment); // add 3 days to endMoment as it will be adjusted to be one day after startDate - const expected = endMoment - .startOf('day') - .add(3, 'days') - .format(); + const expected = endMoment.startOf('day').add(3, 'days').format(); expect(wrapper.state('endDate').format()).toBe(expected); }); @@ -66,10 +63,7 @@ describe('NewEventModal', () => { // trigger handleChangeStart directly with endMoment instance.handleChangeStart(endMoment); // subtract 3 days from startDate as it will be adjusted to be one day before endDate - const expected = startMoment - .startOf('day') - .subtract(2, 'days') - .format(); + const expected = startMoment.startOf('day').subtract(2, 'days').format(); expect(wrapper.state('startDate').format()).toBe(expected); }); diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/utils.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/utils.js index efc54c181fdc1..874a1e14cb5aa 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/utils.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/utils.js @@ -12,10 +12,10 @@ function getJobIds() { return new Promise((resolve, reject) => { ml.jobs .jobsSummary() - .then(resp => { - resolve(resp.map(job => job.id)); + .then((resp) => { + resolve(resp.map((job) => job.id)); }) - .catch(err => { + .catch((err) => { const errorMessage = i18n.translate( 'xpack.ml.calendarsEdit.errorWithFetchingJobSummariesErrorMessage', { @@ -33,10 +33,10 @@ function getGroupIds() { return new Promise((resolve, reject) => { ml.jobs .groups() - .then(resp => { - resolve(resp.map(group => group.id)); + .then((resp) => { + resolve(resp.map((group) => group.id)); }) - .catch(err => { + .catch((err) => { const errorMessage = i18n.translate( 'xpack.ml.calendarsEdit.errorWithLoadingGroupsErrorMessage', { @@ -53,10 +53,10 @@ function getGroupIds() { function getCalendars() { return new Promise((resolve, reject) => { ml.calendars() - .then(resp => { + .then((resp) => { resolve(resp); }) - .catch(err => { + .catch((err) => { const errorMessage = i18n.translate( 'xpack.ml.calendarsEdit.errorWithLoadingCalendarsErrorMessage', { @@ -104,7 +104,5 @@ export function validateCalendarId(calendarId) { } export function generateTempId() { - return Math.random() - .toString(36) - .substr(2, 9); + return Math.random().toString(36).substr(2, 9); } diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js index c968db0b32d5a..c1c00bb022f20 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js @@ -75,7 +75,7 @@ export class CalendarsListUI extends Component { this.setState({ isDestroyModalVisible: true }); }; - setSelectedCalendarList = selectedCalendars => { + setSelectedCalendarList = (selectedCalendars) => { this.setState({ selectedForDeletion: selectedCalendars }); }; @@ -137,7 +137,7 @@ export class CalendarsListUI extends Component { defaultMessage="Delete {calendarsCount, plural, one {this calendar} other {these calendars}}? {calendarsList}" values={{ calendarsCount: selectedForDeletion.length, - calendarsList: selectedForDeletion.map(c => c.calendar_id).join(', '), + calendarsList: selectedForDeletion.map((c) => c.calendar_id).join(', '), }} />

diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js index b2fce2c1474cb..619478db54441 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js @@ -37,11 +37,11 @@ jest.mock('../../../services/ml_api_service', () => ({ jest.mock('react', () => { const r = jest.requireActual('react'); - return { ...r, memo: x => x }; + return { ...r, memo: (x) => x }; }); jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => ({ - withKibana: node => { + withKibana: (node) => { return node; }, })); diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js b/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js index 47dc373e537ba..857d2e7e6659b 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js @@ -10,7 +10,7 @@ import React from 'react'; import { CalendarsListHeader } from './header'; jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => ({ - withKibana: comp => { + withKibana: (comp) => { return comp; }, })); diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js b/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js index be41eabd5ae2d..b81cc6fbb4c30 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js @@ -45,7 +45,9 @@ export const CalendarsListTable = ({ sortable: true, truncateText: true, scope: 'row', - render: id => {id}, + render: (id) => ( + {id} + ), }, { field: 'job_ids_string', @@ -54,7 +56,7 @@ export const CalendarsListTable = ({ }), sortable: true, truncateText: true, - render: jobList => { + render: (jobList) => { return jobList === GLOBAL_CALENDAR ? ( + render: (eventsLength) => i18n.translate('xpack.ml.calendarsList.table.eventsCountLabel', { defaultMessage: '{eventsLength, plural, one {# event} other {# events}}', values: { eventsLength }, @@ -82,7 +84,7 @@ export const CalendarsListTable = ({ ]; const tableSelection = { - onSelectionChange: selection => setSelectedCalendarList(selection), + onSelectionChange: (selection) => setSelectedCalendarList(selection), }; const search = { diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js index 404484b8055f2..816eea39059a8 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js @@ -40,7 +40,7 @@ export class AddItemPopover extends Component { }; } - onItemsTextChange = e => { + onItemsTextChange = (e) => { this.setState({ itemsText: e.target.value, }); @@ -62,7 +62,7 @@ export class AddItemPopover extends Component { const items = this.state.itemsText.split('\n'); const addItems = []; // Remove duplicates. - items.forEach(item => { + items.forEach((item) => { if (addItems.indexOf(item) === -1 && item.length > 0) { addItems.push(item); } diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js index 0266bc2a55318..bb909ddc3aa78 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js @@ -9,7 +9,7 @@ // with 'mock' so it can be used lazily. const mockCheckPermission = jest.fn(() => true); jest.mock('../../../../capabilities/check_capabilities', () => ({ - checkPermission: privilege => mockCheckPermission(privilege), + checkPermission: (privilege) => mockCheckPermission(privilege), })); jest.mock('../../../../services/ml_api_service', () => 'ml'); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js index e1e32afe08dbe..e7846cb546852 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js @@ -33,7 +33,7 @@ export class EditDescriptionPopover extends Component { }; } - onChange = e => { + onChange = (e) => { this.setState({ value: e.target.value, }); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/filter_list_usage_popover/filter_list_usage_popover.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/filter_list_usage_popover/filter_list_usage_popover.js index b3547b3ee6568..25c86c559e303 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/filter_list_usage_popover/filter_list_usage_popover.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/filter_list_usage_popover/filter_list_usage_popover.js @@ -39,7 +39,7 @@ export class FilterListUsagePopover extends Component { const linkText = `${entityValues.length} ${entityType}${entityValues.length !== 1 ? 's' : ''}`; - const listItems = entityValues.map(value =>
  • {value}
  • ); + const listItems = entityValues.map((value) =>
  • {value}
  • ); const button = ( diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js index adf57632bc84b..6437e819db04f 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js @@ -49,11 +49,11 @@ function getMatchingFilterItems(searchBarQuery, items) { // Convert the list of Strings into a list of Objects suitable for running through // the search bar query. - const allItems = items.map(item => ({ value: item })); + const allItems = items.map((item) => ({ value: item })); const matchingObjects = EuiSearchBar.Query.execute(searchBarQuery, allItems, { defaultFields: ['value'], }); - return matchingObjects.map(item => item.value); + return matchingObjects.map((item) => item.value); } function getActivePage(activePageState, itemsPerPage, numMatchingItems) { @@ -105,13 +105,13 @@ export class EditFilterListUI extends Component { } } - loadFilterList = filterId => { + loadFilterList = (filterId) => { ml.filters .filters({ filterId }) - .then(filter => { + .then((filter) => { this.setLoadedFilterState(filter); }) - .catch(resp => { + .catch((resp) => { console.log(`Error loading filter ${filterId}:`, resp); const { toasts } = this.props.kibana.services.notifications; toasts.addDanger( @@ -128,9 +128,9 @@ export class EditFilterListUI extends Component { }); }; - setLoadedFilterState = loadedFilter => { + setLoadedFilterState = (loadedFilter) => { // Store the loaded filter so we can diff changes to the items when saving updates. - this.setState(prevState => { + this.setState((prevState) => { const { itemsPerPage, searchQuery } = prevState; const matchingItems = getMatchingFilterItems(searchQuery, loadedFilter.items); @@ -150,23 +150,23 @@ export class EditFilterListUI extends Component { }); }; - updateNewFilterId = newFilterId => { + updateNewFilterId = (newFilterId) => { this.setState({ newFilterId, isNewFilterIdInvalid: !isValidFilterListId(newFilterId), }); }; - updateDescription = description => { + updateDescription = (description) => { this.setState({ description }); }; - addItems = itemsToAdd => { - this.setState(prevState => { + addItems = (itemsToAdd) => { + this.setState((prevState) => { const { itemsPerPage, searchQuery } = prevState; const items = [...prevState.items]; const alreadyInFilter = []; - itemsToAdd.forEach(item => { + itemsToAdd.forEach((item) => { if (items.indexOf(item) === -1) { items.push(item); } else { @@ -206,10 +206,10 @@ export class EditFilterListUI extends Component { }; deleteSelectedItems = () => { - this.setState(prevState => { + this.setState((prevState) => { const { selectedItems, itemsPerPage, searchQuery } = prevState; const items = [...prevState.items]; - selectedItems.forEach(item => { + selectedItems.forEach((item) => { const index = items.indexOf(item); if (index !== -1) { items.splice(index, 1); @@ -230,7 +230,7 @@ export class EditFilterListUI extends Component { }; onSearchChange = ({ query }) => { - this.setState(prevState => { + this.setState((prevState) => { const { items, itemsPerPage } = prevState; const matchingItems = getMatchingFilterItems(query, items); @@ -245,7 +245,7 @@ export class EditFilterListUI extends Component { }; setItemSelected = (item, isSelected) => { - this.setState(prevState => { + this.setState((prevState) => { const selectedItems = [...prevState.selectedItems]; const index = selectedItems.indexOf(item); if (isSelected === true && index === -1) { @@ -260,11 +260,11 @@ export class EditFilterListUI extends Component { }); }; - setActivePage = activePage => { + setActivePage = (activePage) => { this.setState({ activePage }); }; - setItemsPerPage = itemsPerPage => { + setItemsPerPage = (itemsPerPage) => { this.setState({ itemsPerPage, activePage: 0, @@ -277,11 +277,11 @@ export class EditFilterListUI extends Component { const { loadedFilter, newFilterId, description, items } = this.state; const filterId = this.props.filterId !== undefined ? this.props.filterId : newFilterId; saveFilterList(filterId, description, items, loadedFilter) - .then(savedFilter => { + .then((savedFilter) => { this.setLoadedFilterState(savedFilter); returnToFiltersList(); }) - .catch(resp => { + .catch((resp) => { console.log(`Error saving filter ${filterId}:`, resp); const { toasts } = this.props.kibana.services.notifications; toasts.addDanger( diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js index a743a4b22ce92..1223194030e64 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js @@ -37,7 +37,7 @@ jest.mock('../../../services/ml_api_service', () => ({ })); jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => ({ - withKibana: node => { + withKibana: (node) => { return node; }, })); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.js index f1efa173178f2..f47264221eaec 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.js @@ -88,7 +88,7 @@ export const EditFilterListHeader = ({ name="new_filter_id" value={newFilterId} isInvalid={isNewFilterIdInvalid} - onChange={e => updateNewFilterId(e.target.value)} + onChange={(e) => updateNewFilterId(e.target.value)} /> ); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/utils.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/utils.js index c82be4cbfa71e..b07dfcc48c891 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/utils.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/utils.js @@ -21,19 +21,19 @@ export function saveFilterList(filterId, description, items, loadedFilterList) { if (loadedFilterList === undefined || loadedFilterList.filter_id === undefined) { // Create a new filter. addFilterList(filterId, description, items) - .then(newFilter => { + .then((newFilter) => { resolve(newFilter); }) - .catch(error => { + .catch((error) => { reject(error); }); } else { // Edit to existing filter. updateFilterList(loadedFilterList, description, items) - .then(updatedFilter => { + .then((updatedFilter) => { resolve(updatedFilter); }) - .catch(error => { + .catch((error) => { reject(error); }); } @@ -55,16 +55,16 @@ export function addFilterList(filterId, description, items) { // First check the filterId isn't already in use by loading the current list of filters. ml.filters .filtersStats() - .then(filterLists => { - const savedFilterIds = filterLists.map(filterList => filterList.filter_id); + .then((filterLists) => { + const savedFilterIds = filterLists.map((filterList) => filterList.filter_id); if (savedFilterIds.indexOf(filterId) === -1) { // Save the new filter. ml.filters .addFilter(filterId, description, items) - .then(newFilter => { + .then((newFilter) => { resolve(newFilter); }) - .catch(error => { + .catch((error) => { reject(error); }); } else { @@ -73,7 +73,7 @@ export function addFilterList(filterId, description, items) { reject(new Error(filterWithIdExistsErrorMessage)); } }) - .catch(error => { + .catch((error) => { reject(error); }); }); @@ -83,15 +83,15 @@ export function updateFilterList(loadedFilterList, description, items) { return new Promise((resolve, reject) => { // Get items added and removed from loaded filter. const loadedItems = loadedFilterList.items; - const addItems = items.filter(item => loadedItems.includes(item) === false); - const removeItems = loadedItems.filter(item => items.includes(item) === false); + const addItems = items.filter((item) => loadedItems.includes(item) === false); + const removeItems = loadedItems.filter((item) => items.includes(item) === false); ml.filters .updateFilter(loadedFilterList.filter_id, description, addItems, removeItems) - .then(updatedFilter => { + .then((updatedFilter) => { resolve(updatedFilter); }) - .catch(error => { + .catch((error) => { reject(error); }); }); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.js index 9e40d99f1c898..270d5fa350cae 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.js @@ -42,11 +42,11 @@ export class FilterListsUI extends Component { this.refreshFilterLists(); } - setFilterLists = filterLists => { + setFilterLists = (filterLists) => { // Check selected filter lists still exist. - this.setState(prevState => { - const loadedFilterIds = filterLists.map(filterList => filterList.filter_id); - const selectedFilterLists = prevState.selectedFilterLists.filter(filterList => { + this.setState((prevState) => { + const loadedFilterIds = filterLists.map((filterList) => filterList.filter_id); + const selectedFilterLists = prevState.selectedFilterLists.filter((filterList) => { return loadedFilterIds.indexOf(filterList.filter_id) !== -1; }); @@ -57,7 +57,7 @@ export class FilterListsUI extends Component { }); }; - setSelectedFilterLists = selectedFilterLists => { + setSelectedFilterLists = (selectedFilterLists) => { this.setState({ selectedFilterLists }); }; @@ -65,10 +65,10 @@ export class FilterListsUI extends Component { // Load the list of filters. ml.filters .filtersStats() - .then(filterLists => { + .then((filterLists) => { this.setFilterLists(filterLists); }) - .catch(resp => { + .catch((resp) => { console.log('Error loading list of filters:', resp); const { toasts } = this.props.kibana.services.notifications; toasts.addDanger( diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js index c1bcee4acdd37..6ddea7a3281d3 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js @@ -17,7 +17,7 @@ jest.mock('../../../capabilities/check_capabilities', () => ({ })); jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => ({ - withKibana: node => { + withKibana: (node) => { return node; }, })); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js index fcbf90ec62d4a..7f94f1f2534fb 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js @@ -81,7 +81,9 @@ function getColumns() { name: i18n.translate('xpack.ml.settings.filterLists.table.idColumnName', { defaultMessage: 'ID', }), - render: id => {id}, + render: (id) => ( + {id} + ), sortable: true, scope: 'row', }, @@ -104,7 +106,7 @@ function getColumns() { name: i18n.translate('xpack.ml.settings.filterLists.table.inUseColumnName', { defaultMessage: 'In use', }), - render: usedBy => , + render: (usedBy) => , sortable: true, }, ]; @@ -157,10 +159,10 @@ export function FilterListsTable({ }; const tableSelection = { - selectable: filterList => + selectable: (filterList) => filterList.used_by === undefined || filterList.used_by.jobs.length === 0, selectableMessage: () => undefined, - onSelectionChange: selection => setSelectedFilterLists(selection), + onSelectionChange: (selection) => setSelectedFilterLists(selection), }; return ( diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/context_chart_mask/context_chart_mask.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/context_chart_mask/context_chart_mask.js index 43578e9f5898a..e9657ed601b78 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/context_chart_mask/context_chart_mask.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/context_chart_mask/context_chart_mask.js @@ -39,13 +39,13 @@ export function ContextChartMask(contextGroup, data, drawBounds, swimlaneHeight) this._y = null; } -ContextChartMask.prototype.style = function(prop, val) { +ContextChartMask.prototype.style = function (prop, val) { this.leftGroup.style(prop, val); this.rightGroup.style(prop, val); return this; }; -ContextChartMask.prototype.x = function(f) { +ContextChartMask.prototype.x = function (f) { if (f == null) { return this._x; } @@ -53,7 +53,7 @@ ContextChartMask.prototype.x = function(f) { return this; }; -ContextChartMask.prototype.y = function(f) { +ContextChartMask.prototype.y = function (f) { if (f == null) { return this._y; } @@ -61,7 +61,7 @@ ContextChartMask.prototype.y = function(f) { return this; }; -ContextChartMask.prototype.redraw = function() { +ContextChartMask.prototype.redraw = function () { const yDomain = this._y.domain(); const minY = yDomain[0]; const maxY = yDomain[1]; @@ -71,11 +71,11 @@ ContextChartMask.prototype.redraw = function() { const that = this; - const leftData = this.data.filter(function(d) { + const leftData = this.data.filter(function (d) { return d.date < that.from; }); - const rightData = this.data.filter(function(d) { + const rightData = this.data.filter(function (d) { return d.date > that.to; }); @@ -83,29 +83,29 @@ ContextChartMask.prototype.redraw = function() { if (this.drawBounds === true) { const boundedArea = d3.svg .area() - .x(function(d) { + .x(function (d) { return that._x(d.date) || 1; }) - .y0(function(d) { + .y0(function (d) { return that._y(Math.min(maxY, Math.max(d.lower, minY))); }) - .y1(function(d) { + .y1(function (d) { return that._y(Math.max(minY, Math.min(d.upper, maxY))); }) - .defined(d => d.lower !== null && d.upper !== null); + .defined((d) => d.lower !== null && d.upper !== null); this.leftGroup.select('.left.area.bounds').attr('d', boundedArea(leftData)); this.rightGroup.select('.right.area.bounds').attr('d', boundedArea(rightData)); } const valuesLine = d3.svg .line() - .x(function(d) { + .x(function (d) { return that._x(d.date); }) - .y(function(d) { + .y(function (d) { return that._y(d.value); }) - .defined(d => d.value !== null); + .defined((d) => d.value !== null); this.leftGroup.select('.left.values-line').attr('d', valuesLine(leftData)); drawLineChartDots(leftData, this.leftGroup, valuesLine, 1); @@ -168,7 +168,7 @@ ContextChartMask.prototype.redraw = function() { return this; }; -ContextChartMask.prototype.reveal = function(extent) { +ContextChartMask.prototype.reveal = function (extent) { this.from = extent[0]; this.to = extent[1]; this.redraw(); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx index 7bb0b27472c88..c144525699d81 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx @@ -126,7 +126,7 @@ export class EntityControl extends Component { + inputRef={(input) => { if (input) { this.inputRef = input; } diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js index eded8460d2205..87bd2bb4af62c 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js @@ -72,17 +72,17 @@ export class ForecastingModalUI extends Component { addMessage = (message, status, clearFirst = false) => { const msg = { message, status }; - this.setState(prevState => ({ + this.setState((prevState) => ({ messages: clearFirst ? [msg] : [...prevState.messages, msg], })); }; - viewForecast = forecastId => { + viewForecast = (forecastId) => { this.props.setForecastId(forecastId); this.closeModal(); }; - onNewForecastDurationChange = event => { + onNewForecastDurationChange = (event) => { const newForecastDurationErrors = []; let isNewForecastDurationValid = true; const duration = parseInterval(event.target.value); @@ -157,7 +157,7 @@ export class ForecastingModalUI extends Component { }); this.runForecast(true); }) - .catch(resp => { + .catch((resp) => { console.log('Time series forecast modal - could not open job:', resp); this.addMessage( i18n.translate( @@ -200,7 +200,7 @@ export class ForecastingModalUI extends Component { .then(() => { this.setState({ jobClosingState: PROGRESS_STATES.DONE }); }) - .catch(response => { + .catch((response) => { console.log('Time series forecast modal - could not close job:', response); this.addMessage( i18n.translate( @@ -216,7 +216,7 @@ export class ForecastingModalUI extends Component { } }; - runForecast = closeJobAfterRunning => { + runForecast = (closeJobAfterRunning) => { this.setState({ forecastProgress: 0, }); @@ -227,7 +227,7 @@ export class ForecastingModalUI extends Component { mlForecastService .runForecast(this.props.job.job_id, `${durationInSeconds}s`) - .then(resp => { + .then((resp) => { // Endpoint will return { acknowledged:true, id: } before forecast is complete. // So wait for results and then refresh the dashboard to the end of the forecast. if (resp.forecast_id !== undefined) { @@ -236,7 +236,7 @@ export class ForecastingModalUI extends Component { this.runForecastErrorHandler(resp, closeJobAfterRunning); } }) - .catch(resp => this.runForecastErrorHandler(resp, closeJobAfterRunning)); + .catch((resp) => this.runForecastErrorHandler(resp, closeJobAfterRunning)); }; waitForForecastResults = (forecastId, closeJobAfterRunning) => { @@ -248,7 +248,7 @@ export class ForecastingModalUI extends Component { this.forecastChecker = setInterval(() => { mlForecastService .getForecastRequestStats(this.props.job, forecastId) - .then(resp => { + .then((resp) => { // Get the progress (stats value is between 0 and 1). const progress = _.get(resp, ['stats', 'forecast_progress'], previousProgress); const status = _.get(resp, ['stats', 'forecast_status']); @@ -264,7 +264,7 @@ export class ForecastingModalUI extends Component { // Display any messages returned in the request stats. let messages = _.get(resp, ['stats', 'forecast_messages'], []); - messages = messages.map(message => ({ message, status: MESSAGE_LEVEL.WARNING })); + messages = messages.map((message) => ({ message, status: MESSAGE_LEVEL.WARNING })); this.setState({ messages }); if (status === FORECAST_REQUEST_STATE.FINISHED) { @@ -281,7 +281,7 @@ export class ForecastingModalUI extends Component { this.props.setForecastId(forecastId); this.closeAfterRunningForecast(); }) - .catch(response => { + .catch((response) => { // Load the forecast data in the main page, // but leave this dialog open so the error can be viewed. console.log('Time series forecast modal - could not close job:', response); @@ -340,7 +340,7 @@ export class ForecastingModalUI extends Component { } } }) - .catch(resp => { + .catch((resp) => { console.log( 'Time series forecast modal - error loading stats of forecast from elasticsearch:', resp @@ -376,12 +376,12 @@ export class ForecastingModalUI extends Component { }; mlForecastService .getForecastsSummary(job, statusFinishedQuery, bounds.min.valueOf(), FORECASTS_VIEW_MAX) - .then(resp => { + .then((resp) => { this.setState({ previousForecasts: resp.forecasts, }); }) - .catch(resp => { + .catch((resp) => { console.log('Time series forecast modal - error obtaining forecasts summary:', resp); this.addMessage( i18n.translate( @@ -396,7 +396,7 @@ export class ForecastingModalUI extends Component { // Display a warning about running a forecast if there is high number // of partitioning fields. - const entityFieldNames = this.props.entities.map(entity => entity.fieldName); + const entityFieldNames = this.props.entities.map((entity) => entity.fieldName); if (entityFieldNames.length > 0) { ml.getCardinalityOfFields({ index: job.datafeed_config.indices, @@ -406,9 +406,9 @@ export class ForecastingModalUI extends Component { earliestMs: job.data_counts.earliest_record_timestamp, latestMs: job.data_counts.latest_record_timestamp, }) - .then(results => { + .then((results) => { let numPartitions = 1; - Object.values(results).forEach(cardinality => { + Object.values(results).forEach((cardinality) => { numPartitions = numPartitions * cardinality; }); if (numPartitions > WARN_NUM_PARTITIONS) { @@ -426,7 +426,7 @@ export class ForecastingModalUI extends Component { ); } }) - .catch(resp => { + .catch((resp) => { console.log( 'Time series forecast modal - error obtaining cardinality of fields:', resp diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js index 83c153ab39eba..42e9e28f5862d 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js @@ -25,7 +25,7 @@ function getColumns(viewForecast) { defaultMessage: 'Created', }), dataType: 'date', - render: date => formatHumanReadableDateTimeSeconds(date), + render: (date) => formatHumanReadableDateTimeSeconds(date), sortable: true, }, { @@ -34,7 +34,7 @@ function getColumns(viewForecast) { defaultMessage: 'From', }), dataType: 'date', - render: date => formatHumanReadableDateTimeSeconds(date), + render: (date) => formatHumanReadableDateTimeSeconds(date), sortable: true, }, { @@ -43,7 +43,7 @@ function getColumns(viewForecast) { defaultMessage: 'To', }), dataType: 'date', - render: date => formatHumanReadableDateTimeSeconds(date), + render: (date) => formatHumanReadableDateTimeSeconds(date), sortable: true, }, { @@ -51,7 +51,7 @@ function getColumns(viewForecast) { defaultMessage: 'View', }), width: '60px', - render: forecast => { + render: (forecast) => { const viewForecastAriaLabel = i18n.translate( 'xpack.ml.timeSeriesExplorer.forecastsList.viewForecastAriaLabel', { diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index fca65ea2c1f3b..190bce1639c4a 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -158,25 +158,25 @@ class TimeseriesChartIntl extends Component { this.focusValuesLine = d3.svg .line() - .x(function(d) { + .x(function (d) { return focusXScale(d.date); }) - .y(function(d) { + .y(function (d) { return focusYScale(d.value); }) - .defined(d => d.value !== null); + .defined((d) => d.value !== null); this.focusBoundedArea = d3.svg .area() - .x(function(d) { + .x(function (d) { return focusXScale(d.date) || 1; }) - .y0(function(d) { + .y0(function (d) { return focusYScale(d.upper); }) - .y1(function(d) { + .y1(function (d) { return focusYScale(d.lower); }) - .defined(d => d.lower !== null && d.upper !== null); + .defined((d) => d.lower !== null && d.upper !== null); this.contextXScale = d3.time.scale().range([0, vizWidth]); this.contextYScale = d3.scale.linear().range([contextChartHeight, contextChartLineTopMargin]); @@ -274,10 +274,7 @@ class TimeseriesChartIntl extends Component { const fieldFormat = this.fieldFormat; - const svg = chartElement - .append('svg') - .attr('width', svgWidth) - .attr('height', svgHeight); + const svg = chartElement.append('svg').attr('width', svgWidth).attr('height', svgHeight); let contextDataMin; let contextDataMax; @@ -290,11 +287,11 @@ class TimeseriesChartIntl extends Component { ? contextChartData : contextChartData.concat(contextForecastData); - contextDataMin = d3.min(combinedData, d => Math.min(d.value, d.lower)); - contextDataMax = d3.max(combinedData, d => Math.max(d.value, d.upper)); + contextDataMin = d3.min(combinedData, (d) => Math.min(d.value, d.lower)); + contextDataMax = d3.max(combinedData, (d) => Math.max(d.value, d.upper)); } else { - contextDataMin = d3.min(contextChartData, d => d.value); - contextDataMax = d3.max(contextChartData, d => d.value); + contextDataMin = d3.min(contextChartData, (d) => d.value); + contextDataMax = d3.max(contextChartData, (d) => d.value); } // Set the size of the left margin according to the width of the largest y axis tick label. @@ -324,14 +321,14 @@ class TimeseriesChartIntl extends Component { .data(focusYScale.ticks()) .enter() .append('text') - .text(d => { + .text((d) => { if (fieldFormat !== undefined) { return fieldFormat.convert(d, 'text'); } else { return focusYScale.tickFormat()(d); } }) - .each(function() { + .each(function () { maxYAxisLabelWidth = Math.max( this.getBBox().width + focusYAxis.tickPadding(), maxYAxisLabelWidth @@ -359,10 +356,7 @@ class TimeseriesChartIntl extends Component { ); // Mask to hide annotations overflow - const annotationsMask = svg - .append('defs') - .append('mask') - .attr('id', ANNOTATION_MASK_ID); + const annotationsMask = svg.append('defs').append('mask').attr('id', ANNOTATION_MASK_ID); annotationsMask .append('rect') @@ -622,7 +616,7 @@ class TimeseriesChartIntl extends Component { (focusForecastData !== undefined && focusForecastData.length > 0) ) { if (this.fieldFormat !== undefined) { - this.focusYAxis.tickFormat(d => this.fieldFormat.convert(d, 'text')); + this.focusYAxis.tickFormat((d) => this.fieldFormat.convert(d, 'text')); } else { // Use default tick formatter. this.focusYAxis.tickFormat(null); @@ -637,7 +631,7 @@ class TimeseriesChartIntl extends Component { combinedData = data.concat(focusForecastData); } - yMin = d3.min(combinedData, d => { + yMin = d3.min(combinedData, (d) => { let metricValue = d.value; if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { // If an anomaly coincides with a gap in the data, use the anomaly actual value. @@ -653,7 +647,7 @@ class TimeseriesChartIntl extends Component { } return metricValue; }); - yMax = d3.max(combinedData, d => { + yMax = d3.max(combinedData, (d) => { let metricValue = d.value; if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { // If an anomaly coincides with a gap in the data, use the anomaly actual value. @@ -681,7 +675,7 @@ class TimeseriesChartIntl extends Component { // between annotation labels, chart lines and anomalies. if (focusAnnotationData && focusAnnotationData.length > 0) { const levels = getAnnotationLevels(focusAnnotationData); - const maxLevel = d3.max(Object.keys(levels).map(key => levels[key])); + const maxLevel = d3.max(Object.keys(levels).map((key) => levels[key])); // TODO needs revisiting to be a more robust normalization yMax = yMax * (1 + (maxLevel + 1) / 5); } @@ -698,9 +692,11 @@ class TimeseriesChartIntl extends Component { timeBuckets.setBounds(bounds); const xAxisTickFormat = timeBuckets.getScaledDateFormat(); focusChart.select('.x.axis').call( - this.focusXAxis.ticks(numTicksForDateFormat(this.vizWidth), xAxisTickFormat).tickFormat(d => { - return moment(d).format(xAxisTickFormat); - }) + this.focusXAxis + .ticks(numTicksForDateFormat(this.vizWidth), xAxisTickFormat) + .tickFormat((d) => { + return moment(d).format(xAxisTickFormat); + }) ); focusChart.select('.y.axis').call(this.focusYAxis); @@ -740,7 +736,7 @@ class TimeseriesChartIntl extends Component { .selectAll('.metric-value') .data( data.filter( - d => + (d) => (d.value !== null || typeof d.anomalyScore === 'number') && !showMultiBucketAnomalyMarker(d) ) @@ -753,20 +749,20 @@ class TimeseriesChartIntl extends Component { .enter() .append('circle') .attr('r', LINE_CHART_ANOMALY_RADIUS) - .on('mouseover', function(d) { + .on('mouseover', function (d) { showFocusChartTooltip(d, this); }) .on('mouseout', () => this.props.tooltipService.hide()); // Update all dots to new positions. dots - .attr('cx', d => { + .attr('cx', (d) => { return this.focusXScale(d.date); }) - .attr('cy', d => { + .attr('cy', (d) => { return this.focusYScale(d.value); }) - .attr('class', d => { + .attr('class', (d) => { let markerClass = 'metric-value'; if (_.has(d, 'anomalyScore')) { markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore).id}`; @@ -778,7 +774,9 @@ class TimeseriesChartIntl extends Component { const multiBucketMarkers = d3 .select('.focus-chart-markers') .selectAll('.multi-bucket') - .data(data.filter(d => d.anomalyScore !== null && showMultiBucketAnomalyMarker(d) === true)); + .data( + data.filter((d) => d.anomalyScore !== null && showMultiBucketAnomalyMarker(d) === true) + ); // Remove multi-bucket markers that are no longer needed. multiBucketMarkers.exit().remove(); @@ -787,14 +785,8 @@ class TimeseriesChartIntl extends Component { multiBucketMarkers .enter() .append('path') - .attr( - 'd', - d3.svg - .symbol() - .size(MULTI_BUCKET_SYMBOL_SIZE) - .type('cross') - ) - .on('mouseover', function(d) { + .attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross')) + .on('mouseover', function (d) { showFocusChartTooltip(d, this); }) .on('mouseout', () => this.props.tooltipService.hide()); @@ -803,15 +795,15 @@ class TimeseriesChartIntl extends Component { multiBucketMarkers .attr( 'transform', - d => `translate(${this.focusXScale(d.date)}, ${this.focusYScale(d.value)})` + (d) => `translate(${this.focusXScale(d.date)}, ${this.focusYScale(d.value)})` ) - .attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id}`); + .attr('class', (d) => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id}`); // Add rectangular markers for any scheduled events. const scheduledEventMarkers = d3 .select('.focus-chart-markers') .selectAll('.scheduled-event-marker') - .data(data.filter(d => d.scheduledEvents !== undefined)); + .data(data.filter((d) => d.scheduledEvents !== undefined)); // Remove markers that are no longer needed i.e. if number of chart points has decreased. scheduledEventMarkers.exit().remove(); @@ -828,8 +820,8 @@ class TimeseriesChartIntl extends Component { // Update all markers to new positions. scheduledEventMarkers - .attr('x', d => this.focusXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) - .attr('y', d => this.focusYScale(d.value) - 3); + .attr('x', (d) => this.focusXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) + .attr('y', (d) => this.focusYScale(d.value) - 3); // Plot any forecast data in scope. if (focusForecastData !== undefined) { @@ -854,17 +846,17 @@ class TimeseriesChartIntl extends Component { .enter() .append('circle') .attr('r', LINE_CHART_ANOMALY_RADIUS) - .on('mouseover', function(d) { + .on('mouseover', function (d) { showFocusChartTooltip(d, this); }) .on('mouseout', () => this.props.tooltipService.hide()); // Update all dots to new positions. forecastDots - .attr('cx', d => { + .attr('cx', (d) => { return this.focusXScale(d.date); }) - .attr('cy', d => { + .attr('cy', (d) => { return this.focusYScale(d.value); }) .attr('class', 'metric-value') @@ -895,14 +887,14 @@ class TimeseriesChartIntl extends Component { ); const zoomOptions = [{ durationMs: autoZoomDuration, label: 'auto' }]; - _.each(ZOOM_INTERVAL_OPTIONS, option => { + _.each(ZOOM_INTERVAL_OPTIONS, (option) => { if (option.duration.asSeconds() > minSecs && option.duration.asSeconds() < boundsSecs) { zoomOptions.push({ durationMs: option.duration.asMilliseconds(), label: option.label }); } }); xPos += zoomLabel.node().getBBox().width + 4; - _.each(zoomOptions, option => { + _.each(zoomOptions, (option) => { const text = zoomGroup .append('a') .attr('data-ms', option.durationMs) @@ -949,7 +941,7 @@ class TimeseriesChartIntl extends Component { } const chartElement = d3.select(this.rootNode); - chartElement.selectAll('.focus-zoom a').on('click', function() { + chartElement.selectAll('.focus-zoom a').on('click', function () { d3.event.preventDefault(); setZoomInterval(d3.select(this).attr('data-ms')); }); @@ -968,7 +960,7 @@ class TimeseriesChartIntl extends Component { const combinedData = contextForecastData === undefined ? data : data.concat(contextForecastData); const valuesRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE }; - _.each(combinedData, item => { + _.each(combinedData, (item) => { valuesRange.min = Math.min(item.value, valuesRange.min); valuesRange.max = Math.max(item.value, valuesRange.max); }); @@ -981,7 +973,7 @@ class TimeseriesChartIntl extends Component { (contextForecastData !== undefined && contextForecastData.length > 0) ) { const boundsRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE }; - _.each(combinedData, item => { + _.each(combinedData, (item) => { boundsRange.min = Math.min(item.lower, boundsRange.min); boundsRange.max = Math.max(item.upper, boundsRange.max); }); @@ -1034,7 +1026,7 @@ class TimeseriesChartIntl extends Component { .outerTickSize(0) .tickPadding(0) .ticks(numTicksForDateFormat(cxtWidth, xAxisTickFormat)) - .tickFormat(d => { + .tickFormat((d) => { return moment(d).format(xAxisTickFormat); }); @@ -1042,16 +1034,16 @@ class TimeseriesChartIntl extends Component { const contextBoundsArea = d3.svg .area() - .x(d => { + .x((d) => { return this.contextXScale(d.date); }) - .y0(d => { + .y0((d) => { return this.contextYScale(Math.min(chartLimits.max, Math.max(d.lower, chartLimits.min))); }) - .y1(d => { + .y1((d) => { return this.contextYScale(Math.max(chartLimits.min, Math.min(d.upper, chartLimits.max))); }) - .defined(d => d.lower !== null && d.upper !== null); + .defined((d) => d.lower !== null && d.upper !== null); if (modelPlotEnabled === true) { cxtGroup @@ -1063,19 +1055,15 @@ class TimeseriesChartIntl extends Component { const contextValuesLine = d3.svg .line() - .x(d => { + .x((d) => { return this.contextXScale(d.date); }) - .y(d => { + .y((d) => { return this.contextYScale(d.value); }) - .defined(d => d.value !== null); + .defined((d) => d.value !== null); - cxtGroup - .append('path') - .datum(data) - .attr('class', 'values-line') - .attr('d', contextValuesLine); + cxtGroup.append('path').datum(data).attr('class', 'values-line').attr('d', contextValuesLine); drawLineChartDots(data, cxtGroup, contextValuesLine, 1); // Create the path elements for the forecast value line and bounds area. @@ -1107,10 +1095,7 @@ class TimeseriesChartIntl extends Component { .y(this.contextYScale); // Draw the x axis on top of the mask so that the labels are visible. - cxtGroup - .append('g') - .attr('class', 'x axis context-chart-axis') - .call(xAxis); + cxtGroup.append('g').attr('class', 'x axis context-chart-axis').call(xAxis); // Move the x axis labels up so that they are inside the contact chart area. cxtGroup.selectAll('.x.context-chart-axis text').attr('dy', cxtChartHeight - 5); @@ -1120,7 +1105,7 @@ class TimeseriesChartIntl extends Component { this.drawContextBrush(cxtGroup); } - drawContextBrush = contextGroup => { + drawContextBrush = (contextGroup) => { const { contextChartSelected } = this.props; const brush = this.brush; @@ -1128,10 +1113,7 @@ class TimeseriesChartIntl extends Component { const mask = this.mask; // Create the brush for zooming in to the focus area of interest. - brush - .x(contextXScale) - .on('brush', brushing) - .on('brushend', brushed); + brush.x(contextXScale).on('brush', brushing).on('brushend', brushed); contextGroup .append('g') @@ -1143,15 +1125,9 @@ class TimeseriesChartIntl extends Component { // move the left and right resize areas over to // be under the handles - contextGroup - .selectAll('.w rect') - .attr('x', -10) - .attr('width', 10); + contextGroup.selectAll('.w rect').attr('x', -10).attr('width', 10); - contextGroup - .selectAll('.e rect') - .attr('x', 0) - .attr('width', 10); + contextGroup.selectAll('.e rect').attr('x', 0).attr('width', 10); const handleBrushExtent = brush.extent(); @@ -1219,7 +1195,7 @@ class TimeseriesChartIntl extends Component { } // Set the color of the swimlane cells according to whether they are inside the selection. - contextGroup.selectAll('.swimlane-cell').style('fill', d => { + contextGroup.selectAll('.swimlane-cell').style('fill', (d) => { const cellMs = d.date.getTime(); if (cellMs < selectionMin || cellMs > selectionMax) { return anomalyGrayScale(d.score); @@ -1248,15 +1224,9 @@ class TimeseriesChartIntl extends Component { // Need to use the min(earliest) and max(earliest) of the context chart // aggregation to align the axes of the chart and swimlane elements. const xAxisDomain = this.calculateContextXAxisDomain(); - const x = d3.time - .scale() - .range([0, swlWidth]) - .domain(xAxisDomain); + const x = d3.time.scale().range([0, swlWidth]).domain(xAxisDomain); - const y = d3.scale - .linear() - .range([swlHeight, 0]) - .domain([0, swlHeight]); + const y = d3.scale.linear().range([swlHeight, 0]).domain([0, swlHeight]); const xAxis = d3.svg .axis() @@ -1281,10 +1251,7 @@ class TimeseriesChartIntl extends Component { .attr('transform', 'translate(0,' + swlHeight + ')') .call(xAxis); - axes - .append('g') - .attr('class', 'y axis') - .call(yAxis); + axes.append('g').attr('class', 'y axis').call(yAxis); const earliest = xAxisDomain[0].getTime(); const latest = xAxisDomain[1].getTime(); @@ -1294,27 +1261,23 @@ class TimeseriesChartIntl extends Component { cellWidth = 1; } - const cells = swlGroup - .append('g') - .attr('class', 'swimlane-cells') - .selectAll('rect') - .data(data); + const cells = swlGroup.append('g').attr('class', 'swimlane-cells').selectAll('rect').data(data); cells .enter() .append('rect') - .attr('x', d => { + .attr('x', (d) => { return x(d.date); }) .attr('y', 0) .attr('rx', 0) .attr('ry', 0) - .attr('class', d => { + .attr('class', (d) => { return d.score > 0 ? 'swimlane-cell' : 'swimlane-cell-hidden'; }) .attr('width', cellWidth) .attr('height', swlHeight) - .style('fill', d => { + .style('fill', (d) => { return anomalyColorScale(d.score); }); }; @@ -1675,28 +1638,24 @@ class TimeseriesChartIntl extends Component { selectedMarker .enter() .append('path') - .attr( - 'd', - d3.svg - .symbol() - .size(MULTI_BUCKET_SYMBOL_SIZE) - .type('cross') - ) - .attr('transform', d => `translate(${focusXScale(d.date)}, ${focusYScale(d.value)})`) + .attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross')) + .attr('transform', (d) => `translate(${focusXScale(d.date)}, ${focusYScale(d.value)})`) .attr( 'class', - d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id} highlighted` + (d) => + `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id} highlighted` ); } else { selectedMarker .enter() .append('circle') .attr('r', LINE_CHART_ANOMALY_RADIUS) - .attr('cx', d => focusXScale(d.date)) - .attr('cy', d => focusYScale(d.value)) + .attr('cx', (d) => focusXScale(d.date)) + .attr('cy', (d) => focusYScale(d.value)) .attr( 'class', - d => `anomaly-marker metric-value ${getSeverityWithLow(d.anomalyScore).id} highlighted` + (d) => + `anomaly-marker metric-value ${getSeverityWithLow(d.anomalyScore).id} highlighted` ); } @@ -1713,9 +1672,7 @@ class TimeseriesChartIntl extends Component { } unhighlightFocusChartAnomaly() { - d3.select('.focus-chart-markers') - .selectAll('.anomaly-marker.highlighted') - .remove(); + d3.select('.focus-chart-markers').selectAll('.anomaly-marker.highlighted').remove(); this.props.tooltipService.hide(); } @@ -1732,7 +1689,7 @@ class TimeseriesChartIntl extends Component { } } -export const TimeseriesChart = props => { +export const TimeseriesChart = (props) => { const annotationProp = useObservable(annotation$); if (annotationProp === undefined) { return null; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js index 784ab102fd8ca..2a0fcd57467bd 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js @@ -13,7 +13,7 @@ import React from 'react'; import { TimeseriesChart } from './timeseries_chart'; jest.mock('../../../util/time_buckets', () => ({ - TimeBuckets: function() { + TimeBuckets: function () { this.setBounds = jest.fn(); this.setInterval = jest.fn(); this.getScaledDateFormat = jest.fn(); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts index f23c52fc7119a..0b541d54ee7b3 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts @@ -22,10 +22,7 @@ export const ANNOTATION_MASK_ID = 'mlAnnotationMask'; export function getAnnotationBrush(this: TimeseriesChart) { const focusXScale = this.focusXScale; - const annotateBrush = d3.svg - .brush() - .x(focusXScale) - .on('brushend', brushend.bind(this)); + const annotateBrush = d3.svg.brush().x(focusXScale).on('brushend', brushend.bind(this)); // cast a reference to this so we get the latest state when brushend() gets called function brushend(this: TimeseriesChart) { @@ -114,7 +111,7 @@ export function renderAnnotations( const upperTextMargin = ANNOTATION_UPPER_TEXT_MARGIN; const durations: Dictionary = {}; - focusAnnotationData.forEach(d => { + focusAnnotationData.forEach((d) => { if (d.key !== undefined) { const duration = (d.end_timestamp || 0) - d.timestamp; durations[d.key] = duration; @@ -137,10 +134,7 @@ export function renderAnnotations( .selectAll('g.mlAnnotation') .data(focusAnnotationData || [], (d: Annotation) => d._id || ''); - annotations - .enter() - .append('g') - .classed('mlAnnotation', true); + annotations.enter().append('g').classed('mlAnnotation', true); const rects = annotations.selectAll('.mlAnnotationRect').data((d: Annotation) => [d]); @@ -151,7 +145,7 @@ export function renderAnnotations( .attr('ry', ANNOTATION_RECT_BORDER_RADIUS) .classed('mlAnnotationRect', true) .attr('mask', `url(#${ANNOTATION_MASK_ID})`) - .on('mouseover', function(this: object, d: Annotation) { + .on('mouseover', function (this: object, d: Annotation) { showFocusChartTooltip(d, this); }) .on('mouseout', () => hideFocusChartTooltip()) @@ -189,8 +183,8 @@ export function renderAnnotations( rects.exit().remove(); - const textRects = annotations.selectAll('.mlAnnotationTextRect').data(d => [d]); - const texts = annotations.selectAll('.mlAnnotationText').data(d => [d]); + const textRects = annotations.selectAll('.mlAnnotationTextRect').data((d) => [d]); + const texts = annotations.selectAll('.mlAnnotationText').data((d) => [d]); textRects .enter() @@ -201,10 +195,7 @@ export function renderAnnotations( .attr('rx', ANNOTATION_RECT_BORDER_RADIUS) .attr('ry', ANNOTATION_RECT_BORDER_RADIUS); - texts - .enter() - .append('text') - .classed('mlAnnotationText', true); + texts.enter().append('text').classed('mlAnnotationText', true); function labelXOffset(ts: number) { const earliestMs = focusXScale.domain()[0]; @@ -271,7 +262,7 @@ export function getAnnotationWidth( export function highlightFocusChartAnnotation(annotation: Annotation) { const annotations = d3.selectAll('.mlAnnotation'); - annotations.each(function(d) { + annotations.each(function (d) { // @ts-ignore const element = d3.select(this); @@ -288,7 +279,7 @@ export function highlightFocusChartAnnotation(annotation: Annotation) { export function unhighlightFocusChartAnnotation() { const annotations = d3.selectAll('.mlAnnotation'); - annotations.each(function() { + annotations.each(function () { // @ts-ignore const element = d3.select(this); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts index 4c57eda65a9da..ce5a7565c519b 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts @@ -96,7 +96,7 @@ function getMetricData( interval ) .pipe( - map(resp => { + map((resp) => { _.each(resp.results, (value, time) => { // @ts-ignore obj.results[time] = { @@ -134,7 +134,7 @@ function getChartDetails( } obj.results.functionLabel = functionLabel; - const blankEntityFields = _.filter(entityFields, entity => { + const blankEntityFields = _.filter(entityFields, (entity) => { return entity.fieldValue === null; }); @@ -155,7 +155,7 @@ function getChartDetails( latestMs, }) .then((results: any) => { - _.each(blankEntityFields, field => { + _.each(blankEntityFields, (field) => { // results will not contain keys for non-aggregatable fields, // so store as 0 to indicate over all field values. obj.results.entityData.entities.push({ diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 4e04a63640a87..34da6b8ef6af6 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -95,7 +95,7 @@ function getEntityControlOptions(fieldValues) { fieldValues.sort(); - return fieldValues.map(value => { + return fieldValues.map((value) => { return { label: value === '' ? EMPTY_FIELD_VALUE_LABEL : value, value }; }); } @@ -206,7 +206,7 @@ export class TimeSeriesExplorer extends React.Component { return fieldNamesWithEmptyValues.length === 0; }; - detectorIndexChangeHandler = e => { + detectorIndexChangeHandler = (e) => { const { appStateHandler } = this.props; const id = e.target.value; if (id !== undefined) { @@ -215,13 +215,13 @@ export class TimeSeriesExplorer extends React.Component { }; toggleShowAnnotationsHandler = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ showAnnotations: !prevState.showAnnotations, })); }; toggleShowForecastHandler = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ showForecast: !prevState.showForecast, })); }; @@ -304,14 +304,14 @@ export class TimeSeriesExplorer extends React.Component { focusAggregationInterval, selectedForecastId, modelPlotEnabled, - entityControls.filter(entity => entity.fieldValue !== null), + entityControls.filter((entity) => entity.fieldValue !== null), searchBounds, selectedJob, TIME_FIELD_NAME ); } - contextChartSelected = selection => { + contextChartSelected = (selection) => { const zoomState = { from: selection.from.toISOString(), to: selection.to.toISOString(), @@ -375,10 +375,10 @@ export class TimeSeriesExplorer extends React.Component { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE ) .pipe( - map(resp => { + map((resp) => { const anomalies = resp.anomalies; const detectorsByJob = mlJobService.detectorsByJob; - anomalies.forEach(anomaly => { + anomalies.forEach((anomaly) => { // Add a detector property to each anomaly. // Default to functionDescription if no description available. // TODO - when job_service is moved server_side, move this to server endpoint. @@ -450,7 +450,7 @@ export class TimeSeriesExplorer extends React.Component { .toPromise(); const entityValues = {}; - entities.forEach(entity => { + entities.forEach((entity) => { let fieldValues; if (partitionField?.name === entity.fieldName) { @@ -468,7 +468,7 @@ export class TimeSeriesExplorer extends React.Component { this.setState({ entitiesLoading: false, entityValues }); }; - setForecastId = forecastId => { + setForecastId = (forecastId) => { this.props.appStateHandler(APP_STATE_ACTION.SET_FORECAST_ID, forecastId); }; @@ -528,7 +528,7 @@ export class TimeSeriesExplorer extends React.Component { // finish() function, called after each data set has been loaded and processed. // The last one to call it will trigger the page render. - const finish = counterVar => { + const finish = (counterVar) => { awaitingCount--; if (awaitingCount === 0 && counterVar === loadCounter) { stateUpdate.hasResults = @@ -575,7 +575,7 @@ export class TimeSeriesExplorer extends React.Component { } }; - const nonBlankEntities = entityControls.filter(entity => { + const nonBlankEntities = entityControls.filter((entity) => { return entity.fieldValue !== null; }); @@ -626,12 +626,12 @@ export class TimeSeriesExplorer extends React.Component { stateUpdate.contextAggregationInterval.expression ) .toPromise() - .then(resp => { + .then((resp) => { const fullRangeChartData = processMetricPlotResults(resp.results, modelPlotEnabled); stateUpdate.contextChartData = fullRangeChartData; finish(counter); }) - .catch(resp => { + .catch((resp) => { console.log( 'Time series explorer - error getting metric data from elasticsearch:', resp @@ -648,12 +648,12 @@ export class TimeSeriesExplorer extends React.Component { searchBounds.max.valueOf(), stateUpdate.contextAggregationInterval.expression ) - .then(resp => { + .then((resp) => { const fullRangeRecordScoreData = processRecordScoreResults(resp.results); stateUpdate.swimlaneData = fullRangeRecordScoreData; finish(counter); }) - .catch(resp => { + .catch((resp) => { console.log( 'Time series explorer - error getting bucket anomaly scores from elasticsearch:', resp @@ -669,11 +669,11 @@ export class TimeSeriesExplorer extends React.Component { searchBounds.min.valueOf(), searchBounds.max.valueOf() ) - .then(resp => { + .then((resp) => { stateUpdate.chartDetails = resp.results; finish(counter); }) - .catch(resp => { + .catch((resp) => { console.log( 'Time series explorer - error getting entity counts from elasticsearch:', resp @@ -701,11 +701,11 @@ export class TimeSeriesExplorer extends React.Component { aggType ) .toPromise() - .then(resp => { + .then((resp) => { stateUpdate.contextForecastData = processForecastResults(resp.results); finish(counter); }) - .catch(resp => { + .catch((resp) => { console.log( `Time series explorer - error loading data for forecast ID ${selectedForecastId}`, resp @@ -775,7 +775,7 @@ export class TimeSeriesExplorer extends React.Component { */ getCriteriaFields(detectorIndex, entities) { // Only filter on the entity if the field has a value. - const nonBlankEntities = entities.filter(entity => entity.fieldValue !== null); + const nonBlankEntities = entities.filter((entity) => entity.fieldValue !== null); return [ { fieldName: 'detector_index', @@ -822,7 +822,7 @@ export class TimeSeriesExplorer extends React.Component { } // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. - mlFieldFormatService.populateFormats([jobId]).catch(err => { + mlFieldFormatService.populateFormats([jobId]).catch((err) => { console.log('Error populating field formats:', err); }); } @@ -839,14 +839,14 @@ export class TimeSeriesExplorer extends React.Component { this.subscriptions.add( this.contextChart$ .pipe( - tap(selection => { + tap((selection) => { this.setState({ zoomFrom: selection.from, zoomTo: selection.to, }); }), debounceTime(500), - tap(selection => { + tap((selection) => { const { contextChartData, contextForecastData, @@ -875,7 +875,7 @@ export class TimeSeriesExplorer extends React.Component { }); } }), - switchMap(selection => { + switchMap((selection) => { const { selectedJobId } = this.props; const jobs = createTimeSeriesJobData(mlJobService.jobs); const selectedJob = mlJobService.getJob(selectedJobId); @@ -987,7 +987,7 @@ export class TimeSeriesExplorer extends React.Component { const tableControlsListener = () => { const { zoomFrom, zoomTo } = this.state; if (zoomFrom !== undefined && zoomTo !== undefined) { - this.loadAnomaliesTableData(zoomFrom.getTime(), zoomTo.getTime()).subscribe(res => + this.loadAnomaliesTableData(zoomFrom.getTime(), zoomTo.getTime()).subscribe((res) => this.setState(res) ); } @@ -1076,7 +1076,7 @@ export class TimeSeriesExplorer extends React.Component { const fieldNamesWithEmptyValues = this.getFieldNamesWithEmptyValues(); const arePartitioningFieldsProvided = this.arePartitioningFieldsProvided(); - const detectorSelectOptions = getViewableDetectors(selectedJob).map(d => ({ + const detectorSelectOptions = getViewableDetectors(selectedJob).map((d) => ({ value: d.index, text: d.detector_description, })); @@ -1148,7 +1148,7 @@ export class TimeSeriesExplorer extends React.Component { />
    - {entityControls.map(entity => { + {entityControls.map((entity) => { const entityKey = `${entity.fieldName}`; const forceSelection = !hasEmptyFieldValues && entity.fieldValue === null; hasEmptyFieldValues = !hasEmptyFieldValues && forceSelection; @@ -1219,7 +1219,7 @@ export class TimeSeriesExplorer extends React.Component { {chartDetails.entityData.entities.length > 0 && '('} {chartDetails.entityData.entities - .map(entity => { + .map((entity) => { return `${entity.fieldName}: ${entity.fieldValue}`; }) .join(', ')} @@ -1300,7 +1300,7 @@ export class TimeSeriesExplorer extends React.Component {
    - {tooltipService => ( + {(tooltipService) => ( { + return singleTimeSeriesJobs.map((job) => { const bucketSpan = parseInterval(job.analysis_config.bucket_span); return { id: job.job_id, @@ -110,7 +110,7 @@ export function processDataForFocusAnomalies( if (chartData !== undefined && chartData.length > 0) { lastChartDataPointTime = chartData[chartData.length - 1].date.getTime(); } - anomalyRecords.forEach(record => { + anomalyRecords.forEach((record) => { const recordTime = record[TIME_FIELD_NAME]; const chartPoint = findChartPointForAnomalyTime(chartData, recordTime, aggregationInterval); if (chartPoint === undefined) { @@ -123,7 +123,7 @@ export function processDataForFocusAnomalies( timesToAddPointsFor.sort((a, b) => a - b); - timesToAddPointsFor.forEach(time => { + timesToAddPointsFor.forEach((time) => { const pointToAdd = { date: new Date(time), value: null, @@ -138,7 +138,7 @@ export function processDataForFocusAnomalies( // Iterate through the anomaly records adding the // various properties required for display. - anomalyRecords.forEach(record => { + anomalyRecords.forEach((record) => { // Look for a chart point with the same time as the record. // If none found, find closest time in chartData set. const recordTime = record[TIME_FIELD_NAME]; diff --git a/x-pack/plugins/ml/public/application/util/calc_auto_interval.js b/x-pack/plugins/ml/public/application/util/calc_auto_interval.js index 29064db6689d0..c0a001b968970 100644 --- a/x-pack/plugins/ml/public/application/util/calc_auto_interval.js +++ b/x-pack/plugins/ml/public/application/util/calc_auto_interval.js @@ -81,7 +81,7 @@ export function timeBucketsCalcAutoIntervalProvider() { return moment.duration(ms, 'ms'); } - return function(buckets, duration) { + return function (buckets, duration) { const interval = pick(buckets, duration); if (interval) { return moment.duration(interval._data); @@ -111,7 +111,7 @@ export function timeBucketsCalcAutoIntervalProvider() { true ), - lessThan: find(revRoundingRules, function(upperBound, lowerBound, target) { + lessThan: find(revRoundingRules, function (upperBound, lowerBound, target) { // upperBound - first duration in rule // lowerBound - second duration in rule // target - target interval in milliseconds. Must not return intervals less than this duration. diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.js b/x-pack/plugins/ml/public/application/util/chart_utils.js index 5a062320ca6c5..4ec7c5cb6d819 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.js @@ -21,7 +21,7 @@ export const SCHEDULED_EVENT_SYMBOL_HEIGHT = 5; const MAX_LABEL_WIDTH = 100; export function chartLimits(data = []) { - const domain = d3.extent(data, d => { + const domain = d3.extent(data, (d) => { let metricValue = d.value; if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { // If an anomaly coincides with a gap in the data, use the anomaly actual value. @@ -32,7 +32,7 @@ export function chartLimits(data = []) { const limits = { max: domain[1], min: domain[0] }; if (limits.max === limits.min) { - limits.max = d3.max(data, d => { + limits.max = d3.max(data, (d) => { if (d.typical) { return Math.max(d.value, d.typical); } else { @@ -42,7 +42,7 @@ export function chartLimits(data = []) { return d.value; } }); - limits.min = d3.min(data, d => { + limits.min = d3.min(data, (d) => { if (d.typical) { return Math.min(d.value, d.typical); } else { @@ -65,6 +65,44 @@ export function chartLimits(data = []) { return limits; } +export function chartExtendedLimits(data = [], functionDescription) { + let _min = Infinity; + let _max = -Infinity; + data.forEach((d) => { + let metricValue = d.value; + const actualValue = Array.isArray(d.actual) ? d.actual[0] : d.actual; + const typicalValue = Array.isArray(d.typical) ? d.typical[0] : d.typical; + + if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { + // If an anomaly coincides with a gap in the data, use the anomaly actual value. + metricValue = actualValue; + } + + if (d.anomalyScore !== undefined) { + _min = Math.min(_min, metricValue, actualValue, typicalValue); + _max = Math.max(_max, metricValue, actualValue, typicalValue); + } else { + _min = Math.min(_min, metricValue); + _max = Math.max(_max, metricValue); + } + }); + const limits = { max: _max, min: _min }; + + // add padding of 5% of the difference between max and min + // if we ended up with the same value for both of them + if (limits.max === limits.min) { + const padding = limits.max * 0.05; + limits.max += padding; + limits.min -= padding; + } + + // makes sure the domain starts at 0 if the aggregation is by count + // since the number should always be positive + if (functionDescription === 'count' && limits.min < 0) { + limits.min = 0; + } + return limits; +} export function drawLineChartDots(data, lineChartGroup, lineChartValuesLine, radius = 1.5) { // We need to do this because when creating a line for a chart which has data gaps, // if there are single datapoints without any valid data before and after them, @@ -96,10 +134,7 @@ export function drawLineChartDots(data, lineChartGroup, lineChartValuesLine, rad // use d3's enter/update/exit pattern to render the dots const dots = dotGroup.selectAll('circle').data(dotsData); - dots - .enter() - .append('circle') - .attr('r', radius); + dots.enter().append('circle').attr('r', radius); dots.attr('cx', lineChartValuesLine.x()).attr('cy', lineChartValuesLine.y()); @@ -118,7 +153,7 @@ export function filterAxisLabels(selection, chartWidth) { .selectAll('.tick text') // don't refactor this to an arrow function because // we depend on using `this` here. - .text(function() { + .text(function () { const parent = d3.select(this.parentNode); const labelWidth = parent.node().getBBox().width; const labelXPos = d3.transform(parent.attr('transform')).translate[0]; @@ -142,13 +177,13 @@ export function getChartType(config) { if ( EVENT_DISTRIBUTION_ENABLED && config.functionDescription === 'rare' && - config.entityFields.some(f => f.fieldType === 'over') === false + config.entityFields.some((f) => f.fieldType === 'over') === false ) { chartType = CHART_TYPE.EVENT_DISTRIBUTION; } else if ( POPULATION_DISTRIBUTION_ENABLED && config.functionDescription !== 'rare' && - config.entityFields.some(f => f.fieldType === 'over') && + config.entityFields.some((f) => f.fieldType === 'over') && config.metricFunction !== null // Event distribution chart relies on the ML function mapping to an ES aggregation ) { chartType = CHART_TYPE.POPULATION_DISTRIBUTION; @@ -161,12 +196,12 @@ export function getChartType(config) { // Check that the config does not use script fields defined in the datafeed config. if (config.datafeedConfig !== undefined && config.datafeedConfig.script_fields !== undefined) { const scriptFields = Object.keys(config.datafeedConfig.script_fields); - const checkFields = config.entityFields.map(entity => entity.fieldName); + const checkFields = config.entityFields.map((entity) => entity.fieldName); if (config.metricFieldName) { checkFields.push(config.metricFieldName); } const usesScriptFields = - checkFields.find(fieldName => scriptFields.includes(fieldName)) !== undefined; + checkFields.find((fieldName) => scriptFields.includes(fieldName)) !== undefined; if (usesScriptFields === true) { // Only single metric chart type supports query of model plot data. chartType = CHART_TYPE.SINGLE_METRIC; @@ -193,7 +228,7 @@ export function getExploreSeriesLink(series) { // Initially pass them in the mlTimeSeriesExplorer part of the AppState. // TODO - do we want to pass the entities via the filter? const entityCondition = {}; - series.entityFields.forEach(entity => { + series.entityFields.forEach((entity) => { entityCondition[entity.fieldName] = entity.fieldValue; }); @@ -310,7 +345,8 @@ const LABEL_WRAP_THRESHOLD = 60; // and entity fields) is above LABEL_WRAP_THRESHOLD. export function isLabelLengthAboveThreshold({ detectorLabel, entityFields }) { const labelLength = - detectorLabel.length + entityFields.map(d => `${d.fieldName} ${d.fieldValue}`).join(' ').length; + detectorLabel.length + + entityFields.map((d) => `${d.fieldName} ${d.fieldValue}`).join(' ').length; return labelLength > LABEL_WRAP_THRESHOLD; } @@ -335,13 +371,10 @@ export function getXTransform(t) { export function removeLabelOverlap(axis, startTimeMs, tickInterval, width) { // Put emphasis on all tick lines, will again de-emphasize the // ones where we remove the label in the next steps. - axis - .selectAll('g.tick') - .select('line') - .classed('ml-tick-emphasis', true); + axis.selectAll('g.tick').select('line').classed('ml-tick-emphasis', true); function getNeighborTickFactory(operator) { - return function(ts) { + return function (ts) { switch (operator) { case TICK_DIRECTION.PREVIOUS: return ts - tickInterval; @@ -353,8 +386,8 @@ export function removeLabelOverlap(axis, startTimeMs, tickInterval, width) { function getTickDataFactory(operator) { const getNeighborTick = getNeighborTickFactory(operator); - const fn = function(ts) { - const filteredTicks = axis.selectAll('.tick').filter(d => d === ts); + const fn = function (ts) { + const filteredTicks = axis.selectAll('.tick').filter((d) => d === ts); if (filteredTicks.length === 0 || filteredTicks[0].length === 0) { return false; diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.test.js b/x-pack/plugins/ml/public/application/util/chart_utils.test.js index 57aea3c0ab5aa..b7cf11c088a1e 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.test.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.test.js @@ -10,7 +10,7 @@ jest.mock('./dependency_cache', () => { const dateMath = require('@elastic/datemath'); let _time = undefined; const timefilter = { - setTime: time => { + setTime: (time) => { _time = time; }, getActiveBounds: () => { @@ -436,15 +436,12 @@ describe('ML - chart utils', () => { .innerTickSize(-chartHeight) .outerTickSize(0) .tickPadding(10) - .tickFormat(d => moment(d).format(xAxisTickFormat)); + .tickFormat((d) => moment(d).format(xAxisTickFormat)); const tickValues = getTickValues(startTimeMs, interval, plotEarliest, plotLatest); xAxis.tickValues(tickValues); - const svg = chartElement - .append('svg') - .attr('width', svgWidth) - .attr('height', svgHeight); + const svg = chartElement.append('svg').attr('width', svgWidth).attr('height', svgHeight); const axes = svg.append('g'); diff --git a/x-pack/plugins/ml/public/application/util/custom_url_utils.ts b/x-pack/plugins/ml/public/application/util/custom_url_utils.ts index 47f7b6f4d4fd2..20bb1c7f60597 100644 --- a/x-pack/plugins/ml/public/application/util/custom_url_utils.ts +++ b/x-pack/plugins/ml/public/application/util/custom_url_utils.ts @@ -170,7 +170,7 @@ function buildKibanaUrl(urlConfig: UrlConfig, record: CustomUrlAnomalyRecordDoc) // Split query string by AND operator. .split(/\sand\s/i) // Get property name from `influencerField:$influencerField$` string. - .map(v => v.split(':')[0]); + .map((v) => v.split(':')[0]); const queryParts: string[] = []; const joinOperator = ' AND '; @@ -226,7 +226,7 @@ export function isValidLabel(label: string, savedCustomUrls: any[]) { let isValid = label !== undefined && label.trim().length > 0; if (isValid === true && savedCustomUrls !== undefined) { // Check the label is unique. - const existingLabels = savedCustomUrls.map(customUrl => customUrl.url_name); + const existingLabels = savedCustomUrls.map((customUrl) => customUrl.url_name); isValid = !existingLabels.includes(label); } return isValid; diff --git a/x-pack/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/plugins/ml/public/application/util/dependency_cache.ts index 356da38d5ad08..2586dfe45345e 100644 --- a/x-pack/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/plugins/ml/public/application/util/dependency_cache.ts @@ -204,7 +204,7 @@ export function getGetUrlGenerator() { export function clearCache() { console.log('clearing dependency cache'); // eslint-disable-line no-console - Object.keys(cache).forEach(k => { + Object.keys(cache).forEach((k) => { cache[k as keyof DependencyCache] = null; }); } diff --git a/x-pack/plugins/ml/public/application/util/error_utils.ts b/x-pack/plugins/ml/public/application/util/error_utils.ts new file mode 100644 index 0000000000000..2ce8f4ffc583a --- /dev/null +++ b/x-pack/plugins/ml/public/application/util/error_utils.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; + +export const extractErrorMessage = ( + error: CustomHttpResponseOptions | undefined | string +): string | undefined => { + if (typeof error === 'string') { + return error; + } + + if (error?.body) { + if (typeof error.body === 'string') { + return error.body; + } + if (typeof error.body === 'object' && 'message' in error.body) { + if (typeof error.body.message === 'string') { + return error.body.message; + } + // @ts-ignore + if (typeof (error.body.message?.msg === 'string')) { + // @ts-ignore + return error.body.message?.msg; + } + } + } + return undefined; +}; diff --git a/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts b/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts index 6052cd0dfaa21..4275142d63d56 100644 --- a/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts +++ b/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts @@ -68,7 +68,7 @@ describe('ML - field type utils', () => { const mlKeys = Object.keys(ML_JOB_FIELD_TYPES); const receivedMlLabels: Record = {}; const testStorage = mlJobTypeAriaLabels; - mlKeys.forEach(constant => { + mlKeys.forEach((constant) => { receivedMlLabels[constant] = getMLJobTypeAriaLabel( ML_JOB_FIELD_TYPES[constant as keyof typeof ML_JOB_FIELD_TYPES] ); diff --git a/x-pack/plugins/ml/public/application/util/field_types_utils.ts b/x-pack/plugins/ml/public/application/util/field_types_utils.ts index d6e0a885269e8..8cf2661d7b74d 100644 --- a/x-pack/plugins/ml/public/application/util/field_types_utils.ts +++ b/x-pack/plugins/ml/public/application/util/field_types_utils.ts @@ -73,7 +73,7 @@ export const mlJobTypeAriaLabels = { export const getMLJobTypeAriaLabel = (type: string) => { const requestedFieldType = Object.keys(ML_JOB_FIELD_TYPES).find( - k => ML_JOB_FIELD_TYPES[k as keyof typeof ML_JOB_FIELD_TYPES] === type + (k) => ML_JOB_FIELD_TYPES[k as keyof typeof ML_JOB_FIELD_TYPES] === type ); if (requestedFieldType === undefined) { return null; diff --git a/x-pack/plugins/ml/public/application/util/index_utils.ts b/x-pack/plugins/ml/public/application/util/index_utils.ts index b8cf2e6fa8e96..192552b25d15a 100644 --- a/x-pack/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/plugins/ml/public/application/util/index_utils.ts @@ -28,7 +28,7 @@ export function loadIndexPatterns(indexPatterns: IndexPatternsContract) { fields: ['id', 'title', 'type', 'fields'], perPage: 10000, }) - .then(response => { + .then((response) => { indexPatternCache = response.savedObjects; return indexPatternCache; }); @@ -41,7 +41,7 @@ export function loadSavedSearches() { type: 'search', perPage: 10000, }) - .then(response => { + .then((response) => { savedSearchesCache = response.savedObjects; return savedSearchesCache; }); @@ -62,7 +62,7 @@ export function getIndexPatternsContract() { } export function getIndexPatternNames() { - return indexPatternCache.map(i => i.attributes && i.attributes.title); + return indexPatternCache.map((i) => i.attributes && i.attributes.title); } export function getIndexPatternIdFromName(name: string) { @@ -88,7 +88,7 @@ export async function getIndexPatternAndSavedSearch(savedSearchId: string) { if (ss === null) { return resp; } - const indexPatternId = ss.references.find(r => r.type === 'index-pattern')?.id; + const indexPatternId = ss.references.find((r) => r.type === 'index-pattern')?.id; resp.indexPattern = await getIndexPatternById(indexPatternId!); resp.savedSearch = ss; return resp; @@ -111,7 +111,7 @@ export function getIndexPatternById(id: string): Promise { } export function getSavedSearchById(id: string): SavedSearchSavedObject | undefined { - return savedSearchesCache.find(s => s.id === id); + return savedSearchesCache.find((s) => s.id === id); } /** diff --git a/x-pack/plugins/ml/public/application/util/string_utils.js b/x-pack/plugins/ml/public/application/util/string_utils.js index 66835984df5e5..450c166f90300 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.js +++ b/x-pack/plugins/ml/public/application/util/string_utils.js @@ -83,7 +83,7 @@ function quoteField(field) { // re-order an object based on the value of the keys export function sortByKey(list, reverse, comparator) { - let keys = _.sortBy(_.keys(list), key => { + let keys = _.sortBy(_.keys(list), (key) => { return comparator ? comparator(list[key], key) : key; }); @@ -93,7 +93,7 @@ export function sortByKey(list, reverse, comparator) { return _.object( keys, - _.map(keys, key => { + _.map(keys, (key) => { return list[key]; }) ); @@ -121,7 +121,7 @@ export function mlEscape(str) { "'": ''', '/': '/', }; - return String(str).replace(/[&<>"'\/]/g, s => entityMap[s]); + return String(str).replace(/[&<>"'\/]/g, (s) => entityMap[s]); } // Escapes reserved characters for use in Elasticsearch query terms. diff --git a/x-pack/plugins/ml/public/application/util/string_utils.test.ts b/x-pack/plugins/ml/public/application/util/string_utils.test.ts index d940fce2ee1d5..25f1cbd3abac3 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.test.ts +++ b/x-pack/plugins/ml/public/application/util/string_utils.test.ts @@ -102,7 +102,7 @@ describe('ML - string utils', () => { elephant: 'trunk', }; - const valueComparator = function(value: string) { + const valueComparator = function (value: string) { return value; }; diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.d.ts b/x-pack/plugins/ml/public/application/util/time_buckets.d.ts index 9c5d663a6e4ab..91661ea3fd3fa 100644 --- a/x-pack/plugins/ml/public/application/util/time_buckets.d.ts +++ b/x-pack/plugins/ml/public/application/util/time_buckets.d.ts @@ -5,6 +5,7 @@ */ import { Moment } from 'moment'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; export interface TimeRangeBounds { min?: Moment; diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.js b/x-pack/plugins/ml/public/application/util/time_buckets.js index ccc51982678b7..1915a4ce6516b 100644 --- a/x-pack/plugins/ml/public/application/util/time_buckets.js +++ b/x-pack/plugins/ml/public/application/util/time_buckets.js @@ -11,7 +11,7 @@ import dateMath from '@elastic/datemath'; import { timeBucketsCalcAutoIntervalProvider } from './calc_auto_interval'; import { parseInterval } from '../../../common/util/parse_interval'; import { getFieldFormats, getUiSettings } from './dependency_cache'; -import { FIELD_FORMAT_IDS } from '../../../../../../src/plugins/data/public'; +import { FIELD_FORMAT_IDS, UI_SETTINGS } from '../../../../../../src/plugins/data/public'; const unitsDesc = dateMath.unitsDesc; const largeMax = unitsDesc.indexOf('w'); // Multiple units of week or longer converted to days for ES intervals. @@ -21,8 +21,8 @@ const calcAuto = timeBucketsCalcAutoIntervalProvider(); export function getTimeBucketsFromCache() { const uiSettings = getUiSettings(); return new TimeBuckets({ - 'histogram:maxBars': uiSettings.get('histogram:maxBars'), - 'histogram:barTarget': uiSettings.get('histogram:barTarget'), + [UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), dateFormat: uiSettings.get('dateFormat'), 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), }); @@ -35,8 +35,8 @@ export function getTimeBucketsFromCache() { */ export function TimeBuckets(timeBucketsConfig) { this._timeBucketsConfig = timeBucketsConfig; - this.barTarget = this._timeBucketsConfig['histogram:barTarget']; - this.maxBars = this._timeBucketsConfig['histogram:maxBars']; + this.barTarget = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_BAR_TARGET]; + this.maxBars = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_MAX_BARS]; } /** @@ -46,7 +46,7 @@ export function TimeBuckets(timeBucketsConfig) { * * @returns {undefined} */ -TimeBuckets.prototype.setBarTarget = function(bt) { +TimeBuckets.prototype.setBarTarget = function (bt) { this.barTarget = bt; }; @@ -57,7 +57,7 @@ TimeBuckets.prototype.setBarTarget = function(bt) { * * @returns {undefined} */ -TimeBuckets.prototype.setMaxBars = function(mb) { +TimeBuckets.prototype.setMaxBars = function (mb) { this.maxBars = mb; }; @@ -72,7 +72,7 @@ TimeBuckets.prototype.setMaxBars = function(mb) { * * @returns {undefined} */ -TimeBuckets.prototype.setBounds = function(input) { +TimeBuckets.prototype.setBounds = function (input) { if (!input) return this.clearBounds(); let bounds; @@ -83,9 +83,7 @@ TimeBuckets.prototype.setBounds = function(input) { bounds = Array.isArray(input) ? input : []; } - const moments = _(bounds) - .map(_.ary(moment, 1)) - .sortBy(Number); + const moments = _(bounds).map(_.ary(moment, 1)).sortBy(Number); const valid = moments.size() === 2 && moments.every(isValidMoment); if (!valid) { @@ -105,7 +103,7 @@ TimeBuckets.prototype.setBounds = function(input) { * * @return {undefined} */ -TimeBuckets.prototype.clearBounds = function() { +TimeBuckets.prototype.clearBounds = function () { this._lb = this._ub = null; }; @@ -114,7 +112,7 @@ TimeBuckets.prototype.clearBounds = function() { * * @return {Boolean} */ -TimeBuckets.prototype.hasBounds = function() { +TimeBuckets.prototype.hasBounds = function () { return isValidMoment(this._ub) && isValidMoment(this._lb); }; @@ -130,7 +128,7 @@ TimeBuckets.prototype.hasBounds = function() { * min and max. Each property will be a moment() * object */ -TimeBuckets.prototype.getBounds = function() { +TimeBuckets.prototype.getBounds = function () { if (!this.hasBounds()) return; return { min: this._lb, @@ -145,7 +143,7 @@ TimeBuckets.prototype.getBounds = function() { * * @return {moment.duration|undefined} */ -TimeBuckets.prototype.getDuration = function() { +TimeBuckets.prototype.getDuration = function () { if (!this.hasBounds()) return; return moment.duration(this._ub - this._lb, 'ms'); }; @@ -161,7 +159,7 @@ TimeBuckets.prototype.getDuration = function() { * * @param {string|moment.duration} input - see desc */ -TimeBuckets.prototype.setInterval = function(input) { +TimeBuckets.prototype.setInterval = function (input) { // Preserve the original units because they're lost when the interval is converted to a // moment duration object. this.originalInterval = input; @@ -223,7 +221,7 @@ TimeBuckets.prototype.setInterval = function(input) { * * @return {[type]} [description] */ -TimeBuckets.prototype.getInterval = function() { +TimeBuckets.prototype.getInterval = function () { const self = this; const duration = self.getDuration(); return decorateInterval(maybeScaleInterval(readInterval()), duration); @@ -268,7 +266,7 @@ TimeBuckets.prototype.getInterval = function() { * * @return {moment.duration|undefined} */ -TimeBuckets.prototype.getIntervalToNearestMultiple = function(divisorSecs) { +TimeBuckets.prototype.getIntervalToNearestMultiple = function (divisorSecs) { const interval = this.getInterval(); const intervalSecs = interval.asSeconds(); @@ -306,7 +304,7 @@ TimeBuckets.prototype.getIntervalToNearestMultiple = function(divisorSecs) { * * @return {string} */ -TimeBuckets.prototype.getScaledDateFormat = function() { +TimeBuckets.prototype.getScaledDateFormat = function () { const interval = this.getInterval(); const rules = this._timeBucketsConfig['dateFormat:scaled']; @@ -320,7 +318,7 @@ TimeBuckets.prototype.getScaledDateFormat = function() { return this._timeBucketsConfig.dateFormat; }; -TimeBuckets.prototype.getScaledDateFormatter = function() { +TimeBuckets.prototype.getScaledDateFormatter = function () { const fieldFormats = getFieldFormats(); const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); return new DateFieldFormat( diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.test.js b/x-pack/plugins/ml/public/application/util/time_buckets.test.js index baecf7df90641..250c7255f5b99 100644 --- a/x-pack/plugins/ml/public/application/util/time_buckets.test.js +++ b/x-pack/plugins/ml/public/application/util/time_buckets.test.js @@ -5,6 +5,7 @@ */ import moment from 'moment'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; import { TimeBuckets, getBoundsRoundedToInterval, calcEsInterval } from './time_buckets'; describe('ML - time buckets', () => { @@ -13,8 +14,8 @@ describe('ML - time buckets', () => { beforeEach(() => { const timeBucketsConfig = { - 'histogram:maxBars': 100, - 'histogram:barTarget': 50, + [UI_SETTINGS.HISTOGRAM_MAX_BARS]: 100, + [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: 50, }; autoBuckets = new TimeBuckets(timeBucketsConfig); diff --git a/x-pack/plugins/ml/public/application/util/url_state.ts b/x-pack/plugins/ml/public/application/util/url_state.ts index b0699116895d4..beff5340ce7e4 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.ts +++ b/x-pack/plugins/ml/public/application/util/url_state.ts @@ -35,7 +35,7 @@ export function getUrlState(search: string): Dictionary { const parsedQueryString = parse(search, { sort: false }); try { - Object.keys(parsedQueryString).forEach(a => { + Object.keys(parsedQueryString).forEach((a) => { if (isRisonSerializationRequired(a)) { urlState[a] = decode(parsedQueryString[a] as string); } else { @@ -77,7 +77,7 @@ export const useUrlState = (accessor: string): UrlState => { urlState[accessor][attribute] = value; } else { const attributes = attribute; - Object.keys(attributes).forEach(a => { + Object.keys(attributes).forEach((a) => { urlState[accessor][a] = attributes[a]; }); } @@ -85,7 +85,7 @@ export const useUrlState = (accessor: string): UrlState => { try { const oldLocationSearch = stringify(parsedQueryString, { sort: false, encode: false }); - Object.keys(urlState).forEach(a => { + Object.keys(urlState).forEach((a) => { if (isRisonSerializationRequired(a)) { parsedQueryString[a] = encode(urlState[a]); } else { diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx index 1f26bce165e9f..b53b08e5f6146 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx @@ -96,7 +96,7 @@ export class AnomalySwimlaneEmbeddable extends Embeddable< embeddableInput={this.getInput$()} services={this.services} refresh={this.reload$.asObservable()} - onOutputChange={output => this.updateOutput(output)} + onOutputChange={(output) => this.updateOutput(output)} />, node ); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx index ab25137f2b7d6..00d47c0d897c7 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx @@ -38,7 +38,7 @@ export interface AnomalySwimlaneInitializerProps { onCancel: () => void; } -const limitOptions = [5, 10, 25, 50].map(limit => ({ +const limitOptions = [5, 10, 25, 50].map((limit) => ({ value: limit, text: `${limit}`, })); @@ -72,7 +72,7 @@ export const AnomalySwimlaneInitializer: FC = ( }, ]; - const viewBySwimlaneOptions = ['', ...influencers].map(influencer => { + const viewBySwimlaneOptions = ['', ...influencers].map((influencer) => { return { value: influencer, text: influencer, @@ -112,7 +112,7 @@ export const AnomalySwimlaneInitializer: FC = ( id="panelTitle" name="panelTitle" value={panelTitle} - onChange={e => setPanelTitle(e.target.value)} + onChange={(e) => setPanelTitle(e.target.value)} isInvalid={!isPanelTitleValid} /> @@ -135,7 +135,7 @@ export const AnomalySwimlaneInitializer: FC = ( })} options={swimlaneTypeOptions} idSelected={swimlaneType} - onChange={id => setSwimlaneType(id as SWIMLANE_TYPE)} + onChange={(id) => setSwimlaneType(id as SWIMLANE_TYPE)} /> @@ -151,7 +151,7 @@ export const AnomalySwimlaneInitializer: FC = ( name="selectViewBy" options={viewBySwimlaneOptions} value={viewBySwimlaneFieldName} - onChange={e => setViewBySwimlaneFieldName(e.target.value)} + onChange={(e) => setViewBySwimlaneFieldName(e.target.value)} /> = ( name="limit" options={limitOptions} value={limit} - onChange={e => setLimit(Number(e.target.value))} + onChange={(e) => setLimit(Number(e.target.value))} /> diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx index 89c237b205287..e5d8584683c55 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx @@ -79,11 +79,11 @@ export const ExplorerSwimlaneContainer: FC = ({ return ( - {resizeRef => ( + {(resizeRef) => (
    { + ref={(el) => { resizeRef(el); }} > @@ -93,7 +93,7 @@ export const ExplorerSwimlaneContainer: FC = ({ {chartWidth > 0 && swimlaneData && swimlaneType ? ( - {tooltipService => ( + {(tooltipService) => ( anomalyDetectorService.getJobs$(jobsIds)) + switchMap((jobsIds) => anomalyDetectorService.getJobs$(jobsIds)) ); } @@ -62,8 +62,8 @@ export function useSwimlaneInputResolver( const timeBuckets = useMemo(() => { return new TimeBuckets({ - 'histogram:maxBars': uiSettings.get('histogram:maxBars'), - 'histogram:barTarget': uiSettings.get('histogram:barTarget'), + 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), dateFormat: uiSettings.get('dateFormat'), 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), }); @@ -74,7 +74,7 @@ export function useSwimlaneInputResolver( getJobsObservable(embeddableInput, anomalyDetectorService), embeddableInput, chartWidth$.pipe( - skipWhile(v => !v), + skipWhile((v) => !v), distinctUntilChanged((prev, curr) => { // emit only if the width has been changed significantly return Math.abs(curr - prev) < RESIZE_IGNORED_DIFF_PX; @@ -100,7 +100,7 @@ export function useSwimlaneInputResolver( setSwimlaneType(swimlaneTypeInput); } - const explorerJobs: ExplorerJob[] = jobs.map(job => { + const explorerJobs: ExplorerJob[] = jobs.map((job) => { const bucketSpan = parseInterval(job.analysis_config.bucket_span); return { id: job.job_id, @@ -119,7 +119,7 @@ export function useSwimlaneInputResolver( } return from(explorerService.loadOverallData(explorerJobs, swimlaneContainerWidth)).pipe( - switchMap(overallSwimlaneData => { + switchMap((overallSwimlaneData) => { const { earliest, latest } = overallSwimlaneData; if (overallSwimlaneData && swimlaneTypeInput === SWIMLANE_TYPE.VIEW_BY) { @@ -134,7 +134,7 @@ export function useSwimlaneInputResolver( appliedFilters ) ).pipe( - map(viewBySwimlaneData => { + map((viewBySwimlaneData) => { return { ...viewBySwimlaneData!, earliest, @@ -147,12 +147,12 @@ export function useSwimlaneInputResolver( }) ); }), - catchError(e => { + catchError((e) => { setError(e.body); return of(undefined); }) ) - .subscribe(data => { + .subscribe((data) => { if (data !== undefined) { setError(null); setSwimlaneData(data); diff --git a/x-pack/plugins/ml/server/client/elasticsearch_ml.test.ts b/x-pack/plugins/ml/server/client/elasticsearch_ml.test.ts index 543cf4ddad982..5ad0db3c58ce4 100644 --- a/x-pack/plugins/ml/server/client/elasticsearch_ml.test.ts +++ b/x-pack/plugins/ml/server/client/elasticsearch_ml.test.ts @@ -31,7 +31,7 @@ describe('ML - Endpoints', () => { factory(obj: ClientAction) { // add each endpoint URL to a list if (obj.urls) { - obj.urls.forEach(url => { + obj.urls.forEach((url) => { urls.push(url.fmt); }); } @@ -52,7 +52,7 @@ describe('ML - Endpoints', () => { describe('paths', () => { it(`should start with ${PATH_START}`, () => { - urls.forEach(url => { + urls.forEach((url) => { expect(url[0]).toEqual(PATH_START); }); }); diff --git a/x-pack/plugins/ml/server/lib/capabilities/__mocks__/ml_capabilities.ts b/x-pack/plugins/ml/server/lib/capabilities/__mocks__/ml_capabilities.ts index 8e350b8382276..a5c04092fab77 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/__mocks__/ml_capabilities.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/__mocks__/ml_capabilities.ts @@ -13,7 +13,7 @@ import { export function getAdminCapabilities() { const caps: any = {}; - Object.keys(adminMlCapabilities).forEach(k => { + Object.keys(adminMlCapabilities).forEach((k) => { caps[k] = true; }); return { ...getUserCapabilities(), ...caps } as MlCapabilities; @@ -21,7 +21,7 @@ export function getAdminCapabilities() { export function getUserCapabilities() { const caps: any = {}; - Object.keys(userMlCapabilities).forEach(k => { + Object.keys(userMlCapabilities).forEach((k) => { caps[k] = true; }); diff --git a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts index aef22debf3642..5b8cbc4bdbbe8 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts @@ -40,13 +40,13 @@ function getSwitcher(license$: Observable, logger: Logger): Capabiliti const originalCapabilities = cloneDeep(mlCaps); // not full licence, switch off all capabilities - Object.keys(mlCaps).forEach(k => { + Object.keys(mlCaps).forEach((k) => { mlCaps[k as keyof MlCapabilities] = false; }); // for a basic license, reapply the original capabilities for the basic license features if (isMinimumLicense(license)) { - basicLicenseMlCapabilities.forEach(c => (mlCaps[c] = originalCapabilities[c])); + basicLicenseMlCapabilities.forEach((c) => (mlCaps[c] = originalCapabilities[c])); } return capabilities; diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts index 746c9da47d0ad..3354523b1718c 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts @@ -27,7 +27,7 @@ const callWithRequestUpgrade = async () => ({ upgrade_mode: true }); describe('check_capabilities', () => { describe('getCapabilities() - right number of capabilities', () => { - test('kibana capabilities count', async done => { + test('kibana capabilities count', async (done) => { const { getCapabilities } = capabilitiesProvider( callWithRequestNonUpgrade, getAdminCapabilities(), @@ -42,7 +42,7 @@ describe('check_capabilities', () => { }); describe('getCapabilities() with security', () => { - test('ml_user capabilities only', async done => { + test('ml_user capabilities only', async (done) => { const { getCapabilities } = capabilitiesProvider( callWithRequestNonUpgrade, getUserCapabilities(), @@ -91,7 +91,7 @@ describe('check_capabilities', () => { done(); }); - test('full capabilities', async done => { + test('full capabilities', async (done) => { const { getCapabilities } = capabilitiesProvider( callWithRequestNonUpgrade, getAdminCapabilities(), @@ -140,7 +140,7 @@ describe('check_capabilities', () => { done(); }); - test('upgrade in progress with full capabilities', async done => { + test('upgrade in progress with full capabilities', async (done) => { const { getCapabilities } = capabilitiesProvider( callWithRequestUpgrade, getAdminCapabilities(), @@ -189,7 +189,7 @@ describe('check_capabilities', () => { done(); }); - test('upgrade in progress with partial capabilities', async done => { + test('upgrade in progress with partial capabilities', async (done) => { const { getCapabilities } = capabilitiesProvider( callWithRequestUpgrade, getUserCapabilities(), @@ -238,7 +238,7 @@ describe('check_capabilities', () => { done(); }); - test('full capabilities, ml disabled in space', async done => { + test('full capabilities, ml disabled in space', async (done) => { const { getCapabilities } = capabilitiesProvider( callWithRequestNonUpgrade, getDefaultCapabilities(), @@ -288,7 +288,7 @@ describe('check_capabilities', () => { }); }); - test('full capabilities, basic license, ml disabled in space', async done => { + test('full capabilities, basic license, ml disabled in space', async (done) => { const { getCapabilities } = capabilitiesProvider( callWithRequestNonUpgrade, getDefaultCapabilities(), diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts index d955cf981faca..ce775a425fa73 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts @@ -41,7 +41,7 @@ export function capabilitiesProvider( } function disableAdminPrivileges(capabilities: MlCapabilities) { - Object.keys(adminMlCapabilities).forEach(k => { + Object.keys(adminMlCapabilities).forEach((k) => { capabilities[k as keyof MlCapabilities] = false; }); capabilities.canCreateAnnotation = false; diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts index 8d70b11bce152..0d88fd0cd1bb0 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts @@ -35,7 +35,7 @@ describe('annotation_service', () => { }); describe('deleteAnnotation()', () => { - it('should delete annotation', async done => { + it('should delete annotation', async (done) => { const { deleteAnnotation } = annotationServiceProvider(callWithRequestSpy); const mockFunct = callWithRequestSpy; @@ -56,7 +56,7 @@ describe('annotation_service', () => { }); describe('getAnnotation()', () => { - it('should get annotations for specific job', async done => { + it('should get annotations for specific job', async (done) => { const { getAnnotations } = annotationServiceProvider(callWithRequestSpy); const mockFunct = callWithRequestSpy; @@ -104,7 +104,7 @@ describe('annotation_service', () => { }); describe('indexAnnotation()', () => { - it('should index annotation', async done => { + it('should index annotation', async (done) => { const { indexAnnotation } = annotationServiceProvider(callWithRequestSpy); const mockFunct = callWithRequestSpy; @@ -132,7 +132,7 @@ describe('annotation_service', () => { done(); }); - it('should remove ._id and .key before updating annotation', async done => { + it('should remove ._id and .key before updating annotation', async (done) => { const { indexAnnotation } = annotationServiceProvider(callWithRequestSpy); const mockFunct = callWithRequestSpy; @@ -164,7 +164,7 @@ describe('annotation_service', () => { done(); }); - it('should update annotation text and the username for modified_username', async done => { + it('should update annotation text and the username for modified_username', async (done) => { const { getAnnotations, indexAnnotation } = annotationServiceProvider(callWithRequestSpy); const mockFunct = callWithRequestSpy; diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js index d2e4311bf6f22..2e03a9532c831 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js @@ -128,7 +128,7 @@ export function estimateBucketSpanFactory( this.polledDataChecker .run() - .then(result => { + .then((result) => { // if the data is polled, set a minimum threshold // of bucket span if (result.isPolled) { @@ -154,10 +154,10 @@ export function estimateBucketSpanFactory( } }; - _.each(this.checkers, check => { + _.each(this.checkers, (check) => { check.check .run() - .then(interval => { + .then((interval) => { check.result = interval; runComplete(); }) @@ -170,7 +170,7 @@ export function estimateBucketSpanFactory( }); }); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); @@ -188,8 +188,8 @@ export function estimateBucketSpanFactory( const pos = i * numberOfSplitFields; let resultsSubset = allResults.slice(pos, pos + numberOfSplitFields); // remove results of tests which have failed - resultsSubset = _.remove(resultsSubset, res => res !== null); - resultsSubset = _.sortBy(resultsSubset, r => r.ms); + resultsSubset = _.remove(resultsSubset, (res) => res !== null); + resultsSubset = _.sortBy(resultsSubset, (r) => r.ms); const tempMedian = this.findMedian(resultsSubset); if (tempMedian !== null) { @@ -197,7 +197,7 @@ export function estimateBucketSpanFactory( } } - reducedResults = _.sortBy(reducedResults, r => r.ms); + reducedResults = _.sortBy(reducedResults, (r) => r.ms); return this.findMedian(reducedResults); } @@ -243,7 +243,7 @@ export function estimateBucketSpanFactory( } } - const getFieldCardinality = function(index, field) { + const getFieldCardinality = function (index, field) { return new Promise((resolve, reject) => { callAsCurrentUser('search', { index, @@ -258,24 +258,24 @@ export function estimateBucketSpanFactory( }, }, }) - .then(resp => { + .then((resp) => { const value = _.get(resp, ['aggregations', 'field_count', 'value'], 0); resolve(value); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); }; - const getRandomFieldValues = function(index, field, query) { + const getRandomFieldValues = function (index, field, query) { let fieldValues = []; return new Promise((resolve, reject) => { const NUM_PARTITIONS = 10; // use a partitioned search to load 10 random fields // load ten fields, to test that there are at least 10. getFieldCardinality(index, field) - .then(value => { + .then((value) => { const numPartitions = Math.floor(value / NUM_PARTITIONS) || 1; callAsCurrentUser('search', { index, @@ -295,24 +295,24 @@ export function estimateBucketSpanFactory( }, }, }) - .then(partitionResp => { + .then((partitionResp) => { if (_.has(partitionResp, 'aggregations.fields_bucket_counts.buckets')) { const buckets = partitionResp.aggregations.fields_bucket_counts.buckets; - fieldValues = _.map(buckets, b => b.key); + fieldValues = _.map(buckets, (b) => b.key); } resolve(fieldValues); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); }; - return function(formConfig) { + return function (formConfig) { if (typeof formConfig !== 'object' || formConfig === null) { throw new Error('Invalid formConfig: formConfig needs to be an object.'); } @@ -342,7 +342,7 @@ export function estimateBucketSpanFactory( includeDefaults: true, filterPath: '*.*max_buckets', }) - .then(settings => { + .then((settings) => { if (typeof settings !== 'object') { reject('Unable to retrieve cluster settings'); } @@ -368,10 +368,10 @@ export function estimateBucketSpanFactory( bucketSpanEstimator .run() - .then(resp => { + .then((resp) => { resolve(resp); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }; @@ -380,10 +380,10 @@ export function estimateBucketSpanFactory( // bucket span tests. if (formConfig.splitField !== undefined) { getRandomFieldValues(formConfig.index, formConfig.splitField, formConfig.query) - .then(splitFieldValues => { + .then((splitFieldValues) => { runEstimator(splitFieldValues); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); } else { @@ -391,7 +391,7 @@ export function estimateBucketSpanFactory( runEstimator(); } }) - .catch(resp => { + .catch((resp) => { reject(resp); }); } @@ -413,7 +413,7 @@ export function estimateBucketSpanFactory( ], }; callAsCurrentUser('ml.privilegeCheck', { body }) - .then(resp => { + .then((resp) => { if ( resp.cluster['cluster:monitor/xpack/ml/job/get'] && resp.cluster['cluster:monitor/xpack/ml/job/stats/get'] && diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts index f5daadfe86be0..8e8301db2a3a3 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts @@ -18,7 +18,7 @@ import { estimateBucketSpanFactory, BucketSpanEstimatorData } from './bucket_spa // permissions. const permissions = [false, true]; const callWithRequest: APICaller = (method: string) => { - return new Promise(resolve => { + return new Promise((resolve) => { if (method === 'ml.privilegeCheck') { resolve({ cluster: { @@ -35,7 +35,7 @@ const callWithRequest: APICaller = (method: string) => { }; const callWithInternalUser: APICaller = () => { - return new Promise(resolve => { + return new Promise((resolve) => { resolve({}); }) as Promise; }; @@ -58,20 +58,20 @@ const formConfig: BucketSpanEstimatorData = { describe('ML - BucketSpanEstimator', () => { it('call factory', () => { - expect(function() { + expect(function () { estimateBucketSpanFactory(callWithRequest, callWithInternalUser, false); }).not.toThrow('Not initialized.'); }); - it('call factory and estimator with security disabled', done => { - expect(function() { + it('call factory and estimator with security disabled', (done) => { + expect(function () { const estimateBucketSpan = estimateBucketSpanFactory( callWithRequest, callWithInternalUser, true ); - estimateBucketSpan(formConfig).catch(catchData => { + estimateBucketSpan(formConfig).catch((catchData) => { expect(catchData).toBe('Unable to retrieve cluster setting search.max_buckets'); done(); @@ -79,14 +79,14 @@ describe('ML - BucketSpanEstimator', () => { }).not.toThrow('Not initialized.'); }); - it('call factory and estimator with security enabled.', done => { - expect(function() { + it('call factory and estimator with security enabled.', (done) => { + expect(function () { const estimateBucketSpan = estimateBucketSpanFactory( callWithRequest, callWithInternalUser, false ); - estimateBucketSpan(formConfig).catch(catchData => { + estimateBucketSpan(formConfig).catch((catchData) => { expect(catchData).toBe('Unable to retrieve cluster setting search.max_buckets'); done(); diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js index 674875f8d5d16..de9fd06c34e6a 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js @@ -28,7 +28,7 @@ export function polledDataCheckerFactory(callAsCurrentUser) { return new Promise((resolve, reject) => { const interval = { name: '1m', ms: 60000 }; this.performSearch(interval.ms) - .then(resp => { + .then((resp) => { const fullBuckets = _.get(resp, 'aggregations.non_empty_buckets.buckets', []); const result = this.isPolledData(fullBuckets, interval); if (result.pass) { @@ -42,7 +42,7 @@ export function polledDataCheckerFactory(callAsCurrentUser) { minimumBucketSpan: this.minimumBucketSpan, }); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js index 71e692d089b49..6ae485fe11307 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js @@ -39,11 +39,11 @@ export function singleSeriesCheckerFactory(callAsCurrentUser) { const start = () => { // run all tests, returns a suggested interval this.runTests() - .then(interval => { + .then((interval) => { this.interval = interval; resolve(this.interval); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }; @@ -56,7 +56,7 @@ export function singleSeriesCheckerFactory(callAsCurrentUser) { .then(() => { start(); }) - .catch(resp => { + .catch((resp) => { mlLog.warn('SingleSeriesChecker: Could not load metric reference data'); reject(resp); }); @@ -105,10 +105,10 @@ export function singleSeriesCheckerFactory(callAsCurrentUser) { // recursive function called with the index of the INTERVALS array // each time one of the checks fails, the index is increased and // the tests are repeated. - const runTest = i => { + const runTest = (i) => { const interval = intervals[i]; this.performSearch(interval.ms) - .then(resp => { + .then((resp) => { const buckets = resp.aggregations.non_empty_buckets.buckets; const fullBuckets = this.getFullBuckets(buckets); if (fullBuckets.length) { @@ -149,7 +149,7 @@ export function singleSeriesCheckerFactory(callAsCurrentUser) { reject('runTest stopped because fullBuckets is empty'); } }) - .catch(resp => { + .catch((resp) => { // do something better with this reject(resp); }); @@ -265,7 +265,7 @@ export function singleSeriesCheckerFactory(callAsCurrentUser) { } this.performSearch(intervalMs) // 1h - .then(resp => { + .then((resp) => { const buckets = resp.aggregations.non_empty_buckets.buckets; const fullBuckets = this.getFullBuckets(buckets); if (fullBuckets.length) { @@ -275,7 +275,7 @@ export function singleSeriesCheckerFactory(callAsCurrentUser) { resolve(); }) - .catch(resp => { + .catch((resp) => { reject(resp); }); }); diff --git a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts index 1cc2a07ddbc88..9533fbc89c76c 100644 --- a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts +++ b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts @@ -77,8 +77,8 @@ const cardinalityCheckProvider = (callAsCurrentUser: APICaller) => { } ) => { [byFieldName, partitionFieldName, overFieldName] - .filter(field => field !== undefined && field !== '' && !excludedKeywords.has(field)) - .forEach(key => { + .filter((field) => field !== undefined && field !== '' && !excludedKeywords.has(field)) + .forEach((key) => { acc.add(key as string); }); return acc; @@ -87,7 +87,7 @@ const cardinalityCheckProvider = (callAsCurrentUser: APICaller) => { ); const maxBucketFieldCardinalities: string[] = influencers.filter( - influencerField => + (influencerField) => !!influencerField && !excludedKeywords.has(influencerField) && !overallCardinalityFields.has(influencerField) diff --git a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts index 581770e59043f..acb1bed6a37c0 100644 --- a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts @@ -47,11 +47,11 @@ export class CalendarManager { const events: CalendarEvent[] = await this._eventManager.getAllEvents(); const calendars: Calendar[] = calendarsResp.calendars; - calendars.forEach(cal => (cal.events = [])); + calendars.forEach((cal) => (cal.events = [])); // loop events and combine with related calendars - events.forEach(event => { - const calendar = calendars.find(cal => cal.calendar_id === event.calendar_id); + events.forEach((event) => { + const calendar = calendars.find((cal) => cal.calendar_id === event.calendar_id); if (calendar) { calendar.events.push(event); } @@ -66,7 +66,7 @@ export class CalendarManager { */ async getCalendarsByIds(calendarIds: string) { const calendars: Calendar[] = await this.getAllCalendars(); - return calendars.filter(calendar => calendarIds.includes(calendar.calendar_id)); + return calendars.filter((calendar) => calendarIds.includes(calendar.calendar_id)); } async newCalendar(calendar: FormCalendar) { @@ -96,12 +96,12 @@ export class CalendarManager { // workout the differences between the original events list and the new one // if an event has no event_id, it must be new const eventsToAdd = calendar.events.filter( - event => origCalendar.events.find(e => this._eventManager.isEqual(e, event)) === undefined + (event) => origCalendar.events.find((e) => this._eventManager.isEqual(e, event)) === undefined ); // if an event in the original calendar cannot be found, it must have been deleted const eventsToRemove: CalendarEvent[] = origCalendar.events.filter( - event => calendar.events.find(e => this._eventManager.isEqual(e, event)) === undefined + (event) => calendar.events.find((e) => this._eventManager.isEqual(e, event)) === undefined ); // note, both of the loops below could be removed if the add and delete endpoints @@ -130,7 +130,7 @@ export class CalendarManager { // remove all removed events await Promise.all( - eventsToRemove.map(async event => { + eventsToRemove.map(async (event) => { await this._eventManager.deleteEvent(calendarId, event.event_id); }) ); diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts new file mode 100644 index 0000000000000..d1a4df768a6ae --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; +import { IIndexPattern } from 'src/plugins/data/server'; + +export class IndexPatternHandler { + constructor(private savedObjectsClient: SavedObjectsClientContract) {} + // returns a id based on an index pattern name + async getIndexPatternId(indexName: string) { + const response = await this.savedObjectsClient.find({ + type: 'index-pattern', + perPage: 10, + search: `"${indexName}"`, + searchFields: ['title'], + fields: ['title'], + }); + + const ip = response.saved_objects.find( + (obj) => obj.attributes.title.toLowerCase() === indexName.toLowerCase() + ); + + return ip?.id; + } + + async deleteIndexPatternById(indexId: string) { + return await this.savedObjectsClient.delete('index-pattern', indexId); + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 92ab7739dbcfb..82a272b068bb3 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -125,7 +125,7 @@ export class DataRecognizer { if (err) { reject(err); } - fileNames.forEach(fileName => { + fileNames.forEach((fileName) => { const path = `${dirName}/${fileName}`; if (fs.lstatSync(path).isDirectory()) { dirs.push(fileName); @@ -152,7 +152,7 @@ export class DataRecognizer { const configs: Config[] = []; const dirs = await this.listDirs(this.modulesDir); await Promise.all( - dirs.map(async dir => { + dirs.map(async (dir) => { let file: string | undefined; try { file = await this.readFile(`${this.modulesDir}/${dir}/manifest.json`); @@ -179,7 +179,7 @@ export class DataRecognizer { // get the manifest.json file for a specified id, e.g. "nginx" async getManifestFile(id: string) { const manifestFiles = await this.loadManifestFiles(); - return manifestFiles.find(i => i.json.id === id); + return manifestFiles.find((i) => i.json.id === id); } // called externally by an endpoint @@ -188,7 +188,7 @@ export class DataRecognizer { const results: RecognizeResult[] = []; await Promise.all( - manifestFiles.map(async i => { + manifestFiles.map(async (i) => { const moduleConfig = i.json; let match = false; try { @@ -278,7 +278,7 @@ export class DataRecognizer { const kibana: KibanaObjects = {}; // load all of the job configs await Promise.all( - manifestJSON.jobs.map(async job => { + manifestJSON.jobs.map(async (job) => { try { const jobConfig = await this.readFile( `${this.modulesDir}/${dirName}/${ML_DIR}/${job.file}` @@ -298,7 +298,7 @@ export class DataRecognizer { // load all of the datafeed configs await Promise.all( - manifestJSON.datafeeds.map(async datafeed => { + manifestJSON.datafeeds.map(async (datafeed) => { try { const datafeedConfig = await this.readFile( `${this.modulesDir}/${dirName}/${ML_DIR}/${datafeed.file}` @@ -323,10 +323,10 @@ export class DataRecognizer { if (manifestJSON.kibana !== undefined) { const kKeys = Object.keys(manifestJSON.kibana) as Array; await Promise.all( - kKeys.map(async key => { + kKeys.map(async (key) => { kibana[key] = []; await Promise.all( - manifestJSON!.kibana[key].map(async obj => { + manifestJSON!.kibana[key].map(async (obj) => { try { const kConfig = await this.readFile( `${this.modulesDir}/${dirName}/${KIBANA_DIR}/${key}/${obj.file}` @@ -416,9 +416,9 @@ export class DataRecognizer { savedObjects: [] as KibanaObjectResponse[], }; - this.jobsForModelMemoryEstimation = moduleConfig.jobs.map(job => ({ + this.jobsForModelMemoryEstimation = moduleConfig.jobs.map((job) => ({ job, - query: moduleConfig.datafeeds.find(d => d.config.job_id === job.id)?.config.query ?? null, + query: moduleConfig.datafeeds.find((d) => d.config.job_id === job.id)?.config.query ?? null, })); this.applyJobConfigOverrides(moduleConfig, jobOverrides, jobPrefix); @@ -431,12 +431,12 @@ export class DataRecognizer { if (moduleConfig.jobs && moduleConfig.jobs.length) { if (Array.isArray(groups)) { // update groups list for each job - moduleConfig.jobs.forEach(job => (job.config.groups = groups)); + moduleConfig.jobs.forEach((job) => (job.config.groups = groups)); } // Set the results_index_name property for each job if useDedicatedIndex is true if (useDedicatedIndex === true) { - moduleConfig.jobs.forEach(job => (job.config.results_index_name = job.id)); + moduleConfig.jobs.forEach((job) => (job.config.results_index_name = job.id)); } saveResults.jobs = await this.saveJobs(moduleConfig.jobs); } @@ -444,20 +444,20 @@ export class DataRecognizer { // create the datafeeds if (moduleConfig.datafeeds && moduleConfig.datafeeds.length) { if (typeof query === 'object' && query !== null) { - moduleConfig.datafeeds.forEach(df => { + moduleConfig.datafeeds.forEach((df) => { df.config.query = query; }); } saveResults.datafeeds = await this.saveDatafeeds(moduleConfig.datafeeds); if (startDatafeed) { - const savedDatafeeds = moduleConfig.datafeeds.filter(df => { - const datafeedResult = saveResults.datafeeds.find(d => d.id === df.id); + const savedDatafeeds = moduleConfig.datafeeds.filter((df) => { + const datafeedResult = saveResults.datafeeds.find((d) => d.id === df.id); return datafeedResult !== undefined && datafeedResult.success === true; }); const startResults = await this.startDatafeeds(savedDatafeeds, start, end); - saveResults.datafeeds.forEach(df => { + saveResults.datafeeds.forEach((df) => { const startedDatafeed = startResults[df.id]; if (startedDatafeed !== undefined) { df.started = startedDatafeed.started; @@ -494,7 +494,7 @@ export class DataRecognizer { if (module && module.jobs) { // Add a wildcard at the front of each of the job IDs in the module, // as a prefix may have been supplied when creating the jobs in the module. - const jobIds = module.jobs.map(job => `*${job.id}`); + const jobIds = module.jobs.map((job) => `*${job.id}`); const { jobsExist } = jobServiceProvider(this.callAsCurrentUser); const jobInfo = await jobsExist(jobIds); @@ -507,11 +507,11 @@ export class DataRecognizer { const jobStats: MlJobStats = await this.callAsCurrentUser('ml.jobStats', { jobId: jobIds }); const jobStatsJobs: JobStat[] = []; if (jobStats.jobs && jobStats.jobs.length > 0) { - const foundJobIds = jobStats.jobs.map(job => job.job_id); + const foundJobIds = jobStats.jobs.map((job) => job.job_id); const { getLatestBucketTimestampByJob } = resultsServiceProvider(this.callAsCurrentUser); const latestBucketTimestampsByJob = await getLatestBucketTimestampByJob(foundJobIds); - jobStats.jobs.forEach(job => { + jobStats.jobs.forEach((job) => { const jobStat = { id: job.job_id, } as JobStat; @@ -548,7 +548,7 @@ export class DataRecognizer { if (indexPatterns === undefined || indexPatterns.saved_objects === undefined) { return; } - const ip = indexPatterns.saved_objects.find(i => i.attributes.title === name); + const ip = indexPatterns.saved_objects.find((i) => i.attributes.title === name); return ip !== undefined ? ip.id : undefined; } catch (error) { mlLog.warn(`Error loading index patterns, ${error}`); @@ -563,10 +563,10 @@ export class DataRecognizer { // first check if the saved objects already exist. const savedObjectExistResults = await this.checkIfSavedObjectsExist(moduleConfig.kibana); // loop through the kibanaSaveResults and update - Object.keys(moduleConfig.kibana).forEach(type => { + Object.keys(moduleConfig.kibana).forEach((type) => { // type e.g. dashboard, search ,visualization - moduleConfig.kibana[type]!.forEach(configItem => { - const existsResult = savedObjectExistResults.find(o => o.id === configItem.id); + moduleConfig.kibana[type]!.forEach((configItem) => { + const existsResult = savedObjectExistResults.find((o) => o.id === configItem.id); if (existsResult !== undefined) { configItem.exists = existsResult.exists; if (existsResult.exists === false) { @@ -590,9 +590,9 @@ export class DataRecognizer { objectExistResults: ObjectExistResult[] ) { (Object.keys(kibanaSaveResults) as Array).forEach( - type => { - kibanaSaveResults[type].forEach(resultItem => { - const i = objectExistResults.find(o => o.id === resultItem.id && o.type === type); + (type) => { + kibanaSaveResults[type].forEach((resultItem) => { + const i = objectExistResults.find((o) => o.id === resultItem.id && o.type === type); resultItem.exists = i !== undefined; }); } @@ -606,11 +606,11 @@ export class DataRecognizer { async checkIfSavedObjectsExist(kibanaObjects: KibanaObjects): Promise { const types = Object.keys(kibanaObjects); const results: ObjectExistResponse[][] = await Promise.all( - types.map(async type => { + types.map(async (type) => { const existingObjects = await this.loadExistingSavedObjects(type); - return kibanaObjects[type]!.map(obj => { + return kibanaObjects[type]!.map((obj) => { const existingObject = existingObjects.saved_objects.find( - o => o.attributes && o.attributes.title === obj.title + (o) => o.attributes && o.attributes.title === obj.title ); return { id: obj.id, @@ -634,13 +634,16 @@ export class DataRecognizer { async saveKibanaObjects(objectExistResults: ObjectExistResponse[]) { let results = { saved_objects: [] as any[] }; const filteredSavedObjects = objectExistResults - .filter(o => o.exists === false) - .map(o => o.savedObject); + .filter((o) => o.exists === false) + .map((o) => o.savedObject); if (filteredSavedObjects.length) { results = await this.savedObjectsClient.bulkCreate( // Add an empty migrationVersion attribute to each saved object to ensure // it is automatically migrated to the 7.0+ format with a references attribute. - filteredSavedObjects.map(doc => ({ ...doc, migrationVersion: doc.migrationVersion || {} })) + filteredSavedObjects.map((doc) => ({ + ...doc, + migrationVersion: doc.migrationVersion || {}, + })) ); } return results.saved_objects; @@ -651,7 +654,7 @@ export class DataRecognizer { // as success: false async saveJobs(jobs: ModuleJob[]): Promise { return await Promise.all( - jobs.map(async job => { + jobs.map(async (job) => { const jobId = job.id; try { job.id = jobId; @@ -674,7 +677,7 @@ export class DataRecognizer { // as success: false async saveDatafeeds(datafeeds: ModuleDataFeed[]) { return await Promise.all( - datafeeds.map(async datafeed => { + datafeeds.map(async (datafeed) => { try { await this.saveDatafeed(datafeed); return { id: datafeed.id, success: true, started: false }; @@ -748,8 +751,8 @@ export class DataRecognizer { // which is returned from the endpoint async updateResults(results: DataRecognizerConfigResponse, saveResults: SaveResults) { // update job results - results.jobs.forEach(j => { - saveResults.jobs.forEach(j2 => { + results.jobs.forEach((j) => { + saveResults.jobs.forEach((j2) => { if (j.id === j2.id) { j.success = j2.success; if (j2.error !== undefined) { @@ -760,8 +763,8 @@ export class DataRecognizer { }); // update datafeed results - results.datafeeds.forEach(d => { - saveResults.datafeeds.forEach(d2 => { + results.datafeeds.forEach((d) => { + saveResults.datafeeds.forEach((d2) => { if (d.id === d2.id) { d.success = d2.success; d.started = d2.started; @@ -774,9 +777,9 @@ export class DataRecognizer { // update savedObjects results (Object.keys(results.kibana) as Array).forEach( - category => { - results.kibana[category].forEach(item => { - const result = saveResults.savedObjects.find(o => o.id === item.id); + (category) => { + results.kibana[category].forEach((item) => { + const result = saveResults.savedObjects.find((o) => o.id === item.id); if (result !== undefined) { item.exists = result.exists; @@ -808,7 +811,7 @@ export class DataRecognizer { index: string | number ): void { resultItems[index] = []; - configItems.forEach(j => { + configItems.forEach((j) => { resultItems[index].push({ id: j.id, success: false, @@ -816,12 +819,12 @@ export class DataRecognizer { }); } - (Object.keys(reducedConfig) as Array).forEach(i => { + (Object.keys(reducedConfig) as Array).forEach((i) => { if (Array.isArray(reducedConfig[i])) { createResultsItems(reducedConfig[i] as any[], results, i); } else { results[i] = {} as any; - Object.keys(reducedConfig[i]).forEach(k => { + Object.keys(reducedConfig[i]).forEach((k) => { createResultsItems((reducedConfig[i] as Module['kibana'])[k] as any[], results[i], k); }); } @@ -837,13 +840,13 @@ export class DataRecognizer { // add each one to the datafeed const indexPatternNames = splitIndexPatternNames(this.indexPatternName); - moduleConfig.datafeeds.forEach(df => { + moduleConfig.datafeeds.forEach((df) => { const newIndices: string[] = []; // the datafeed can contain indexes and indices const currentIndices = df.config.indexes !== undefined ? df.config.indexes : df.config.indices; - currentIndices.forEach(index => { + currentIndices.forEach((index) => { if (index === INDEX_PATTERN_NAME) { // the datafeed index is INDEX_PATTERN_NAME, so replace it with index pattern(s) // supplied by the user or the default one from the manifest @@ -864,11 +867,11 @@ export class DataRecognizer { // marker for the id of the specified index pattern updateJobUrlIndexPatterns(moduleConfig: Module) { if (Array.isArray(moduleConfig.jobs)) { - moduleConfig.jobs.forEach(job => { + moduleConfig.jobs.forEach((job) => { // if the job has custom_urls if (job.config.custom_settings && job.config.custom_settings.custom_urls) { // loop through each url, replacing the INDEX_PATTERN_ID marker - job.config.custom_settings.custom_urls.forEach(cUrl => { + job.config.custom_settings.custom_urls.forEach((cUrl) => { const url = cUrl.url_value; if (url.match(INDEX_PATTERN_ID)) { const newUrl = url.replace( @@ -906,8 +909,8 @@ export class DataRecognizer { // INDEX_PATTERN_NAME markers for the id or name of the specified index pattern updateSavedObjectIndexPatterns(moduleConfig: Module) { if (moduleConfig.kibana) { - Object.keys(moduleConfig.kibana).forEach(category => { - moduleConfig.kibana[category]!.forEach(item => { + Object.keys(moduleConfig.kibana).forEach((category) => { + moduleConfig.kibana[category]!.forEach((item) => { let jsonString = item.config.kibanaSavedObjectMeta!.searchSourceJSON; if (jsonString.match(INDEX_PATTERN_ID)) { jsonString = jsonString.replace( @@ -1094,7 +1097,7 @@ export class DataRecognizer { const generalOverrides: GeneralJobsOverride[] = []; const jobSpecificOverrides: JobSpecificOverride[] = []; - overrides.forEach(override => { + overrides.forEach((override) => { if (isGeneralJobOverride(override)) { generalOverrides.push(override); } else { @@ -1102,17 +1105,18 @@ export class DataRecognizer { } }); - if (generalOverrides.some(override => !!override.analysis_limits?.model_memory_limit)) { + if (generalOverrides.some((override) => !!override.analysis_limits?.model_memory_limit)) { this.jobsForModelMemoryEstimation = []; } else { this.jobsForModelMemoryEstimation = moduleConfig.jobs - .filter(job => { - const override = jobSpecificOverrides.find(o => `${jobPrefix}${o.job_id}` === job.id); + .filter((job) => { + const override = jobSpecificOverrides.find((o) => `${jobPrefix}${o.job_id}` === job.id); return override?.analysis_limits?.model_memory_limit === undefined; }) - .map(job => ({ + .map((job) => ({ job, - query: moduleConfig.datafeeds.find(d => d.config.job_id === job.id)?.config.query || null, + query: + moduleConfig.datafeeds.find((d) => d.config.job_id === job.id)?.config.query || null, })); } @@ -1121,7 +1125,7 @@ export class DataRecognizer { return; } - Object.keys(source).forEach(key => { + Object.keys(source).forEach((key) => { const sourceValue = source[key]; const updateValue = update[key]; @@ -1142,17 +1146,17 @@ export class DataRecognizer { }); } - generalOverrides.forEach(generalOverride => { - jobs.forEach(job => { + generalOverrides.forEach((generalOverride) => { + jobs.forEach((job) => { merge(job.config, generalOverride); processArrayValues(job.config, generalOverride); }); }); - jobSpecificOverrides.forEach(jobSpecificOverride => { + jobSpecificOverrides.forEach((jobSpecificOverride) => { // for each override, find the relevant job. // note, the job id already has the prefix prepended to it - const job = jobs.find(j => j.id === `${jobPrefix}${jobSpecificOverride.job_id}`); + const job = jobs.find((j) => j.id === `${jobPrefix}${jobSpecificOverride.job_id}`); if (job !== undefined) { // delete the job_id in the override as this shouldn't be overridden delete jobSpecificOverride.job_id; @@ -1183,7 +1187,7 @@ export class DataRecognizer { // the overrides which don't contain a datafeed id or a job id will be applied to all jobs in the module const generalOverrides: GeneralDatafeedsOverride[] = []; const datafeedSpecificOverrides: DatafeedOverride[] = []; - overrides.forEach(o => { + overrides.forEach((o) => { if (o.datafeed_id === undefined && o.job_id === undefined) { generalOverrides.push(o); } else { @@ -1191,20 +1195,20 @@ export class DataRecognizer { } }); - generalOverrides.forEach(o => { + generalOverrides.forEach((o) => { datafeeds.forEach(({ config }) => { merge(config, o); }); }); // collect all the overrides which contain either a job id or a datafeed id - datafeedSpecificOverrides.forEach(o => { + datafeedSpecificOverrides.forEach((o) => { // either a job id or datafeed id has been specified, so create a new id // containing either one plus the prefix const tempId: string = String(o.datafeed_id !== undefined ? o.datafeed_id : o.job_id); const dId = prefixDatafeedId(tempId, jobPrefix); - const datafeed = datafeeds.find(d => d.id === dId); + const datafeed = datafeeds.find((d) => d.id === dId); if (datafeed !== undefined) { delete o.job_id; delete o.datafeed_id; diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 8ccd359137b67..43581ad75fb17 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -142,7 +142,7 @@ export class DataVisualizer { // To avoid checking for the existence of too many aggregatable fields in one request, // split the check into multiple batches (max 200 fields per request). const batches: string[][] = [[]]; - _.each(aggregatableFields, field => { + _.each(aggregatableFields, (field) => { let lastArray: string[] = _.last(batches); if (lastArray.length === AGGREGATABLE_EXISTS_REQUEST_BATCH_SIZE) { lastArray = []; @@ -152,7 +152,7 @@ export class DataVisualizer { }); await Promise.all( - batches.map(async fields => { + batches.map(async (fields) => { const batchStats = await this.checkAggregatableFieldsExist( indexPatternTitle, query, @@ -173,7 +173,7 @@ export class DataVisualizer { ); await Promise.all( - nonAggregatableFields.map(async field => { + nonAggregatableFields.map(async (field) => { const existsInDocs = await this.checkNonAggregatableFieldExists( indexPatternTitle, query, @@ -217,7 +217,7 @@ export class DataVisualizer { // Batch up fields by type, getting stats for multiple fields at a time. const batches: Field[][] = []; const batchedFields: { [key: string]: Field[][] } = {}; - _.each(fields, field => { + _.each(fields, (field) => { if (field.fieldName === undefined) { // undefined fieldName is used for a document count request. // getDocumentCountStats requires timeField - don't add to batched requests if not defined @@ -238,13 +238,13 @@ export class DataVisualizer { } }); - _.each(batchedFields, lists => { + _.each(batchedFields, (lists) => { batches.push(...lists); }); let results: BatchStats[] = []; await Promise.all( - batches.map(async batch => { + batches.map(async (batch) => { let batchStats: BatchStats[] = []; const first = batch[0]; switch (first.type) { @@ -313,7 +313,7 @@ export class DataVisualizer { // Use an exists filter on the the field name to get // examples of the field, so cannot batch up. await Promise.all( - batch.map(async field => { + batch.map(async (field) => { const stats = await this.getFieldExamples( indexPatternTitle, query, @@ -492,7 +492,7 @@ export class DataVisualizer { ['aggregations', 'eventRate', 'buckets'], [] ); - _.each(dataByTimeBucket, dataForTime => { + _.each(dataByTimeBucket, (dataForTime) => { const time = dataForTime.key; buckets[time] = dataForTime.doc_count; }); @@ -867,7 +867,7 @@ export class DataVisualizer { [...aggsPath, `${safeFieldName}_values`, 'buckets'], [] ); - _.each(valueBuckets, bucket => { + _.each(valueBuckets, (bucket) => { stats[`${bucket.key_as_string}Count`] = bucket.doc_count; }); diff --git a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts index 6558d0d48ded9..d9d765028b123 100644 --- a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts +++ b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts @@ -42,7 +42,7 @@ export function fieldsServiceProvider(callAsCurrentUser: APICaller) { fields: fieldNames, }); const aggregatableFields: string[] = []; - fieldNames.forEach(fieldName => { + fieldNames.forEach((fieldName) => { const fieldInfo = fieldCapsResp.fields[fieldName]; const typeKeys = fieldInfo !== undefined ? Object.keys(fieldInfo) : []; if (typeKeys.length > 0) { @@ -88,7 +88,7 @@ export function fieldsServiceProvider(callAsCurrentUser: APICaller) { ) ?? {}; // No need to perform aggregation over the cached fields - const fieldsToAgg = aggregatableFields.filter(field => !cachedValues.hasOwnProperty(field)); + const fieldsToAgg = aggregatableFields.filter((field) => !cachedValues.hasOwnProperty(field)); if (fieldsToAgg.length === 0) { return cachedValues; @@ -276,7 +276,7 @@ export function fieldsServiceProvider(callAsCurrentUser: APICaller) { ) ?? {}; // No need to perform aggregation over the cached fields - const fieldsToAgg = aggregatableFields.filter(field => !cachedValues.hasOwnProperty(field)); + const fieldsToAgg = aggregatableFields.filter((field) => !cachedValues.hasOwnProperty(field)); if (fieldsToAgg.length === 0) { return cachedValues; diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts index 9d7009955124f..afe699cc7fecf 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts @@ -175,7 +175,5 @@ export function importDataProvider(callAsCurrentUser: APICaller) { } function generateId() { - return Math.random() - .toString(36) - .substr(2, 9); + return Math.random().toString(36).substr(2, 9); } diff --git a/x-pack/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts index 42440057a2d1a..11208b4d941f3 100644 --- a/x-pack/plugins/ml/server/models/filter/filter_manager.ts +++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts @@ -174,16 +174,16 @@ export class FilterManager { buildFiltersInUse(jobsList: PartialJob[]) { // Build a map of filter_ids against jobs and detectors using that filter. const filtersInUse: FiltersInUse = {}; - jobsList.forEach(job => { + jobsList.forEach((job) => { const detectors = job.analysis_config.detectors; - detectors.forEach(detector => { + detectors.forEach((detector) => { if (detector.custom_rules) { const rules = detector.custom_rules; - rules.forEach(rule => { + rules.forEach((rule) => { if (rule.scope) { const ruleScope: DetectorRuleScope = rule.scope; const scopeFields = Object.keys(ruleScope); - scopeFields.forEach(scopeField => { + scopeFields.forEach((scopeField) => { const filter = ruleScope[scopeField]; const filterId = filter.filter_id; if (filtersInUse[filterId] === undefined) { diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js index 2cdfc0ef4f4c5..6b782f8652363 100644 --- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js +++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js @@ -113,7 +113,7 @@ export function jobAuditMessagesProvider(callAsCurrentUser) { let messages = []; if (resp.hits.total !== 0) { - messages = resp.hits.hits.map(hit => hit._source); + messages = resp.hits.hits.map((hit) => hit._source); } return messages; } catch (e) { @@ -210,14 +210,14 @@ export function jobAuditMessagesProvider(callAsCurrentUser) { messagesPerJob = resp.aggregations.levelsPerJob.buckets; } - messagesPerJob.forEach(job => { + messagesPerJob.forEach((job) => { // ignore system messages (id==='') if (job.key !== '' && job.levels && job.levels.buckets && job.levels.buckets.length) { let highestLevel = 0; let highestLevelText = ''; let msgTime = 0; - job.levels.buckets.forEach(level => { + job.levels.buckets.forEach((level) => { const label = level.key; // note the highest message level if (LEVEL[label] > highestLevel) { @@ -227,7 +227,7 @@ export function jobAuditMessagesProvider(callAsCurrentUser) { level.latestMessage.buckets && level.latestMessage.buckets.length ) { - level.latestMessage.buckets.forEach(msg => { + level.latestMessage.buckets.forEach((msg) => { // there should only be one result here. highestLevelText = msg.key; diff --git a/x-pack/plugins/ml/server/models/job_service/groups.ts b/x-pack/plugins/ml/server/models/job_service/groups.ts index e44609bc66711..56757e4cc3ff7 100644 --- a/x-pack/plugins/ml/server/models/job_service/groups.ts +++ b/x-pack/plugins/ml/server/models/job_service/groups.ts @@ -35,10 +35,10 @@ export function groupsProvider(callAsCurrentUser: APICaller) { ]); if (jobs) { - jobs.forEach(job => { + jobs.forEach((job) => { jobIds[job.job_id] = null; if (job.groups !== undefined) { - job.groups.forEach(g => { + job.groups.forEach((g) => { if (groups[g] === undefined) { groups[g] = { id: g, @@ -53,8 +53,8 @@ export function groupsProvider(callAsCurrentUser: APICaller) { }); } if (calendars) { - calendars.forEach(cal => { - cal.job_ids.forEach(jId => { + calendars.forEach((cal) => { + cal.job_ids.forEach((jId) => { // don't include _all in the calendar groups list if (jId !== GLOBAL_CALENDAR && jobIds[jId] === undefined) { if (groups[jId] === undefined) { @@ -71,7 +71,7 @@ export function groupsProvider(callAsCurrentUser: APICaller) { }); } - return Object.keys(groups).map(g => groups[g]); + return Object.keys(groups).map((g) => groups[g]); } async function updateGroups(jobs: Job[]) { diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index 225cd43e411a4..5503169f2d371 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -130,7 +130,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { async function jobsSummary(jobIds: string[] = []) { const fullJobsList: CombinedJobWithStats[] = await createFullJobsList(); - const fullJobsIds = fullJobsList.map(job => job.job_id); + const fullJobsIds = fullJobsList.map((job) => job.job_id); const auditMessages: AuditMessage[] = await getAuditMessagesSummary(fullJobsIds); const auditMessagesByJob = auditMessages.reduce((acc, cur) => { acc[cur.job_id] = cur; @@ -141,7 +141,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { defaultMessage: 'deleting', }); - const jobs = fullJobsList.map(job => { + const jobs = fullJobsList.map((job) => { const hasDatafeed = typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0; const dataCounts = job.data_counts; @@ -169,7 +169,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { nodeName: job.node ? job.node.name : undefined, deleting: job.deleting || undefined, }; - if (jobIds.find(j => j === tempJob.id)) { + if (jobIds.find((j) => j === tempJob.id)) { tempJob.fullJob = job; } const auditMessage = auditMessagesByJob[tempJob.id]; @@ -193,7 +193,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { const fullJobsList = await createFullJobsList(); const jobsMap: { [id: string]: string[] } = {}; - const jobs = fullJobsList.map(job => { + const jobs = fullJobsList.map((job) => { jobsMap[job.job_id] = job.groups || []; const hasDatafeed = typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0; @@ -267,10 +267,10 @@ export function jobsProvider(callAsCurrentUser: APICaller) { >(requests); if (datafeedResults && datafeedResults.datafeeds) { - datafeedResults.datafeeds.forEach(datafeed => { + datafeedResults.datafeeds.forEach((datafeed) => { if (datafeedStatsResults && datafeedStatsResults.datafeeds) { const datafeedStats = datafeedStatsResults.datafeeds.find( - ds => ds.datafeed_id === datafeed.datafeed_id + (ds) => ds.datafeed_id === datafeed.datafeed_id ); if (datafeedStats) { datafeeds[datafeed.job_id] = { ...datafeed, ...datafeedStats }; @@ -283,11 +283,11 @@ export function jobsProvider(callAsCurrentUser: APICaller) { // used for assigning calendars to jobs when a calendar has // only been attached to a group if (jobResults && jobResults.jobs) { - jobResults.jobs.forEach(job => { + jobResults.jobs.forEach((job) => { calendarsByJobId[job.job_id] = []; if (job.groups !== undefined) { - job.groups.forEach(gId => { + job.groups.forEach((gId) => { if (groups[gId] === undefined) { groups[gId] = []; } @@ -299,12 +299,12 @@ export function jobsProvider(callAsCurrentUser: APICaller) { // assign calendars to jobs if (calendarResults) { - calendarResults.forEach(cal => { - cal.job_ids.forEach(id => { + calendarResults.forEach((cal) => { + cal.job_ids.forEach((id) => { if (id === GLOBAL_CALENDAR) { globalCalendars.push(cal.calendar_id); } else if (groups[id]) { - groups[id].forEach(jId => { + groups[id].forEach((jId) => { if (calendarsByJobId[jId] !== undefined) { calendarsByJobId[jId].push(cal.calendar_id); } @@ -327,7 +327,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { // create jobs objects containing job stats, datafeeds, datafeed stats and calendars if (jobResults && jobResults.jobs) { - jobResults.jobs.forEach(job => { + jobResults.jobs.forEach((job) => { let tempJob = job as CombinedJobWithStats; const calendars: string[] = [ @@ -339,7 +339,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { } if (jobStatsResults && jobStatsResults.jobs) { - const jobStats = jobStatsResults.jobs.find(js => js.job_id === tempJob.job_id); + const jobStats = jobStatsResults.jobs.find((js) => js.job_id === tempJob.job_id); if (jobStats !== undefined) { tempJob = { ...tempJob, ...jobStats }; if (jobStats.node) { @@ -375,9 +375,9 @@ export function jobsProvider(callAsCurrentUser: APICaller) { const jobIds = []; try { const tasksList = await callAsCurrentUser('tasks.list', { actions, detailed }); - Object.keys(tasksList.nodes).forEach(nodeId => { + Object.keys(tasksList.nodes).forEach((nodeId) => { const tasks = tasksList.nodes[nodeId].tasks; - Object.keys(tasks).forEach(taskId => { + Object.keys(tasks).forEach((taskId) => { jobIds.push(tasks[taskId].description.replace(/^delete-job-/, '')); }); }); @@ -385,7 +385,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { // if the user doesn't have permission to load the task list, // use the jobs list to get the ids of deleting jobs const { jobs } = await callAsCurrentUser('ml.jobs'); - jobIds.push(...jobs.filter(j => j.deleting === true).map(j => j.job_id)); + jobIds.push(...jobs.filter((j) => j.deleting === true).map((j) => j.job_id)); } return { jobIds }; } @@ -401,17 +401,17 @@ export function jobsProvider(callAsCurrentUser: APICaller) { const results: { [id: string]: boolean } = {}; if (jobsInfo.count > 0) { - const allJobIds = jobsInfo.jobs.map(job => job.job_id); + const allJobIds = jobsInfo.jobs.map((job) => job.job_id); // Check if each of the supplied IDs match existing jobs. - jobIds.forEach(jobId => { + jobIds.forEach((jobId) => { // Create a Regex for each supplied ID as wildcard * is allowed. const regexp = new RegExp(`^${jobId.replace(/\*+/g, '.*')}$`); - const exists = allJobIds.some(existsJobId => regexp.test(existsJobId)); + const exists = allJobIds.some((existsJobId) => regexp.test(existsJobId)); results[jobId] = exists; }); } else { - jobIds.forEach(jobId => { + jobIds.forEach((jobId) => { results[jobId] = false; }); } @@ -422,9 +422,9 @@ export function jobsProvider(callAsCurrentUser: APICaller) { async function getAllJobAndGroupIds() { const { getAllGroups } = groupsProvider(callAsCurrentUser); const jobs = await callAsCurrentUser('ml.jobs'); - const jobIds = jobs.jobs.map(job => job.job_id); + const jobIds = jobs.jobs.map((job) => job.job_id); const groups = await getAllGroups(); - const groupIds = groups.map(group => group.id); + const groupIds = groups.map((group) => group.id); return { jobIds, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index b209dc5681563..bf0d79b3ec072 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -92,7 +92,7 @@ export function categorizationExamplesProvider( return { examples: examplesWithTokens }; } catch (error) { validationResults.createTooManyTokensResult(error, halfChunkSize); - return { examples: halfExamples.map(e => ({ text: e, tokens: [] })) }; + return { examples: halfExamples.map((e) => ({ text: e, tokens: [] })) }; } } } @@ -119,10 +119,10 @@ export function categorizationExamplesProvider( }, }); - const lengths = examples.map(e => e.length); - const sumLengths = lengths.map((s => (a: number) => (s += a))(0)); + const lengths = examples.map((e) => e.length); + const sumLengths = lengths.map(((s) => (a: number) => (s += a))(0)); - const tokensPerExample: Token[][] = examples.map(e => []); + const tokensPerExample: Token[][] = examples.map((e) => []); tokens.forEach((t, i) => { for (let g = 0; g < sumLengths.length; g++) { @@ -193,7 +193,7 @@ export function categorizationExamplesProvider( // sort back into original order and remove origIndex property const processedExamples = filteredExamples .sort((a, b) => a.origIndex - b.origIndex) - .map(e => ({ text: e.text, tokens: e.tokens })); + .map((e) => ({ text: e.text, tokens: e.tokens })); return { overallValidStatus: validationResults.overallResult, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts index 3361cc454e2b7..13c5f107972eb 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts @@ -125,7 +125,7 @@ export function topCategoriesProvider(callWithRequest: callWithRequestType) { const catCounts = await getTopCategoryCounts(jobId, numberOfCategories); const categories = await getCategories( jobId, - catCounts.map(c => c.id), + catCounts.map((c) => c.id), catCounts.length || numberOfCategories ); @@ -149,7 +149,7 @@ export function topCategoriesProvider(callWithRequest: callWithRequestType) { } else { return { total, - categories: categories.map(category => { + categories: categories.map((category) => { return { category, }; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts index e3b37fffa9c77..4b90283a3a966 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts @@ -28,17 +28,19 @@ export class ValidationResults { } public get overallResult() { - if (this._results.some(c => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID)) { + if (this._results.some((c) => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID)) { return CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; } - if (this._results.some(c => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID)) { + if ( + this._results.some((c) => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID) + ) { return CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID; } return CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID; } private _resultExists(id: VALIDATION_RESULT) { - return this._results.some(r => r.id === id); + return this._results.some((r) => r.id === id); } public createTokenCountResult(examples: CategoryFieldExample[], sampleSize: number) { @@ -53,7 +55,7 @@ export class ValidationResults { return; } - const validExamplesSize = examples.filter(e => e.tokens.length >= VALID_TOKEN_COUNT).length; + const validExamplesSize = examples.filter((e) => e.tokens.length >= VALID_TOKEN_COUNT).length; const percentValid = sampleSize === 0 ? 0 : validExamplesSize / sampleSize; let valid = CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID; @@ -119,7 +121,7 @@ export class ValidationResults { } public createNullValueResult(examples: Array) { - const nullCount = examples.filter(e => e === null).length; + const nullCount = examples.filter((e) => e === null).length; if (nullCount / examples.length >= NULL_COUNT_PERCENT_LIMIT) { this._results.push({ diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts index a198fac4e3fef..4872f0f5e0ea4 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts @@ -50,7 +50,7 @@ export function newJobLineChartProvider(callWithRequest: callWithRequestType) { const results = await callWithRequest('search', json); return processSearchResults( results, - aggFieldNamePairs.map(af => af.field) + aggFieldNamePairs.map((af) => af.field) ); } diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts index ee35f13c44ee6..26609bdcc8f7d 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -55,7 +55,7 @@ export function newJobPopulationChartProvider(callWithRequest: callWithRequestTy const results = await callWithRequest('search', json); return processSearchResults( results, - aggFieldNamePairs.map(af => af.field) + aggFieldNamePairs.map((af) => af.field) ); } catch (error) { return { error }; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index 405f645ca0e1d..a5ed4a18bf51c 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -149,27 +149,27 @@ async function combineFieldsAndAggs( const isRollup = Object.keys(rollupFields).length > 0; const mix = mixFactory(isRollup, rollupFields); - aggs.forEach(a => { + aggs.forEach((a) => { if (a.type === METRIC_AGG_TYPE && a.fields !== undefined) { switch (a.id) { case ML_JOB_AGGREGATION.LAT_LONG: - geoFields.forEach(f => mix(f, a)); + geoFields.forEach((f) => mix(f, a)); break; case ML_JOB_AGGREGATION.INFO_CONTENT: case ML_JOB_AGGREGATION.HIGH_INFO_CONTENT: case ML_JOB_AGGREGATION.LOW_INFO_CONTENT: - textFields.forEach(f => mix(f, a)); + textFields.forEach((f) => mix(f, a)); case ML_JOB_AGGREGATION.DISTINCT_COUNT: case ML_JOB_AGGREGATION.HIGH_DISTINCT_COUNT: case ML_JOB_AGGREGATION.LOW_DISTINCT_COUNT: // distinct count (i.e. cardinality) takes keywords, ips // as well as numerical fields - keywordFields.forEach(f => mix(f, a)); - ipFields.forEach(f => mix(f, a)); + keywordFields.forEach((f) => mix(f, a)); + ipFields.forEach((f) => mix(f, a)); // note, no break to fall through to add numerical fields. default: // all other aggs take numerical fields - numericalFields.forEach(f => { + numericalFields.forEach((f) => { mix(f, a); }); break; @@ -186,7 +186,7 @@ async function combineFieldsAndAggs( // remove fields that have no aggs associated to them, unless they are date fields function filterFields(fields: Field[]): Field[] { return fields.filter( - f => f.aggs && (f.aggs.length > 0 || (f.aggs.length === 0 && f.type === ES_FIELD_TYPES.DATE)) + (f) => f.aggs && (f.aggs.length > 0 || (f.aggs.length === 0 && f.type === ES_FIELD_TYPES.DATE)) ); } @@ -196,7 +196,7 @@ function mixFactory(isRollup: boolean, rollupFields: RollupFields) { return function mix(field: Field, agg: Aggregation): void { if ( isRollup === false || - (rollupFields[field.id] && rollupFields[field.id].find(f => f.agg === agg.dslName)) + (rollupFields[field.id] && rollupFields[field.id].find((f) => f.agg === agg.dslName)) ) { if (field.aggs !== undefined) { field.aggs.push(agg); @@ -210,14 +210,14 @@ function mixFactory(isRollup: boolean, rollupFields: RollupFields) { function combineAllRollupFields(rollupConfigs: RollupJob[]): RollupFields { const rollupFields: RollupFields = {}; - rollupConfigs.forEach(conf => { - Object.keys(conf.fields).forEach(fieldName => { + rollupConfigs.forEach((conf) => { + Object.keys(conf.fields).forEach((fieldName) => { if (rollupFields[fieldName] === undefined) { rollupFields[fieldName] = conf.fields[fieldName]; } else { const aggs = conf.fields[fieldName]; - aggs.forEach(agg => { - if (rollupFields[fieldName].find(f => f.agg === agg.agg) === null) { + aggs.forEach((agg) => { + if (rollupFields[fieldName].find((f) => f.agg === agg.agg) === null) { rollupFields[fieldName].push(agg); } }); @@ -228,20 +228,20 @@ function combineAllRollupFields(rollupConfigs: RollupJob[]): RollupFields { } function getKeywordFields(fields: Field[]): Field[] { - return fields.filter(f => f.type === ES_FIELD_TYPES.KEYWORD); + return fields.filter((f) => f.type === ES_FIELD_TYPES.KEYWORD); } function getTextFields(fields: Field[]): Field[] { - return fields.filter(f => f.type === ES_FIELD_TYPES.TEXT); + return fields.filter((f) => f.type === ES_FIELD_TYPES.TEXT); } function getIpFields(fields: Field[]): Field[] { - return fields.filter(f => f.type === ES_FIELD_TYPES.IP); + return fields.filter((f) => f.type === ES_FIELD_TYPES.IP); } function getNumericalFields(fields: Field[]): Field[] { return fields.filter( - f => + (f) => f.type === ES_FIELD_TYPES.LONG || f.type === ES_FIELD_TYPES.INTEGER || f.type === ES_FIELD_TYPES.SHORT || @@ -255,6 +255,6 @@ function getNumericalFields(fields: Field[]): Field[] { function getGeoFields(fields: Field[]): Field[] { return fields.filter( - f => f.type === ES_FIELD_TYPES.GEO_POINT || f.type === ES_FIELD_TYPES.GEO_SHAPE + (f) => f.type === ES_FIELD_TYPES.GEO_POINT || f.type === ES_FIELD_TYPES.GEO_SHAPE ); } diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts index f1af7614b4232..02fef16a384d0 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts @@ -45,7 +45,7 @@ describe('job_service - job_caps', () => { }); describe('farequote newJobCaps()', () => { - it('can get job caps for index pattern', async done => { + it('can get job caps for index pattern', async (done) => { const indexPattern = 'farequote-*'; const isRollup = false; const { newJobCaps } = newJobCapsProvider(callWithRequestNonRollupMock); @@ -54,7 +54,7 @@ describe('job_service - job_caps', () => { done(); }); - it('can get rollup job caps for non rollup index pattern', async done => { + it('can get rollup job caps for non rollup index pattern', async (done) => { const indexPattern = 'farequote-*'; const isRollup = true; const { newJobCaps } = newJobCapsProvider(callWithRequestNonRollupMock); @@ -65,7 +65,7 @@ describe('job_service - job_caps', () => { }); describe('cloudwatch newJobCaps()', () => { - it('can get rollup job caps for rollup index pattern', async done => { + it('can get rollup job caps for rollup index pattern', async (done) => { const indexPattern = 'cloud_roll_index'; const isRollup = true; const { newJobCaps } = newJobCapsProvider(callWithRequestRollupMock); @@ -74,7 +74,7 @@ describe('job_service - job_caps', () => { done(); }); - it('can get non rollup job caps for rollup index pattern', async done => { + it('can get non rollup job caps for rollup index pattern', async (done) => { const indexPattern = 'cloud_roll_index'; const isRollup = false; const { newJobCaps } = newJobCapsProvider(callWithRequestRollupMock); diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts index 3a9d979ccb22c..a0ab4b5cf4e3e 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts @@ -43,15 +43,15 @@ export function newJobCapsProvider(callWithRequest: any) { // map of ids to allow it to be stringified for transportation // over the network. function convertForStringify(aggs: Aggregation[], fields: Field[]): void { - fields.forEach(f => { - f.aggIds = f.aggs ? f.aggs.map(a => a.id) : []; + fields.forEach((f) => { + f.aggIds = f.aggs ? f.aggs.map((a) => a.id) : []; delete f.aggs; }); - aggs.forEach(a => { + aggs.forEach((a) => { if (a.fields !== undefined) { // if the aggregation supports fields, i.e. it's fields list isn't undefined, // create a list of field ids - a.fieldIds = a.fields.map(f => f.id); + a.fieldIds = a.fields.map((f) => f.id); } delete a.fields; }); diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts index 2845006dae05f..f7d846839503d 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts @@ -67,7 +67,7 @@ async function loadRollupIndexPattern( }); const obj = resp.saved_objects.find( - r => + (r) => r.attributes && r.attributes.type === 'rollup' && r.attributes.title === indexPattern && diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts index 3a86693e91828..f9999a06f38ed 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts @@ -11,7 +11,7 @@ import { JobValidationMessage } from '../../../common/constants/messages'; // mock callWithRequest const callWithRequest: APICaller = (method: string) => { - return new Promise(resolve => { + return new Promise((resolve) => { if (method === 'fieldCaps') { resolve({ fields: [] }); return; @@ -36,8 +36,8 @@ describe('ML - validateJob', () => { job: { analysis_config: { detectors: [] } }, } as unknown) as ValidateJobPayload; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([ 'job_id_empty', @@ -49,7 +49,7 @@ describe('ML - validateJob', () => { }); const jobIdTests = (testIds: string[], messageId: string) => { - const promises = testIds.map(id => { + const promises = testIds.map((id) => { const payload = ({ job: { analysis_config: { detectors: [] }, @@ -61,11 +61,11 @@ describe('ML - validateJob', () => { }); }); - return Promise.all(promises).then(testResults => { - testResults.forEach(messages => { + return Promise.all(promises).then((testResults) => { + testResults.forEach((messages) => { expect(Array.isArray(messages)).toBe(true); if (Array.isArray(messages)) { - const ids = messages.map(m => m.id); + const ids = messages.map((m) => m.id); expect(ids.includes(messageId)).toBe(true); } }); @@ -77,8 +77,8 @@ describe('ML - validateJob', () => { job: { analysis_config: { detectors: [] }, groups: testIds }, } as unknown) as ValidateJobPayload; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids.includes(messageId)).toBe(true); }); }; @@ -113,7 +113,7 @@ describe('ML - validateJob', () => { }); const bucketSpanFormatTests = (testFormats: string[], messageId: string) => { - const promises = testFormats.map(format => { + const promises = testFormats.map((format) => { const payload = ({ job: { analysis_config: { bucket_span: format, detectors: [] } }, } as unknown) as ValidateJobPayload; @@ -122,11 +122,11 @@ describe('ML - validateJob', () => { }); }); - return Promise.all(promises).then(testResults => { - testResults.forEach(messages => { + return Promise.all(promises).then((testResults) => { + testResults.forEach((messages) => { expect(Array.isArray(messages)).toBe(true); if (Array.isArray(messages)) { - const ids = messages.map(m => m.id); + const ids = messages.map((m) => m.id); expect(ids.includes(messageId)).toBe(true); } }); @@ -156,8 +156,8 @@ describe('ML - validateJob', () => { function: undefined, }); - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids.includes('detectors_function_empty')).toBe(true); }); }); @@ -170,8 +170,8 @@ describe('ML - validateJob', () => { function: 'count', }); - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids.includes('detectors_function_not_empty')).toBe(true); }); }); @@ -182,8 +182,8 @@ describe('ML - validateJob', () => { fields: {}, } as unknown) as ValidateJobPayload; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids.includes('index_fields_invalid')).toBe(true); }); }); @@ -194,8 +194,8 @@ describe('ML - validateJob', () => { fields: { testField: {} }, } as unknown) as ValidateJobPayload; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids.includes('index_fields_valid')).toBe(true); }); }); @@ -218,7 +218,7 @@ describe('ML - validateJob', () => { fields: { testField: {} }, }); - it('throws an error because job.analysis_config.influencers is not an Array', done => { + it('throws an error because job.analysis_config.influencers is not an Array', (done) => { const payload = getBasicPayload() as any; delete payload.job.analysis_config.influencers; @@ -234,8 +234,8 @@ describe('ML - validateJob', () => { it('detect duplicate detectors', () => { const payload = getBasicPayload() as any; payload.job.analysis_config.detectors.push({ function: 'count' }); - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([ 'job_id_valid', 'detectors_function_not_empty', @@ -257,8 +257,8 @@ describe('ML - validateJob', () => { { function: 'count', by_field_name: 'airline' }, { function: 'count', partition_field_name: 'airline' }, ]; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([ 'job_id_valid', 'detectors_function_not_empty', @@ -272,8 +272,8 @@ describe('ML - validateJob', () => { // Failing https://github.com/elastic/kibana/issues/65865 it('basic validation passes, extended checks return some messages', () => { const payload = getBasicPayload(); - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([ 'job_id_valid', 'detectors_function_not_empty', @@ -305,8 +305,8 @@ describe('ML - validateJob', () => { fields: { testField: {} }, }; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([ 'job_id_valid', 'detectors_function_not_empty', @@ -338,8 +338,8 @@ describe('ML - validateJob', () => { fields: { testField: {} }, }; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([ 'job_id_valid', 'detectors_function_not_empty', @@ -381,8 +381,8 @@ describe('ML - validateJob', () => { fields: { testField: {} }, }; - return validateJob(callWithRequest, payload).then(messages => { - const ids = messages.map(m => m.id); + return validateJob(callWithRequest, payload).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([ 'job_id_valid', 'detectors_function_not_empty', @@ -400,18 +400,18 @@ describe('ML - validateJob', () => { const docsTestPayload = getBasicPayload() as any; docsTestPayload.job.analysis_config.detectors = [{ function: 'count', by_field_name: 'airline' }]; it('creates a docs url pointing to the current docs version', () => { - return validateJob(callWithRequest, docsTestPayload).then(messages => { + return validateJob(callWithRequest, docsTestPayload).then((messages) => { const message = messages[ - messages.findIndex(m => m.id === 'field_not_aggregatable') + messages.findIndex((m) => m.id === 'field_not_aggregatable') ] as JobValidationMessage; expect(message.url!.search('/current/')).not.toBe(-1); }); }); it('creates a docs url pointing to the master docs version', () => { - return validateJob(callWithRequest, docsTestPayload, 'master').then(messages => { + return validateJob(callWithRequest, docsTestPayload, 'master').then((messages) => { const message = messages[ - messages.findIndex(m => m.id === 'field_not_aggregatable') + messages.findIndex((m) => m.id === 'field_not_aggregatable') ] as JobValidationMessage; expect(message.url!.search('/master/')).not.toBe(-1); }); diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts index f852de785c70a..9d7154bbbb304 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts @@ -58,7 +58,7 @@ export async function validateJob( if (basicValidation.valid === true) { // remove basic success messages from tests // where we run additional extended tests. - const filteredBasicValidationMessages = basicValidation.messages.filter(m => { + const filteredBasicValidationMessages = basicValidation.messages.filter((m) => { return m.id !== 'bucket_span_valid'; }); @@ -83,7 +83,7 @@ export async function validateJob( // so we can decide later whether certain additional tests should be run const cardinalityMessages = await validateCardinality(callWithRequest, job); validationMessages.push(...cardinalityMessages); - const cardinalityError = cardinalityMessages.some(m => { + const cardinalityError = cardinalityMessages.some((m) => { return messages[m.id as MessageId].status === VALIDATION_STATUS.ERROR; }); @@ -111,7 +111,7 @@ export async function validateJob( validationMessages.push({ id: 'skipped_extended_tests' }); } - return uniqWithIsEqual(validationMessages).map(message => { + return uniqWithIsEqual(validationMessages).map((message) => { const messageId = message.id as MessageId; const messageDef = messages[messageId] as JobValidationMessageDef; if (typeof messageDef !== 'undefined') { diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js index 883f1aed1209e..46d05d3cf7637 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js +++ b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js @@ -13,7 +13,7 @@ import { validateJobObject } from './validate_job_object'; const BUCKET_SPAN_HIGH_THRESHOLD = 1; -const wrapQuery = query => ({ +const wrapQuery = (query) => ({ bool: { must: [query], must_not: [], @@ -28,7 +28,7 @@ const wrapQuery = query => ({ // 3 * (30 - 10) + 30 - 30 + 60 - 30 = 90, // 3 * (60 - 10) + 60 - 30 + 60 - 60 = 180] // 70 is the lowest so 10m would be picked -const pickBucketSpan = bucketSpans => { +const pickBucketSpan = (bucketSpans) => { if (bucketSpans.length === 1) { return bucketSpans[0]; } @@ -39,10 +39,10 @@ const pickBucketSpan = bucketSpans => { }, 0); }; - const spansMs = bucketSpans.map(span => span.ms); - const sumDistances = spansMs.map(ms => getSumDistance(spansMs, ms)); + const spansMs = bucketSpans.map((span) => span.ms); + const sumDistances = spansMs.map((ms) => getSumDistance(spansMs, ms)); const minSumDistance = Math.min(...sumDistances); - const i = sumDistances.findIndex(d => d === minSumDistance); + const i = sumDistances.findIndex((d) => d === minSumDistance); return bucketSpans[i]; }; @@ -105,7 +105,7 @@ export async function validateBucketSpan( const estimatorConfigs = []; - job.analysis_config.detectors.forEach(detector => { + job.analysis_config.detectors.forEach((detector) => { const data = getRequestData(); const aggType = mlFunctionToESAggregation(detector.function); const fieldName = typeof detector.field_name === 'undefined' ? null : detector.field_name; @@ -119,8 +119,8 @@ export async function validateBucketSpan( // do the actual bucket span estimation try { - const estimations = estimatorConfigs.map(data => { - return new Promise(resolve => { + const estimations = estimatorConfigs.map((data) => { + return new Promise((resolve) => { estimateBucketSpanFactory( callWithRequest, callAsInternalUser, @@ -131,7 +131,7 @@ export async function validateBucketSpan( // but isn't able to come up with a bucket span estimation. // this doesn't trigger a HTTP error but an object with an error message. // triggering a HTTP error would be too severe for this case. - .catch(resp => { + .catch((resp) => { resolve({ error: true, message: resp, @@ -142,7 +142,7 @@ export async function validateBucketSpan( // run the estimations, filter valid results, then pick a bucket span. const results = await Promise.all(estimations); - const bucketSpans = results.filter(result => result.name && result.ms); + const bucketSpans = results.filter((result) => result.name && result.ms); if (bucketSpans.length > 0) { const bucketSpan = pickBucketSpan(bucketSpans); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts index 84f865879d67f..8d77fd5a1fd0e 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts @@ -23,28 +23,28 @@ import mockItSearchResponse from './__mocks__/mock_it_search_response.json'; // mock callWithRequestFactory const callWithRequestFactory = (mockSearchResponse: any) => { return () => { - return new Promise(resolve => { + return new Promise((resolve) => { resolve(mockSearchResponse); }); }; }; describe('ML - validateBucketSpan', () => { - it('called without arguments', done => { + it('called without arguments', (done) => { validateBucketSpan(callWithRequestFactory(mockFareQuoteSearchResponse)).then( () => done(new Error('Promise should not resolve for this test without job argument.')), () => done() ); }); - it('called with non-valid job argument #1, missing datafeed_config', done => { + it('called with non-valid job argument #1, missing datafeed_config', (done) => { validateBucketSpan(callWithRequestFactory(mockFareQuoteSearchResponse), {}).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() ); }); - it('called with non-valid job argument #2, missing datafeed_config.indices', done => { + it('called with non-valid job argument #2, missing datafeed_config.indices', (done) => { validateBucketSpan(callWithRequestFactory(mockFareQuoteSearchResponse), { datafeed_config: {}, }).then( @@ -53,7 +53,7 @@ describe('ML - validateBucketSpan', () => { ); }); - it('called with non-valid job argument #3, missing data_description', done => { + it('called with non-valid job argument #3, missing data_description', (done) => { const job = { datafeed_config: { indices: [] } }; validateBucketSpan(callWithRequestFactory(mockFareQuoteSearchResponse), job).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), @@ -61,7 +61,7 @@ describe('ML - validateBucketSpan', () => { ); }); - it('called with non-valid job argument #4, missing data_description.time_field', done => { + it('called with non-valid job argument #4, missing data_description.time_field', (done) => { const job = { datafeed_config: { indices: [] }, data_description: {} }; validateBucketSpan(callWithRequestFactory(mockFareQuoteSearchResponse), job).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), @@ -69,7 +69,7 @@ describe('ML - validateBucketSpan', () => { ); }); - it('called with non-valid job argument #5, missing analysis_config.influencers', done => { + it('called with non-valid job argument #5, missing analysis_config.influencers', (done) => { const job = { datafeed_config: { indices: [] }, data_description: { time_field: '@timestamp' }, @@ -89,7 +89,7 @@ describe('ML - validateBucketSpan', () => { return validateBucketSpan(callWithRequestFactory(mockFareQuoteSearchResponse), job).then( (messages: JobValidationMessage[]) => { - const ids = messages.map(m => m.id); + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([]); } ); @@ -114,7 +114,7 @@ describe('ML - validateBucketSpan', () => { job, duration ).then((messages: JobValidationMessage[]) => { - const ids = messages.map(m => m.id); + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['success_bucket_span']); }); }); @@ -128,7 +128,7 @@ describe('ML - validateBucketSpan', () => { job, duration ).then((messages: JobValidationMessage[]) => { - const ids = messages.map(m => m.id); + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['bucket_span_high']); }); }); @@ -149,20 +149,20 @@ describe('ML - validateBucketSpan', () => { return validateBucketSpan(callWithRequestFactory(mockSearchResponse), job, {}).then( (messages: JobValidationMessage[]) => { - const ids = messages.map(m => m.id); + const ids = messages.map((m) => m.id); test(ids); } ); }; it('farequote count detector, bucket span estimation matches 15m', () => { - return testBucketSpan('15m', mockFareQuoteSearchResponse, ids => { + return testBucketSpan('15m', mockFareQuoteSearchResponse, (ids) => { expect(ids).toStrictEqual(['success_bucket_span']); }); }); it('farequote count detector, bucket span estimation does not match 1m', () => { - return testBucketSpan('1m', mockFareQuoteSearchResponse, ids => { + return testBucketSpan('1m', mockFareQuoteSearchResponse, (ids) => { expect(ids).toStrictEqual(['bucket_span_estimation_mismatch']); }); }); @@ -172,7 +172,7 @@ describe('ML - validateBucketSpan', () => { // not many non-empty buckets. future work on bucket estimation and sparsity validation // should result in a lower bucket span estimation. it('it_ops_app_logs count detector, bucket span estimation matches 6h', () => { - return testBucketSpan('6h', mockItSearchResponse, ids => { + return testBucketSpan('6h', mockItSearchResponse, (ids) => { expect(ids).toStrictEqual(['success_bucket_span']); }); }); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts index e5111629f1182..13e5495aac4c4 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts @@ -35,21 +35,21 @@ const callWithRequestFactory = (responses: Record, fail = false): A }; describe('ML - validateCardinality', () => { - it('called without arguments', done => { + it('called without arguments', (done) => { validateCardinality(callWithRequestFactory(mockResponses)).then( () => done(new Error('Promise should not resolve for this test without job argument.')), () => done() ); }); - it('called with non-valid job argument #1, missing analysis_config', done => { + it('called with non-valid job argument #1, missing analysis_config', (done) => { validateCardinality(callWithRequestFactory(mockResponses), {} as CombinedJob).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() ); }); - it('called with non-valid job argument #2, missing datafeed_config', done => { + it('called with non-valid job argument #2, missing datafeed_config', (done) => { validateCardinality(callWithRequestFactory(mockResponses), { analysis_config: {}, } as CombinedJob).then( @@ -58,7 +58,7 @@ describe('ML - validateCardinality', () => { ); }); - it('called with non-valid job argument #3, missing datafeed_config.indices', done => { + it('called with non-valid job argument #3, missing datafeed_config.indices', (done) => { const job = { analysis_config: {}, datafeed_config: {} } as CombinedJob; validateCardinality(callWithRequestFactory(mockResponses), job).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), @@ -66,7 +66,7 @@ describe('ML - validateCardinality', () => { ); }); - it('called with non-valid job argument #4, missing data_description', done => { + it('called with non-valid job argument #4, missing data_description', (done) => { const job = ({ analysis_config: {}, datafeed_config: { indices: [] }, @@ -77,7 +77,7 @@ describe('ML - validateCardinality', () => { ); }); - it('called with non-valid job argument #5, missing data_description.time_field', done => { + it('called with non-valid job argument #5, missing data_description.time_field', (done) => { const job = ({ analysis_config: {}, data_description: {}, @@ -89,7 +89,7 @@ describe('ML - validateCardinality', () => { ); }); - it('called with non-valid job argument #6, missing analysis_config.influencers', done => { + it('called with non-valid job argument #6, missing analysis_config.influencers', (done) => { const job = ({ analysis_config: {}, datafeed_config: { indices: [] }, @@ -110,8 +110,8 @@ describe('ML - validateCardinality', () => { }, } as unknown) as CombinedJob; - return validateCardinality(callWithRequestFactory(mockResponses), job).then(messages => { - const ids = messages.map(m => m.id); + return validateCardinality(callWithRequestFactory(mockResponses), job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([]); }); }); @@ -143,8 +143,8 @@ describe('ML - validateCardinality', () => { return validateCardinality( callWithRequestFactory(mockCardinality), (job as unknown) as CombinedJob - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); test(ids); }); }; @@ -155,8 +155,8 @@ describe('ML - validateCardinality', () => { return validateCardinality( callWithRequestFactory(mockResponses), (job as unknown) as CombinedJob - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['field_not_aggregatable']); }); }); @@ -166,8 +166,8 @@ describe('ML - validateCardinality', () => { return validateCardinality( callWithRequestFactory(mockResponses), (job as unknown) as CombinedJob - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['success_cardinality']); }); }); @@ -175,8 +175,8 @@ describe('ML - validateCardinality', () => { it('field not aggregatable', () => { const job = getJobConfig('partition_field_name'); return validateCardinality(callWithRequestFactory({}), (job as unknown) as CombinedJob).then( - messages => { - const ids = messages.map(m => m.id); + (messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['field_not_aggregatable']); } ); @@ -191,50 +191,50 @@ describe('ML - validateCardinality', () => { return validateCardinality( callWithRequestFactory({}, true), (job as unknown) as CombinedJob - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['fields_not_aggregatable']); }); }); it('valid partition field cardinality', () => { - return testCardinality('partition_field_name', 50, ids => { + return testCardinality('partition_field_name', 50, (ids) => { expect(ids).toStrictEqual(['success_cardinality']); }); }); it('too high partition field cardinality', () => { - return testCardinality('partition_field_name', 1001, ids => { + return testCardinality('partition_field_name', 1001, (ids) => { expect(ids).toStrictEqual(['cardinality_partition_field']); }); }); it('valid by field cardinality', () => { - return testCardinality('by_field_name', 50, ids => { + return testCardinality('by_field_name', 50, (ids) => { expect(ids).toStrictEqual(['success_cardinality']); }); }); it('too high by field cardinality', () => { - return testCardinality('by_field_name', 1001, ids => { + return testCardinality('by_field_name', 1001, (ids) => { expect(ids).toStrictEqual(['cardinality_by_field']); }); }); it('valid over field cardinality', () => { - return testCardinality('over_field_name', 50, ids => { + return testCardinality('over_field_name', 50, (ids) => { expect(ids).toStrictEqual(['success_cardinality']); }); }); it('too low over field cardinality', () => { - return testCardinality('over_field_name', 9, ids => { + return testCardinality('over_field_name', 9, (ids) => { expect(ids).toStrictEqual(['cardinality_over_field_low']); }); }); it('too high over field cardinality', () => { - return testCardinality('over_field_name', 1000001, ids => { + return testCardinality('over_field_name', 1000001, (ids) => { expect(ids).toStrictEqual(['cardinality_over_field_high']); }); }); @@ -245,8 +245,8 @@ describe('ML - validateCardinality', () => { job.model_plot_config = { enabled: false }; const mockCardinality = _.cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; - return validateCardinality(callWithRequestFactory(mockCardinality), job).then(messages => { - const ids = messages.map(m => m.id); + return validateCardinality(callWithRequestFactory(mockCardinality), job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['success_cardinality']); }); }); @@ -256,8 +256,8 @@ describe('ML - validateCardinality', () => { job.model_plot_config = { enabled: true }; const mockCardinality = _.cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; - return validateCardinality(callWithRequestFactory(mockCardinality), job).then(messages => { - const ids = messages.map(m => m.id); + return validateCardinality(callWithRequestFactory(mockCardinality), job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['cardinality_model_plot_high']); }); }); @@ -267,8 +267,8 @@ describe('ML - validateCardinality', () => { job.model_plot_config = { enabled: false }; const mockCardinality = _.cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; - return validateCardinality(callWithRequestFactory(mockCardinality), job).then(messages => { - const ids = messages.map(m => m.id); + return validateCardinality(callWithRequestFactory(mockCardinality), job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['cardinality_by_field']); }); }); @@ -278,8 +278,8 @@ describe('ML - validateCardinality', () => { job.model_plot_config = { enabled: true }; const mockCardinality = _.cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; - return validateCardinality(callWithRequestFactory(mockCardinality), job).then(messages => { - const ids = messages.map(m => m.id); + return validateCardinality(callWithRequestFactory(mockCardinality), job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['cardinality_model_plot_high', 'cardinality_by_field']); }); }); @@ -289,8 +289,8 @@ describe('ML - validateCardinality', () => { job.model_plot_config = { enabled: true, terms: 'AAL,AAB' }; const mockCardinality = _.cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; - return validateCardinality(callWithRequestFactory(mockCardinality), job).then(messages => { - const ids = messages.map(m => m.id); + return validateCardinality(callWithRequestFactory(mockCardinality), job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['cardinality_by_field']); }); }); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts index 2ad483fb07ca7..62f272f26bb3f 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts @@ -62,13 +62,15 @@ const validateFactory = (callWithRequest: APICaller, job: CombinedJob): Validato >; const detectors = job.analysis_config.detectors; - const relevantDetectors = detectors.filter(detector => { + const relevantDetectors = detectors.filter((detector) => { return typeof detector[fieldName] !== 'undefined'; }); if (relevantDetectors.length > 0) { try { - const uniqueFieldNames = [...new Set(relevantDetectors.map(f => f[fieldName]))] as string[]; + const uniqueFieldNames = [ + ...new Set(relevantDetectors.map((f) => f[fieldName])), + ] as string[]; // use fieldCaps endpoint to get data about whether fields are aggregatable const fieldCaps = await callWithRequest('fieldCaps', { @@ -79,7 +81,7 @@ const validateFactory = (callWithRequest: APICaller, job: CombinedJob): Validato let aggregatableFieldNames: string[] = []; // parse fieldCaps to return an array of just the fields which are aggregatable if (typeof fieldCaps === 'object' && typeof fieldCaps.fields === 'object') { - aggregatableFieldNames = uniqueFieldNames.filter(field => { + aggregatableFieldNames = uniqueFieldNames.filter((field) => { if (typeof fieldCaps.fields[field] !== 'undefined') { const fieldType = Object.keys(fieldCaps.fields[field])[0]; return fieldCaps.fields[field][fieldType].aggregatable; @@ -96,9 +98,9 @@ const validateFactory = (callWithRequest: APICaller, job: CombinedJob): Validato job.data_description.time_field ); - uniqueFieldNames.forEach(uniqueFieldName => { + uniqueFieldNames.forEach((uniqueFieldName) => { const field = stats.aggregatableExistsFields.find( - fieldData => fieldData.fieldName === uniqueFieldName + (fieldData) => fieldData.fieldName === uniqueFieldName ); if (field !== undefined && typeof field === 'object' && field.stats) { modelPlotCardinality += @@ -160,7 +162,7 @@ export async function validateCardinality( // find out if there are any relevant detector field names // where cardinality checks could be run against. - const numDetectorsWithFieldNames = job.analysis_config.detectors.filter(d => { + const numDetectorsWithFieldNames = job.analysis_config.detectors.filter((d) => { return d.by_field_name || d.over_field_name || d.partition_field_name; }); if (numDetectorsWithFieldNames.length === 0) { @@ -175,23 +177,23 @@ export async function validateCardinality( // check over fields (population analysis) const validateOverFieldsLow = validate({ type: 'over', - isInvalid: cardinality => cardinality < OVER_FIELD_CARDINALITY_THRESHOLD_LOW, + isInvalid: (cardinality) => cardinality < OVER_FIELD_CARDINALITY_THRESHOLD_LOW, messageId: 'cardinality_over_field_low', }); const validateOverFieldsHigh = validate({ type: 'over', - isInvalid: cardinality => cardinality > OVER_FIELD_CARDINALITY_THRESHOLD_HIGH, + isInvalid: (cardinality) => cardinality > OVER_FIELD_CARDINALITY_THRESHOLD_HIGH, messageId: 'cardinality_over_field_high', }); // check partition/by fields (multi-metric analysis) const validatePartitionFields = validate({ type: 'partition', - isInvalid: cardinality => cardinality > PARTITION_FIELD_CARDINALITY_THRESHOLD, + isInvalid: (cardinality) => cardinality > PARTITION_FIELD_CARDINALITY_THRESHOLD, }); const validateByFields = validate({ type: 'by', - isInvalid: cardinality => cardinality > BY_FIELD_CARDINALITY_THRESHOLD, + isInvalid: (cardinality) => cardinality > BY_FIELD_CARDINALITY_THRESHOLD, }); // we already called the validation functions above, @@ -217,7 +219,7 @@ export async function validateCardinality( } // add all messages returned from the individual cardinality checks - validations.forEach(v => messages.push(...v.messages)); + validations.forEach((v) => messages.push(...v.messages)); if (messages.length === 0) { messages.push({ id: 'success_cardinality' }); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts index df3310ad9f5e8..89c265d0b6f60 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts @@ -11,7 +11,7 @@ import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; import { validateInfluencers } from './validate_influencers'; describe('ML - validateInfluencers', () => { - it('called without arguments throws an error', done => { + it('called without arguments throws an error', (done) => { validateInfluencers( (undefined as unknown) as APICaller, (undefined as unknown) as CombinedJob @@ -21,14 +21,14 @@ describe('ML - validateInfluencers', () => { ); }); - it('called with non-valid job argument #1, missing analysis_config', done => { + it('called with non-valid job argument #1, missing analysis_config', (done) => { validateInfluencers((undefined as unknown) as APICaller, ({} as unknown) as CombinedJob).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() ); }); - it('called with non-valid job argument #2, missing analysis_config.influencers', done => { + it('called with non-valid job argument #2, missing analysis_config.influencers', (done) => { const job = { analysis_config: {}, datafeed_config: { indices: [] }, @@ -40,7 +40,7 @@ describe('ML - validateInfluencers', () => { ); }); - it('called with non-valid job argument #3, missing analysis_config.detectors', done => { + it('called with non-valid job argument #3, missing analysis_config.detectors', (done) => { const job = { analysis_config: { influencers: [] }, datafeed_config: { indices: [] }, @@ -66,8 +66,8 @@ describe('ML - validateInfluencers', () => { it('success_influencer', () => { const job = getJobConfig(['airline']); - return validateInfluencers((undefined as unknown) as APICaller, job).then(messages => { - const ids = messages.map(m => m.id); + return validateInfluencers((undefined as unknown) as APICaller, job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['success_influencers']); }); }); @@ -84,24 +84,24 @@ describe('ML - validateInfluencers', () => { ] ); - return validateInfluencers((undefined as unknown) as APICaller, job).then(messages => { - const ids = messages.map(m => m.id); + return validateInfluencers((undefined as unknown) as APICaller, job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual([]); }); }); it('influencer_low', () => { const job = getJobConfig(); - return validateInfluencers((undefined as unknown) as APICaller, job).then(messages => { - const ids = messages.map(m => m.id); + return validateInfluencers((undefined as unknown) as APICaller, job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['influencer_low']); }); }); it('influencer_high', () => { const job = getJobConfig(['i1', 'i2', 'i3', 'i4']); - return validateInfluencers((undefined as unknown) as APICaller, job).then(messages => { - const ids = messages.map(m => m.id); + return validateInfluencers((undefined as unknown) as APICaller, job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['influencer_high']); }); }); @@ -118,8 +118,8 @@ describe('ML - validateInfluencers', () => { }, ] ); - return validateInfluencers((undefined as unknown) as APICaller, job).then(messages => { - const ids = messages.map(m => m.id); + return validateInfluencers((undefined as unknown) as APICaller, job).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['influencer_low_suggestion']); }); }); @@ -148,7 +148,7 @@ describe('ML - validateInfluencers', () => { }, ] ); - return validateInfluencers((undefined as unknown) as APICaller, job).then(messages => { + return validateInfluencers((undefined as unknown) as APICaller, job).then((messages) => { expect(messages).toStrictEqual([ { id: 'influencer_low_suggestions', diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_influencers.ts b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.ts index e54ffc4586a8e..531d4dfdba0c5 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_influencers.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.ts @@ -21,7 +21,7 @@ export async function validateInfluencers(callWithRequest: APICaller, job: Combi const influencers = job.analysis_config.influencers; const detectorFieldNames: string[] = []; - job.analysis_config.detectors.forEach(d => { + job.analysis_config.detectors.forEach((d) => { if (d.by_field_name) { detectorFieldNames.push(d.by_field_name); } @@ -56,7 +56,7 @@ export async function validateInfluencers(callWithRequest: APICaller, job: Combi if (detectorFieldNames.length > 1) { id = 'influencer_low_suggestions'; const uniqueInfluencers = [...new Set(detectorFieldNames)]; - influencerSuggestion = `[${uniqueInfluencers.map(i => `"${i}"`).join(',')}]`; + influencerSuggestion = `[${uniqueInfluencers.map((i) => `"${i}"`).join(',')}]`; } messages.push({ id, influencerSuggestion }); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts index bf88716181bb3..581153036fed7 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts @@ -129,8 +129,8 @@ describe('ML - validateModelMemoryLimit', () => { const job = getJobConfig(); const duration = undefined; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual([]); }); }); @@ -141,8 +141,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '31mb'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_greater_than_max_mml']); }); }); @@ -158,8 +158,8 @@ describe('ML - validateModelMemoryLimit', () => { getMockCallWithRequest({ 'ml.estimateModelMemory': { model_memory_estimate: '66mb' } }), job, duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['estimated_mml_greater_than_max_mml']); }); }); @@ -175,8 +175,8 @@ describe('ML - validateModelMemoryLimit', () => { getMockCallWithRequest({ 'ml.estimateModelMemory': { model_memory_estimate: '24mb' } }), job, duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['success_mml']); }); }); @@ -192,8 +192,8 @@ describe('ML - validateModelMemoryLimit', () => { getMockCallWithRequest({ 'ml.estimateModelMemory': { model_memory_estimate: '22mb' } }), job, duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['half_estimated_mml_greater_than_mml']); }); }); @@ -206,8 +206,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '10mb'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['half_estimated_mml_greater_than_mml']); }); }); @@ -218,8 +218,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '31mb'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual([]); }); }); @@ -230,8 +230,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '41mb'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_greater_than_effective_max_mml']); }); }); @@ -247,8 +247,8 @@ describe('ML - validateModelMemoryLimit', () => { getMockCallWithRequest({ 'ml.estimateModelMemory': { model_memory_estimate: '19mb' } }), job, duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['success_mml']); }); }); @@ -260,8 +260,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '0mb'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_value_invalid']); }); }); @@ -273,8 +273,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '10mbananas'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_value_invalid']); }); }); @@ -286,8 +286,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '10'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_value_invalid']); }); }); @@ -299,8 +299,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = 'mb'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_value_invalid']); }); }); @@ -312,8 +312,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = 'asdf'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_value_invalid']); }); }); @@ -325,8 +325,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '1023KB'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['mml_value_invalid']); }); }); @@ -338,8 +338,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '1024KB'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['half_estimated_mml_greater_than_mml']); }); }); @@ -351,8 +351,8 @@ describe('ML - validateModelMemoryLimit', () => { // @ts-ignore job.analysis_limits.model_memory_limit = '6MB'; - return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then(messages => { - const ids = messages.map(m => m.id); + return validateModelMemoryLimit(getMockCallWithRequest(), job, duration).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['half_estimated_mml_greater_than_mml']); }); }); @@ -368,8 +368,8 @@ describe('ML - validateModelMemoryLimit', () => { getMockCallWithRequest({ 'ml.estimateModelMemory': { model_memory_estimate: '20mb' } }), job, duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toEqual(['success_mml']); }); }); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts index 2c3b2dd4dc6ae..3ba8701b4bbd2 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts @@ -23,7 +23,7 @@ const mockSearchResponse = { const callWithRequestFactory = (resp: any): APICaller => { return (path: string) => { - return new Promise(resolve => { + return new Promise((resolve) => { resolve(resp[path]); }) as Promise; }; @@ -44,7 +44,7 @@ function getMinimalValidJob() { } describe('ML - isValidTimeField', () => { - it('called without job config argument triggers Promise rejection', done => { + it('called without job config argument triggers Promise rejection', (done) => { isValidTimeField( callWithRequestFactory(mockSearchResponse), (undefined as unknown) as CombinedJob @@ -54,9 +54,9 @@ describe('ML - isValidTimeField', () => { ); }); - it('time_field `@timestamp`', done => { + it('time_field `@timestamp`', (done) => { isValidTimeField(callWithRequestFactory(mockSearchResponse), getMinimalValidJob()).then( - valid => { + (valid) => { expect(valid).toBe(true); done(); }, @@ -64,7 +64,7 @@ describe('ML - isValidTimeField', () => { ); }); - it('time_field `metadata.timestamp`', done => { + it('time_field `metadata.timestamp`', (done) => { const mockJobConfigNestedDate = getMinimalValidJob(); mockJobConfigNestedDate.data_description.time_field = 'metadata.timestamp'; @@ -77,7 +77,7 @@ describe('ML - isValidTimeField', () => { callWithRequestFactory(mockSearchResponseNestedDate), mockJobConfigNestedDate ).then( - valid => { + (valid) => { expect(valid).toBe(true); done(); }, @@ -87,7 +87,7 @@ describe('ML - isValidTimeField', () => { }); describe('ML - validateTimeRange', () => { - it('called without arguments', done => { + it('called without arguments', (done) => { validateTimeRange( callWithRequestFactory(mockSearchResponse), (undefined as unknown) as CombinedJob @@ -97,7 +97,7 @@ describe('ML - validateTimeRange', () => { ); }); - it('called with non-valid job argument #2, missing datafeed_config', done => { + it('called with non-valid job argument #2, missing datafeed_config', (done) => { validateTimeRange(callWithRequestFactory(mockSearchResponse), ({ analysis_config: {}, } as unknown) as CombinedJob).then( @@ -106,7 +106,7 @@ describe('ML - validateTimeRange', () => { ); }); - it('called with non-valid job argument #3, missing datafeed_config.indices', done => { + it('called with non-valid job argument #3, missing datafeed_config.indices', (done) => { const job = { analysis_config: {}, datafeed_config: {} }; validateTimeRange( callWithRequestFactory(mockSearchResponse), @@ -117,7 +117,7 @@ describe('ML - validateTimeRange', () => { ); }); - it('called with non-valid job argument #4, missing data_description', done => { + it('called with non-valid job argument #4, missing data_description', (done) => { const job = { analysis_config: {}, datafeed_config: { indices: [] } }; validateTimeRange( callWithRequestFactory(mockSearchResponse), @@ -128,7 +128,7 @@ describe('ML - validateTimeRange', () => { ); }); - it('called with non-valid job argument #5, missing data_description.time_field', done => { + it('called with non-valid job argument #5, missing data_description.time_field', (done) => { const job = { analysis_config: {}, data_description: {}, datafeed_config: { indices: [] } }; validateTimeRange( callWithRequestFactory(mockSearchResponse), @@ -147,8 +147,8 @@ describe('ML - validateTimeRange', () => { callWithRequestFactory(mockSearchResponseInvalid), getMinimalValidJob(), duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['time_field_invalid']); }); }); @@ -161,8 +161,8 @@ describe('ML - validateTimeRange', () => { callWithRequestFactory(mockSearchResponse), jobShortTimeRange, duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['time_range_short']); }); }); @@ -173,8 +173,8 @@ describe('ML - validateTimeRange', () => { callWithRequestFactory(mockSearchResponse), getMinimalValidJob(), duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['time_range_short']); }); }); @@ -185,8 +185,8 @@ describe('ML - validateTimeRange', () => { callWithRequestFactory(mockSearchResponse), getMinimalValidJob(), duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['time_range_short']); }); }); @@ -197,8 +197,8 @@ describe('ML - validateTimeRange', () => { callWithRequestFactory(mockSearchResponse), getMinimalValidJob(), duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['success_time_range']); }); }); @@ -209,8 +209,8 @@ describe('ML - validateTimeRange', () => { callWithRequestFactory(mockSearchResponse), getMinimalValidJob(), duration - ).then(messages => { - const ids = messages.map(m => m.id); + ).then((messages) => { + const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['time_range_before_epoch']); }); }); diff --git a/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js index 4934a0ba07081..e664a1403d7d6 100644 --- a/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js +++ b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js @@ -25,7 +25,7 @@ export function buildAnomalyTableItems(anomalyRecords, aggregationInterval, date displayRecords = aggregateAnomalies(anomalyRecords, aggregationInterval, dateFormatTz); } else { // Show all anomaly records. - displayRecords = anomalyRecords.map(record => { + displayRecords = anomalyRecords.map((record) => { return { time: record.timestamp, source: record, @@ -56,9 +56,9 @@ export function buildAnomalyTableItems(anomalyRecords, aggregationInterval, date if (source.influencers !== undefined) { const influencers = []; const sourceInfluencers = _.sortBy(source.influencers, 'influencer_field_name'); - sourceInfluencers.forEach(influencer => { + sourceInfluencers.forEach((influencer) => { const influencerFieldName = influencer.influencer_field_name; - influencer.influencer_field_values.forEach(influencerFieldValue => { + influencer.influencer_field_values.forEach((influencerFieldValue) => { influencers.push({ [influencerFieldName]: influencerFieldValue, }); @@ -125,17 +125,12 @@ function aggregateAnomalies(anomalyRecords, interval, dateFormatTz) { } const aggregatedData = {}; - anomalyRecords.forEach(record => { + anomalyRecords.forEach((record) => { // Use moment.js to get start of interval. const roundedTime = dateFormatTz !== undefined - ? moment(record.timestamp) - .tz(dateFormatTz) - .startOf(interval) - .valueOf() - : moment(record.timestamp) - .startOf(interval) - .valueOf(); + ? moment(record.timestamp).tz(dateFormatTz).startOf(interval).valueOf() + : moment(record.timestamp).startOf(interval).valueOf(); if (aggregatedData[roundedTime] === undefined) { aggregatedData[roundedTime] = {}; } @@ -178,9 +173,9 @@ function aggregateAnomalies(anomalyRecords, interval, dateFormatTz) { // the highest score per bucketed time / jobId / detectorIndex. const summaryRecords = []; _.each(aggregatedData, (times, roundedTime) => { - _.each(times, jobIds => { - _.each(jobIds, entityDetectors => { - _.each(entityDetectors, record => { + _.each(times, (jobIds) => { + _.each(jobIds, (entityDetectors) => { + _.each(entityDetectors, (record) => { summaryRecords.push({ time: +roundedTime, source: record, diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts index 1df53d9cee79c..a985ef7b8f3f5 100644 --- a/x-pack/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -89,7 +89,7 @@ export function resultsServiceProvider(callAsCurrentUser: APICaller) { } // Add in term queries for each of the specified criteria. - criteriaFields.forEach(criteria => { + criteriaFields.forEach((criteria) => { boolCriteria.push({ term: { [criteria.fieldName]: criteria.fieldValue, @@ -105,7 +105,7 @@ export function resultsServiceProvider(callAsCurrentUser: APICaller) { if (influencers.length > 0) { boolCriteria.push({ bool: { - should: influencers.map(influencer => { + should: influencers.map((influencer) => { return { nested: { path: 'influencers', @@ -169,7 +169,7 @@ export function resultsServiceProvider(callAsCurrentUser: APICaller) { }; if (resp.hits.total !== 0) { let records: AnomalyRecordDoc[] = []; - resp.hits.hits.forEach(hit => { + resp.hits.hits.forEach((hit) => { records.push(hit._source); }); @@ -195,7 +195,7 @@ export function resultsServiceProvider(callAsCurrentUser: APICaller) { tableData.examplesByJobId = {}; const categoryIdsByJobId: { [key: string]: any } = {}; - categoryAnomalies.forEach(anomaly => { + categoryAnomalies.forEach((anomaly) => { if (!_.has(categoryIdsByJobId, anomaly.jobId)) { categoryIdsByJobId[anomaly.jobId] = []; } @@ -206,7 +206,7 @@ export function resultsServiceProvider(callAsCurrentUser: APICaller) { const categoryJobIds = Object.keys(categoryIdsByJobId); await Promise.all( - categoryJobIds.map(async jobId => { + categoryJobIds.map(async (jobId) => { const examplesByCategoryId = await getCategoryExamples( jobId, categoryIdsByJobId[jobId], @@ -358,7 +358,7 @@ export function resultsServiceProvider(callAsCurrentUser: APICaller) { [] ); const timestampByJobId: { [key: string]: number | undefined } = {}; - bucketsByJobId.forEach(bucket => { + bucketsByJobId.forEach((bucket) => { timestampByJobId[bucket.key] = bucket.maxTimestamp.value; }); diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 969b74194148b..b167214cc33cf 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -133,7 +133,7 @@ export class MlServerPlugin implements Plugin { + members.forEach((member) => { collection.push(serializeProperty(member)); }); @@ -138,7 +138,7 @@ export function extractDocumentation( ? `${symbol.getName()}[]` : symbol.getName(); - members.forEach(member => { + members.forEach((member) => { nestedEntries.push(serializeProperty(member)); }); } diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts index 0eaf3143eaac0..8b9b35f7a0d3c 100644 --- a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts @@ -13,11 +13,11 @@ export function postProcess(parsedFiles: any[]): void { const schemasDirPath = `${__dirname}${path.sep}..${path.sep}..${path.sep}schemas${path.sep}`; const schemaFiles = fs .readdirSync(schemasDirPath) - .map(filename => path.resolve(schemasDirPath + filename)); + .map((filename) => path.resolve(schemasDirPath + filename)); const schemaDocs = extractDocumentation(schemaFiles); - parsedFiles.forEach(parsedFile => { + parsedFiles.forEach((parsedFile) => { parsedFile.forEach((block: Block) => { const { local: { schemas }, diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts index 8cbe38d667b2c..754368e318f38 100644 --- a/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts @@ -13,7 +13,7 @@ const API_VERSION = '7.8.0'; * Updates api version of the endpoints. */ export function postFilter(parsedFiles: any[]) { - parsedFiles.forEach(parsedFile => { + parsedFiles.forEach((parsedFile) => { parsedFile.forEach((block: Block) => { block.local.version = API_VERSION; }); diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 894c4739ef96e..e2601c7ad6a2e 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RequestHandlerContext } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; import { RouteInitialization } from '../types'; @@ -13,12 +14,48 @@ import { dataAnalyticsExplainSchema, analyticsIdSchema, stopsDataFrameAnalyticsJobQuerySchema, + deleteDataFrameAnalyticsJobSchema, } from './schemas/data_analytics_schema'; +import { IndexPatternHandler } from '../models/data_frame_analytics/index_patterns'; +import { DeleteDataFrameAnalyticsWithIndexStatus } from '../../common/types/data_frame_analytics'; + +function getIndexPatternId(context: RequestHandlerContext, patternName: string) { + const iph = new IndexPatternHandler(context.core.savedObjects.client); + return iph.getIndexPatternId(patternName); +} + +function deleteDestIndexPatternById(context: RequestHandlerContext, indexPatternId: string) { + const iph = new IndexPatternHandler(context.core.savedObjects.client); + return iph.deleteIndexPatternById(indexPatternId); +} /** * Routes for the data frame analytics */ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitialization) { + async function userCanDeleteIndex( + context: RequestHandlerContext, + destinationIndex: string + ): Promise { + if (!mlLicense.isSecurityEnabled()) { + return true; + } + const privilege = await context.ml!.mlClient.callAsCurrentUser('ml.privilegeCheck', { + body: { + index: [ + { + names: [destinationIndex], // uses wildcard + privileges: ['delete_index'], + }, + ], + }, + }); + if (!privilege) { + return false; + } + return privilege.has_all_requested === true; + } + /** * @apiGroup DataFrameAnalytics * @@ -277,6 +314,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { params: analyticsIdSchema, + query: deleteDataFrameAnalyticsJobSchema, }, options: { tags: ['access:ml:canDeleteDataFrameAnalytics'], @@ -285,12 +323,78 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat mlLicense.fullLicenseAPIGuard(async (context, request, response) => { try { const { analyticsId } = request.params; - const results = await context.ml!.mlClient.callAsCurrentUser( - 'ml.deleteDataFrameAnalytics', - { + const { deleteDestIndex, deleteDestIndexPattern } = request.query; + let destinationIndex: string | undefined; + const analyticsJobDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { success: false }; + const destIndexDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { success: false }; + const destIndexPatternDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { + success: false, + }; + + // Check if analyticsId is valid and get destination index + if (deleteDestIndex || deleteDestIndexPattern) { + try { + const dfa = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics', { + analyticsId, + }); + if (Array.isArray(dfa.data_frame_analytics) && dfa.data_frame_analytics.length > 0) { + destinationIndex = dfa.data_frame_analytics[0].dest.index; + } + } catch (e) { + return response.customError(wrapError(e)); + } + + // If user checks box to delete the destinationIndex associated with the job + if (destinationIndex && deleteDestIndex) { + // Verify if user has privilege to delete the destination index + const userCanDeleteDestIndex = await userCanDeleteIndex(context, destinationIndex); + // If user does have privilege to delete the index, then delete the index + if (userCanDeleteDestIndex) { + try { + await context.ml!.mlClient.callAsCurrentUser('indices.delete', { + index: destinationIndex, + }); + destIndexDeleted.success = true; + } catch (deleteIndexError) { + destIndexDeleted.error = wrapError(deleteIndexError); + } + } else { + return response.forbidden(); + } + } + + // Delete the index pattern if there's an index pattern that matches the name of dest index + if (destinationIndex && deleteDestIndexPattern) { + try { + const indexPatternId = await getIndexPatternId(context, destinationIndex); + if (indexPatternId) { + await deleteDestIndexPatternById(context, indexPatternId); + } + destIndexPatternDeleted.success = true; + } catch (deleteDestIndexPatternError) { + destIndexPatternDeleted.error = wrapError(deleteDestIndexPatternError); + } + } + } + // Grab the target index from the data frame analytics job id + // Delete the data frame analytics + + try { + await context.ml!.mlClient.callAsCurrentUser('ml.deleteDataFrameAnalytics', { analyticsId, + }); + analyticsJobDeleted.success = true; + } catch (deleteDFAError) { + analyticsJobDeleted.error = wrapError(deleteDFAError); + if (analyticsJobDeleted.error.statusCode === 404) { + return response.notFound(); } - ); + } + const results = { + analyticsJobDeleted, + destIndexDeleted, + destIndexPatternDeleted, + }; return response.ok({ body: results, }); diff --git a/x-pack/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts index 632166d6d5fb8..0af8141a2a641 100644 --- a/x-pack/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -66,7 +66,7 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, let errorResp; const resp = await estimateBucketSpanFactory( context.ml!.mlClient.callAsCurrentUser, - context.core.elasticsearch.adminClient.callAsInternalUser, + context.ml!.mlClient.callAsInternalUser, mlLicense.isSecurityEnabled() === false )(request.body) // this catch gets triggered when the estimation code runs without error @@ -187,7 +187,7 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, context.ml!.mlClient.callAsCurrentUser, request.body, version, - context.core.elasticsearch.adminClient.callAsInternalUser, + context.ml!.mlClient.callAsInternalUser, mlLicense.isSecurityEnabled() === false ); diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 88b86de322e3c..de393e002c55b 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -77,6 +77,12 @@ export const analysisConfigSchema = schema.object({ detectors: schema.arrayOf(detectorSchema), influencers: schema.arrayOf(schema.maybe(schema.string())), categorization_field_name: schema.maybe(schema.string()), + per_partition_categorization: schema.maybe( + schema.object({ + enabled: schema.boolean(), + stop_on_warn: schema.maybe(schema.boolean()), + }) + ), }); export const anomalyDetectionJobSchema = { diff --git a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts index f1d4947a7abc5..e6b4e4ccf8582 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts @@ -47,6 +47,7 @@ export const dataAnalyticsExplainSchema = schema.object({ /** Source */ source: schema.object({ index: schema.string(), + query: schema.maybe(schema.any()), }), analysis: schema.any(), analyzed_fields: schema.maybe(schema.any()), @@ -60,6 +61,14 @@ export const analyticsIdSchema = schema.object({ analyticsId: schema.string(), }); +export const deleteDataFrameAnalyticsJobSchema = schema.object({ + /** + * Analytics Destination Index + */ + deleteDestIndex: schema.maybe(schema.boolean()), + deleteDestIndexPattern: schema.maybe(schema.boolean()), +}); + export const stopsDataFrameAnalyticsJobQuerySchema = schema.object({ force: schema.maybe(schema.boolean()), }); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index 1ca1e5287e9d0..be107db9508fd 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -40,10 +40,8 @@ export const forceStartDatafeedSchema = schema.object({ }); export const jobIdsSchema = schema.object({ - /** Optional list of job ID(s). */ - jobIds: schema.maybe( - schema.oneOf([schema.string(), schema.arrayOf(schema.maybe(schema.string()))]) - ), + /** Optional list of job IDs. */ + jobIds: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), }); export const jobsWithTimerangeSchema = { diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts index 7ae7dd8eef065..99c7805a62e76 100644 --- a/x-pack/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -29,7 +29,7 @@ export function systemRoutes( let count = 0; if (typeof resp.nodes === 'object') { - Object.keys(resp.nodes).forEach(k => { + Object.keys(resp.nodes).forEach((k) => { if (resp.nodes[k].attributes !== undefined) { const maxOpenJobs = resp.nodes[k].attributes['ml.max_open_jobs']; if (maxOpenJobs !== null && maxOpenJobs > 0) { diff --git a/x-pack/plugins/ml/server/shared_services/providers/system.ts b/x-pack/plugins/ml/server/shared_services/providers/system.ts index 698ac8e6261e5..33a4d854dd3e9 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/system.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/system.ts @@ -23,7 +23,7 @@ export interface MlSystemProvider { ): { mlCapabilities(): Promise; mlInfo(): Promise; - mlSearch(searchParams: SearchParams): Promise>; + mlAnomalySearch(searchParams: SearchParams): Promise>; }; } @@ -68,7 +68,7 @@ export function getMlSystemProvider( cloudId, }; }, - async mlSearch(searchParams: SearchParams): Promise> { + async mlAnomalySearch(searchParams: SearchParams): Promise> { isFullLicense(); return callAsCurrentUser('search', { ...searchParams, diff --git a/x-pack/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js b/x-pack/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js index 470d596bd2bdc..aec30f0628f31 100644 --- a/x-pack/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js +++ b/x-pack/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js @@ -24,16 +24,12 @@ describe('formatTimestampToDuration', () => { formatTimestampToDuration(fiftyNineSeconds, CALCULATE_DURATION_SINCE, getTestTime()) ).to.be('59 seconds'); - const fiveMins = getTestTime() - .subtract(5, 'minutes') - .subtract(30, 'seconds'); + const fiveMins = getTestTime().subtract(5, 'minutes').subtract(30, 'seconds'); expect(formatTimestampToDuration(fiveMins, CALCULATE_DURATION_SINCE, getTestTime())).to.be( '6 mins' ); - const sixHours = getTestTime() - .subtract(6, 'hours') - .subtract(30, 'minutes'); + const sixHours = getTestTime().subtract(6, 'hours').subtract(30, 'minutes'); expect(formatTimestampToDuration(sixHours, CALCULATE_DURATION_SINCE, getTestTime())).to.be( '6 hrs 30 mins' ); @@ -85,17 +81,12 @@ describe('formatTimestampToDuration', () => { '10 mins' ); - const sixHours = getTestTime() - .add(6, 'hours') - .add(30, 'minutes'); + const sixHours = getTestTime().add(6, 'hours').add(30, 'minutes'); expect(formatTimestampToDuration(sixHours, CALCULATE_DURATION_UNTIL, getTestTime())).to.be( '6 hrs 30 mins' ); - const sevenDays = getTestTime() - .add(7, 'days') - .add(6, 'hours') - .add(18, 'minutes'); + const sevenDays = getTestTime().add(7, 'days').add(6, 'hours').add(18, 'minutes'); expect(formatTimestampToDuration(sevenDays, CALCULATE_DURATION_UNTIL, getTestTime())).to.be( '7 days 6 hrs 18 mins' ); diff --git a/x-pack/plugins/monitoring/common/cancel_promise.ts b/x-pack/plugins/monitoring/common/cancel_promise.ts index f100edda50796..e8d9282945947 100644 --- a/x-pack/plugins/monitoring/common/cancel_promise.ts +++ b/x-pack/plugins/monitoring/common/cancel_promise.ts @@ -53,13 +53,13 @@ export class PromiseWithCancel { return new Promise((resolve, reject) => { this._status = Status.Awaiting; return this._promise - .then(response => { + .then((response) => { if (this._status !== Status.Canceled) { this._status = Status.Resolved; return resolve(response); } }) - .catch(error => { + .catch((error) => { if (this._status !== Status.Canceled) { this._status = Status.Failed; return reject(error); diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json index 115cc08871ea4..4ed693464712d 100644 --- a/x-pack/plugins/monitoring/kibana.json +++ b/x-pack/plugins/monitoring/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "configPath": ["monitoring"], "requiredPlugins": ["licensing", "features", "data", "navigation", "kibanaLegacy"], - "optionalPlugins": ["alerting", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home"], + "optionalPlugins": ["alerts", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home"], "server": true, "ui": true } diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts index 3fa79cedf4ce7..96b122801085f 100644 --- a/x-pack/plugins/monitoring/public/angular/app_modules.ts +++ b/x-pack/plugins/monitoring/public/angular/app_modules.ts @@ -102,7 +102,7 @@ function createMonitoringAppConfigConstants(keys: MonitoringPluginDependencies[' function createLocalStateModule(query: any) { angular .module('monitoring/State', ['monitoring/Private']) - .service('globalState', function( + .service('globalState', function ( Private: IPrivate, $rootScope: ng.IRootScopeService, $location: ng.ILocationService @@ -129,7 +129,7 @@ function createLocalStateModule(query: any) { function createLocalKbnUrlModule() { angular .module('monitoring/KbnUrl', ['monitoring/Private', 'ngRoute']) - .service('kbnUrl', function(Private: IPrivate) { + .service('kbnUrl', function (Private: IPrivate) { return Private(KbnUrlProvider); }); } @@ -137,22 +137,22 @@ function createLocalKbnUrlModule() { function createMonitoringAppServices() { angular .module('monitoring/services', ['monitoring/Private']) - .service('breadcrumbs', function(Private: IPrivate) { + .service('breadcrumbs', function (Private: IPrivate) { return Private(breadcrumbsProvider); }) - .service('monitoringClusters', function(Private: IPrivate) { + .service('monitoringClusters', function (Private: IPrivate) { return Private(monitoringClustersProvider); }) - .service('$executor', function(Private: IPrivate) { + .service('$executor', function (Private: IPrivate) { return Private(executorProvider); }) - .service('features', function(Private: IPrivate) { + .service('features', function (Private: IPrivate) { return Private(featuresProvider); }) - .service('license', function(Private: IPrivate) { + .service('license', function (Private: IPrivate) { return Private(licenseProvider); }) - .service('title', function(Private: IPrivate) { + .service('title', function (Private: IPrivate) { return Private(titleProvider); }); } @@ -169,24 +169,24 @@ function createMonitoringAppDirectives() { function createMonitoringAppFilters() { angular .module('monitoring/filters', []) - .filter('capitalize', function() { - return function(input: string) { + .filter('capitalize', function () { + return function (input: string) { return capitalize(input?.toLowerCase()); }; }) - .filter('formatNumber', function() { + .filter('formatNumber', function () { return formatNumber; }) - .filter('formatMetric', function() { + .filter('formatMetric', function () { return formatMetric; }) - .filter('extractIp', function() { + .filter('extractIp', function () { return extractIp; }); } function createLocalConfigModule(core: MonitoringPluginDependencies['core']) { - angular.module('monitoring/Config', []).provider('config', function() { + angular.module('monitoring/Config', []).provider('config', function () { return { $get: () => ({ get: (key: string) => core.uiSettings?.get(key), @@ -198,13 +198,13 @@ function createLocalConfigModule(core: MonitoringPluginDependencies['core']) { function createLocalStorage() { angular .module('monitoring/Storage', []) - .service('localStorage', function($window: IWindowService) { + .service('localStorage', function ($window: IWindowService) { return new Storage($window.localStorage); }) - .service('sessionStorage', function($window: IWindowService) { + .service('sessionStorage', function ($window: IWindowService) { return new Storage($window.sessionStorage); }) - .service('sessionTimeout', function() { + .service('sessionTimeout', function () { return {}; }); } @@ -230,12 +230,12 @@ function createLocalI18nModule() { function createHrefModule(core: AppMountContext['core']) { const name: string = 'kbnHref'; - angular.module('monitoring/href', []).directive(name, function() { + angular.module('monitoring/href', []).directive(name, function () { return { restrict: 'A', link: { pre: (_$scope, _$el, $attr) => { - $attr.$observe(name, val => { + $attr.$observe(name, (val) => { if (val) { const url = getSafeForExternalLink(val as string); $attr.$set('href', core.http.basePath.prepend(url)); diff --git a/x-pack/plugins/monitoring/public/angular/helpers/routes.ts b/x-pack/plugins/monitoring/public/angular/helpers/routes.ts index b9307e8594a7a..22b6fd8a4e912 100644 --- a/x-pack/plugins/monitoring/public/angular/helpers/routes.ts +++ b/x-pack/plugins/monitoring/public/angular/helpers/routes.ts @@ -26,7 +26,7 @@ class Routes { }; public addToProvider = ($routeProvider: any) => { - this.routes.forEach(args => { + this.routes.forEach((args) => { $routeProvider.when.apply(this, args); }); diff --git a/x-pack/plugins/monitoring/public/angular/providers/private.js b/x-pack/plugins/monitoring/public/angular/providers/private.js index e456f2617f7b8..3a667037b2919 100644 --- a/x-pack/plugins/monitoring/public/angular/providers/private.js +++ b/x-pack/plugins/monitoring/public/angular/providers/private.js @@ -86,13 +86,7 @@ import _ from 'lodash'; const nextId = _.partial(_.uniqueId, 'privateProvider#'); function name(fn) { - return ( - fn.name || - fn - .toString() - .split('\n') - .shift() - ); + return fn.name || fn.toString().split('\n').shift(); } export function PrivateProvider() { @@ -112,12 +106,12 @@ export function PrivateProvider() { else return (fn.$$id = nextId()); } - provider.stub = function(fn, instance) { + provider.stub = function (fn, instance) { cache[identify(fn)] = instance; return instance; }; - provider.swap = function(fn, prov) { + provider.swap = function (fn, prov) { const id = identify(fn); swaps[id] = prov; }; @@ -127,7 +121,7 @@ export function PrivateProvider() { function PrivateFactory($injector) { // prevent circular deps by tracking where we came from const privPath = []; - const pathToString = function() { + const pathToString = function () { return privPath.map(name).join(' -> '); }; diff --git a/x-pack/plugins/monitoring/public/angular/providers/url.js b/x-pack/plugins/monitoring/public/angular/providers/url.js index 57b63708b546e..0c984a71c9f2c 100644 --- a/x-pack/plugins/monitoring/public/angular/providers/url.js +++ b/x-pack/plugins/monitoring/public/angular/providers/url.js @@ -34,7 +34,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {Object} [paramObj] - optional set of parameters for the url template * @return {undefined} */ - self.change = function(url, paramObj, appState) { + self.change = function (url, paramObj, appState) { self._changeLocation('url', url, paramObj, false, appState); }; @@ -46,7 +46,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {Object} [paramObj] - optional set of parameters for the path template * @return {undefined} */ - self.changePath = function(path, paramObj) { + self.changePath = function (path, paramObj) { self._changeLocation('path', path, paramObj); }; @@ -57,7 +57,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {Object} [paramObj] - optional set of parameters for the url template * @return {undefined} */ - self.redirect = function(url, paramObj, appState) { + self.redirect = function (url, paramObj, appState) { self._changeLocation('url', url, paramObj, true, appState); }; @@ -69,7 +69,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {Object} [paramObj] - optional set of parameters for the path template * @return {undefined} */ - self.redirectPath = function(path, paramObj) { + self.redirectPath = function (path, paramObj) { self._changeLocation('path', path, paramObj, true); }; @@ -82,10 +82,10 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @return {String} - the evaluated result * @throws {Error} If any of the expressions can't be parsed. */ - self.eval = function(template, paramObj) { + self.eval = function (template, paramObj) { paramObj = paramObj || {}; - return template.replace(/\{\{([^\}]+)\}\}/g, function(match, expr) { + return template.replace(/\{\{([^\}]+)\}\}/g, function (match, expr) { // remove filters const key = expr.split('|')[0].trim(); @@ -109,7 +109,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {string} route - the route name * @return {string} - the computed href */ - self.getRouteHref = function(obj, route) { + self.getRouteHref = function (obj, route) { return '#' + self.getRouteUrl(obj, route); }; @@ -120,7 +120,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {string} route - the route name * @return {string} - the computed url */ - self.getRouteUrl = function(obj, route) { + self.getRouteUrl = function (obj, route) { const template = obj && obj.routes && obj.routes[route]; if (template) return self.eval(template, obj); }; @@ -133,7 +133,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {string} route - the route name * @return {undefined} */ - self.redirectToRoute = function(obj, route) { + self.redirectToRoute = function (obj, route) { self.redirect(self.getRouteUrl(obj, route)); }; @@ -145,7 +145,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * @param {string} route - the route name * @return {undefined} */ - self.changeToRoute = function(obj, route) { + self.changeToRoute = function (obj, route) { self.change(self.getRouteUrl(obj, route)); }; @@ -154,7 +154,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { * history. * @param param */ - self.removeParam = function(param) { + self.removeParam = function (param) { $location.search(param, null).replace(); }; @@ -163,7 +163,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { ///// let reloading; - self._changeLocation = function(type, url, paramObj, replace, appState) { + self._changeLocation = function (type, url, paramObj, replace, appState) { const prev = { path: $location.path(), search: $location.search(), @@ -186,7 +186,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { const $route = $injector.get('$route'); if (self._shouldForceReload(next, prev, $route)) { - reloading = $rootScope.$on('$locationChangeSuccess', function() { + reloading = $rootScope.$on('$locationChangeSuccess', function () { // call the "unlisten" function returned by $on reloading(); reloading = false; @@ -198,7 +198,7 @@ export function KbnUrlProvider($injector, $location, $rootScope, $parse) { }; // determine if the router will automatically reload the route - self._shouldForceReload = function(next, prev, $route) { + self._shouldForceReload = function (next, prev, $route) { if (reloading) return false; const route = $route.current && $route.current.$$route; diff --git a/x-pack/plugins/monitoring/public/components/alerts/alerts.js b/x-pack/plugins/monitoring/public/components/alerts/alerts.js index a86fdb1041a5c..0ac67228db359 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/alerts.js +++ b/x-pack/plugins/monitoring/public/components/alerts/alerts.js @@ -36,7 +36,7 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'status', sortable: true, - render: severity => { + render: (severity) => { const severityIconDefaults = { title: i18n.translate('xpack.monitoring.alerts.severityTitle.unknown', { defaultMessage: 'Unknown', @@ -67,7 +67,7 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'resolved_timestamp', sortable: true, - render: resolvedTimestamp => { + render: (resolvedTimestamp) => { const notResolvedLabel = i18n.translate('xpack.monitoring.alerts.notResolvedDescription', { defaultMessage: 'Not Resolved', }); @@ -109,7 +109,7 @@ const getColumns = (kbnUrl, scope, timezone) => [ suffix={alert.suffix} message={message} metadata={alert.metadata} - changeUrl={target => { + changeUrl={(target) => { scope.$evalAsync(() => { kbnUrl.changePath(target); }); @@ -124,7 +124,7 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'category', sortable: true, - render: link => + render: (link) => linkToCategories[link] ? linkToCategories[link] : i18n.translate('xpack.monitoring.alerts.categoryColumn.generalLabel', { @@ -137,7 +137,7 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'update_timestamp', sortable: true, - render: timestamp => formatDateTimeLocal(timestamp, timezone), + render: (timestamp) => formatDateTimeLocal(timestamp, timezone), }, { name: i18n.translate('xpack.monitoring.alerts.triggeredColumnTitle', { @@ -145,7 +145,7 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'timestamp', sortable: true, - render: timestamp => + render: (timestamp) => i18n.translate('xpack.monitoring.alerts.triggeredColumnValue', { defaultMessage: '{timestamp} ago', values: { @@ -156,7 +156,7 @@ const getColumns = (kbnUrl, scope, timezone) => [ ]; export const Alerts = ({ alerts, angular, sorting, pagination, onTableChange }) => { - const alertsFlattened = alerts.map(alert => ({ + const alertsFlattened = alerts.map((alert) => ({ ...alert, status: get(alert, 'metadata.severity', get(alert, 'severity', 0)), category: get(alert, 'metadata.link', get(alert, 'type', null)), diff --git a/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx b/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx index 2c2d7c6464e1b..7caef8c230bf4 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx @@ -30,13 +30,7 @@ describe('Configuration', () => { }); function getStep(component: ShallowWrapper, index: number) { - return component - .find('EuiSteps') - .shallow() - .find('EuiStep') - .at(index) - .children() - .shallow(); + return component.find('EuiSteps').shallow().find('EuiStep').at(index).children().shallow(); } describe('shallow view', () => { @@ -82,12 +76,7 @@ describe('Configuration', () => { it('reflect in Step1', async () => { const steps = component.find('EuiSteps').dive(); - expect( - steps - .find('EuiStep') - .at(0) - .prop('title') - ).toBe('Select email action'); + expect(steps.find('EuiStep').at(0).prop('title')).toBe('Select email action'); expect(steps.find('Step1').prop('selectedEmailActionId')).toBe(actionId); }); diff --git a/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.tsx b/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.tsx index 61f86b0f9b609..f248e20493a24 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/configuration/configuration.tsx @@ -61,7 +61,7 @@ export const AlertsConfiguration: React.FC = ( async function fetchEmailActions() { const kibanaActions = await Legacy.shims.kfetch({ method: 'GET', - pathname: `/api/action/_getAll`, + pathname: `/api/actions`, }); const actions = kibanaActions.data.filter( diff --git a/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx b/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx index 5734d379dfb0c..1be66ce4ccfef 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx @@ -135,7 +135,7 @@ describe('Step1', () => { expect(kfetch).toHaveBeenCalledWith({ method: 'POST', - pathname: `/api/action`, + pathname: `/api/actions/action`, body: JSON.stringify({ name: 'Email action for Stack Monitoring alerts', actionTypeId: ALERT_ACTION_TYPE_EMAIL, @@ -193,7 +193,7 @@ describe('Step1', () => { expect(kfetch).toHaveBeenCalledWith({ method: 'PUT', - pathname: `/api/action/${emailActions[0].id}`, + pathname: `/api/actions/action/${emailActions[0].id}`, body: JSON.stringify({ name: emailActions[0].name, config: omit(data, ['user', 'password']), @@ -209,8 +209,8 @@ describe('Step1', () => { jest.doMock('../../../legacy_shims', () => ({ Legacy: { shims: { - kfetch: jest.fn().mockImplementation(arg => { - if (arg.pathname === '/api/action/1/_execute') { + kfetch: jest.fn().mockImplementation((arg) => { + if (arg.pathname === '/api/actions/action/1/_execute') { return { status: 'ok' }; } return {}; @@ -223,29 +223,11 @@ describe('Step1', () => { const component = shallow(); - expect( - component - .find('EuiButton') - .at(1) - .prop('isLoading') - ).toBe(false); - component - .find('EuiButton') - .at(1) - .simulate('click'); - expect( - component - .find('EuiButton') - .at(1) - .prop('isLoading') - ).toBe(true); + expect(component.find('EuiButton').at(1).prop('isLoading')).toBe(false); + component.find('EuiButton').at(1).simulate('click'); + expect(component.find('EuiButton').at(1).prop('isLoading')).toBe(true); await component.update(); - expect( - component - .find('EuiButton') - .at(1) - .prop('isLoading') - ).toBe(false); + expect(component.find('EuiButton').at(1).prop('isLoading')).toBe(false); }); it('should show a successful test', async () => { @@ -254,7 +236,7 @@ describe('Step1', () => { Legacy: { shims: { kfetch: (arg: any) => { - if (arg.pathname === '/api/action/1/_execute') { + if (arg.pathname === '/api/actions/action/1/_execute') { return { status: 'ok' }; } return {}; @@ -267,10 +249,7 @@ describe('Step1', () => { const component = shallow(); - component - .find('EuiButton') - .at(1) - .simulate('click'); + component.find('EuiButton').at(1).simulate('click'); await component.update(); expect(component).toMatchSnapshot(); }); @@ -281,7 +260,7 @@ describe('Step1', () => { Legacy: { shims: { kfetch: (arg: any) => { - if (arg.pathname === '/api/action/1/_execute') { + if (arg.pathname === '/api/actions/action/1/_execute') { return { message: 'Very detailed error message' }; } return {}; @@ -294,10 +273,7 @@ describe('Step1', () => { const component = shallow(); - component - .find('EuiButton') - .at(1) - .simulate('click'); + component.find('EuiButton').at(1).simulate('click'); await component.update(); expect(component).toMatchSnapshot(); }); @@ -307,12 +283,7 @@ describe('Step1', () => { emailAddress: '', }; const component = shallow(); - expect( - component - .find('EuiButton') - .at(1) - .prop('isDisabled') - ).toBe(true); + expect(component.find('EuiButton').at(1).prop('isDisabled')).toBe(true); }); it('should should a tooltip if there is no email address', () => { @@ -344,25 +315,17 @@ describe('Step1', () => { }; const component = shallow(); - await component - .find('EuiButton') - .at(2) - .simulate('click'); + await component.find('EuiButton').at(2).simulate('click'); await component.update(); expect(kfetch).toHaveBeenCalledWith({ method: 'DELETE', - pathname: `/api/action/${emailActions[0].id}`, + pathname: `/api/actions/action/${emailActions[0].id}`, }); expect(customProps.setSelectedEmailActionId).toHaveBeenCalledWith(''); expect(customProps.onActionDone).toHaveBeenCalled(); - expect( - component - .find('EuiButton') - .at(2) - .prop('isLoading') - ).toBe(false); + expect(component.find('EuiButton').at(2).prop('isLoading')).toBe(false); }); }); }); diff --git a/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.tsx b/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.tsx index 7953010005885..b3e6c079378ef 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/configuration/step1.tsx @@ -44,7 +44,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { if (props.editAction) { await Legacy.shims.kfetch({ method: 'PUT', - pathname: `${BASE_ACTION_API_PATH}/${props.editAction.id}`, + pathname: `${BASE_ACTION_API_PATH}/action/${props.editAction.id}`, body: JSON.stringify({ name: props.editAction.name, config: omit(data, ['user', 'password']), @@ -55,7 +55,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { } else { await Legacy.shims.kfetch({ method: 'POST', - pathname: BASE_ACTION_API_PATH, + pathname: `${BASE_ACTION_API_PATH}/action`, body: JSON.stringify({ name: i18n.translate('xpack.monitoring.alerts.configuration.emailAction.name', { defaultMessage: 'Email action for Stack Monitoring alerts', @@ -75,7 +75,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { await Legacy.shims.kfetch({ method: 'DELETE', - pathname: `${BASE_ACTION_API_PATH}/${id}`, + pathname: `${BASE_ACTION_API_PATH}/action/${id}`, }); if (props.editAction && props.editAction.id === id) { @@ -101,7 +101,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { const result = await Legacy.shims.kfetch({ method: 'POST', - pathname: `${BASE_ACTION_API_PATH}/${props.selectedEmailActionId}/_execute`, + pathname: `${BASE_ACTION_API_PATH}/action/${props.selectedEmailActionId}/_execute`, body: JSON.stringify({ params }), }); if (result.status === 'ok') { @@ -178,7 +178,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { ); const options = [ - ...props.emailActions.map(action => { + ...props.emailActions.map((action) => { const actionLabel = i18n.translate( 'xpack.monitoring.alerts.configuration.selectAction.inputDisplay', { @@ -207,7 +207,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { props.setSelectedEmailActionId(id)} + onChange={(id) => props.setSelectedEmailActionId(id)} hasDividers /> ); @@ -238,7 +238,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { let manageConfiguration = null; const selectedEmailAction = props.emailActions.find( - action => action.id === props.selectedEmailActionId + (action) => action.id === props.selectedEmailActionId ); if ( @@ -288,7 +288,7 @@ export const Step1: React.FC = (props: GetStep1Props) => { iconType="pencil" onClick={() => { const editAction = - props.emailActions.find(action => action.id === props.selectedEmailActionId) || + props.emailActions.find((action) => action.id === props.selectedEmailActionId) || null; props.setEditAction(editAction); }} diff --git a/x-pack/plugins/monitoring/public/components/alerts/configuration/step2.tsx b/x-pack/plugins/monitoring/public/components/alerts/configuration/step2.tsx index 974dd8513d231..2c215e310af69 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/configuration/step2.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/configuration/step2.tsx @@ -30,7 +30,7 @@ export const Step2: React.FC = (props: GetStep2Props) => { props.setEmailAddress(e.target.value)} + onChange={(e) => props.setEmailAddress(e.target.value)} /> diff --git a/x-pack/plugins/monitoring/public/components/alerts/manage_email_action.tsx b/x-pack/plugins/monitoring/public/components/alerts/manage_email_action.tsx index 3ef9654076340..87588a435078d 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/manage_email_action.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/manage_email_action.tsx @@ -108,7 +108,7 @@ export const ManageEmailAction: React.FC = ( } } - const serviceOptions = ALERT_EMAIL_SERVICES.map(service => ({ + const serviceOptions = ALERT_EMAIL_SERVICES.map((service) => ({ value: service, inputDisplay: {service}, dropdownDisplay: {service}, @@ -139,7 +139,7 @@ export const ManageEmailAction: React.FC = ( setNewService(e.target.value)} + onChange={(e) => setNewService(e.target.value)} isInvalid={showErrors} /> @@ -166,7 +166,7 @@ export const ManageEmailAction: React.FC = ( { + onChange={(id) => { if (id === NEW_SERVICE_ID) { setCreateNewService(true); setData({ ...data, service: NEW_SERVICE_ID }); @@ -194,7 +194,7 @@ export const ManageEmailAction: React.FC = ( > setData({ ...data, host: e.target.value })} + onChange={(e) => setData({ ...data, host: e.target.value })} isInvalid={showErrors && !!errors.host} /> @@ -211,7 +211,7 @@ export const ManageEmailAction: React.FC = ( > setData({ ...data, port: parseInt(e.target.value, 10) })} + onChange={(e) => setData({ ...data, port: parseInt(e.target.value, 10) })} isInvalid={showErrors && !!errors.port} /> @@ -227,7 +227,7 @@ export const ManageEmailAction: React.FC = ( setData({ ...data, secure: e.target.checked })} + onChange={(e) => setData({ ...data, secure: e.target.checked })} /> @@ -243,7 +243,7 @@ export const ManageEmailAction: React.FC = ( > setData({ ...data, from: e.target.value })} + onChange={(e) => setData({ ...data, from: e.target.value })} isInvalid={showErrors && !!errors.from} /> @@ -260,7 +260,7 @@ export const ManageEmailAction: React.FC = ( > setData({ ...data, user: e.target.value })} + onChange={(e) => setData({ ...data, user: e.target.value })} isInvalid={showErrors && !!errors.user} /> @@ -277,7 +277,7 @@ export const ManageEmailAction: React.FC = ( > setData({ ...data, password: e.target.value })} + onChange={(e) => setData({ ...data, password: e.target.value })} isInvalid={showErrors && !!errors.password} /> diff --git a/x-pack/plugins/monitoring/public/components/alerts/status.test.tsx b/x-pack/plugins/monitoring/public/components/alerts/status.test.tsx index a0031f50951bd..1c35328d2f881 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/status.test.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/status.test.tsx @@ -71,7 +71,7 @@ describe('Status', () => { it('should render a success message if all alerts have been migrated and in setup mode', async () => { (Legacy.shims.kfetch as jest.Mock).mockReturnValue({ - data: ALERT_TYPES.map(type => ({ alertTypeId: type })), + data: ALERT_TYPES.map((type) => ({ alertTypeId: type })), }); (getSetupModeState as jest.Mock).mockReturnValue({ diff --git a/x-pack/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/plugins/monitoring/public/components/alerts/status.tsx index cdddbf1031303..6f72168f5069b 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/status.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/status.tsx @@ -18,7 +18,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Legacy } from '../../legacy_shims'; -import { Alert, BASE_ALERT_API_PATH } from '../../../../../plugins/alerting/common'; +import { Alert, BASE_ALERT_API_PATH } from '../../../../alerts/common'; import { getSetupModeState, addSetupModeCallback, toggleSetupMode } from '../../lib/setup_mode'; import { NUMBER_OF_MIGRATED_ALERTS, ALERT_TYPE_PREFIX } from '../../../common/constants'; import { AlertsConfiguration } from './configuration'; diff --git a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js index a48fbc51341f1..9d0d2c5aefa56 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js @@ -79,28 +79,28 @@ function getColumns(setupMode) { defaultMessage: 'Total Events Rate', }), field: 'total_events_rate', - render: value => formatMetric(value, '', '/s'), + render: (value) => formatMetric(value, '', '/s'), }, { name: i18n.translate('xpack.monitoring.apm.instances.bytesSentRateTitle', { defaultMessage: 'Bytes Sent Rate', }), field: 'bytes_sent_rate', - render: value => formatMetric(value, 'byte', '/s'), + render: (value) => formatMetric(value, 'byte', '/s'), }, { name: i18n.translate('xpack.monitoring.apm.instances.outputErrorsTitle', { defaultMessage: 'Output Errors', }), field: 'errors', - render: value => formatMetric(value, '0'), + render: (value) => formatMetric(value, '0'), }, { name: i18n.translate('xpack.monitoring.apm.instances.lastEventTitle', { defaultMessage: 'Last Event', }), field: 'time_of_last_event', - render: value => + render: (value) => i18n.translate('xpack.monitoring.apm.instances.lastEventValue', { defaultMessage: '{timeOfLastEvent} ago', values: { @@ -113,7 +113,7 @@ function getColumns(setupMode) { defaultMessage: 'Allocated Memory', }), field: 'memory', - render: value => formatMetric(value, 'byte'), + render: (value) => formatMetric(value, 'byte'), }, { name: i18n.translate('xpack.monitoring.apm.instances.versionTitle', { @@ -138,7 +138,7 @@ export function ApmServerInstances({ apms, setupMode }) { ); } - const versions = uniq(data.apms.map(item => item.version)).map(version => { + const versions = uniq(data.apms.map((item) => item.version)).map((version) => { return { value: version }; }); diff --git a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js index 5863f6e5161ad..81ae6796d7294 100644 --- a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js @@ -86,28 +86,28 @@ export class Listing extends PureComponent { defaultMessage: 'Total Events Rate', }), field: 'total_events_rate', - render: value => formatMetric(value, '', '/s'), + render: (value) => formatMetric(value, '', '/s'), }, { name: i18n.translate('xpack.monitoring.beats.instances.bytesSentRateTitle', { defaultMessage: 'Bytes Sent Rate', }), field: 'bytes_sent_rate', - render: value => formatMetric(value, 'byte', '/s'), + render: (value) => formatMetric(value, 'byte', '/s'), }, { name: i18n.translate('xpack.monitoring.beats.instances.outputErrorsTitle', { defaultMessage: 'Output Errors', }), field: 'errors', - render: value => formatMetric(value, '0'), + render: (value) => formatMetric(value, '0'), }, { name: i18n.translate('xpack.monitoring.beats.instances.allocatedMemoryTitle', { defaultMessage: 'Allocated Memory', }), field: 'memory', - render: value => formatMetric(value, 'byte'), + render: (value) => formatMetric(value, 'byte'), }, { name: i18n.translate('xpack.monitoring.beats.instances.versionTitle', { @@ -132,11 +132,11 @@ export class Listing extends PureComponent { ); } - const types = uniq(data.map(item => item.type)).map(type => { + const types = uniq(data.map((item) => item.type)).map((type) => { return { value: type }; }); - const versions = uniq(data.map(item => item.version)).map(version => { + const versions = uniq(data.map((item) => item.version)).map((version) => { return { value: version }; }); diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_color.js b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_color.js index c1f0d3e8bbb23..7b70f1d1fb224 100644 --- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_color.js +++ b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_color.js @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { getColor } from '../get_color'; -describe('getColors', function() { +describe('getColors', function () { it('elasticsearch colors', () => { expect(getColor('elasticsearch', 0)).to.be('#3ebeb0'); expect(getColor('elasticsearch', 1)).to.be('#3b73ac'); diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_last_value.js b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_last_value.js index bc469b7149e2c..c089b47408e81 100644 --- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_last_value.js +++ b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_last_value.js @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { getLastValue } from '../get_last_value'; -describe('monitoringChartGetLastValue', function() { +describe('monitoringChartGetLastValue', function () { it('getLastValue for single number', () => { expect(getLastValue(3)).to.be(3); }); diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_title.js b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_title.js index 66100ee573aca..a7f4b48a5862a 100644 --- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_title.js +++ b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_title.js @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { getTitle } from '../get_title'; -describe('getTitle', function() { +describe('getTitle', function () { it('with metric.title', () => { const series = [ { metric: { title: 'Foo', label: 'Bar X' } }, diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_values_for_legend.js b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_values_for_legend.js index 59386996678dc..e5c933426efcd 100644 --- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_values_for_legend.js +++ b/x-pack/plugins/monitoring/public/components/chart/__tests__/get_values_for_legend.js @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; import { findIndexByX, getValuesByX, getValuesForSeriesIndex } from '../get_values_for_legend'; -describe('monitoringChartHelpers', function() { +describe('monitoringChartHelpers', function () { it('getValuesForSeriesIndex sets does not impact callback without series', () => { const callback = sinon.stub(); diff --git a/x-pack/plugins/monitoring/public/components/chart/chart_target.js b/x-pack/plugins/monitoring/public/components/chart/chart_target.js index e6a6cc4b77755..31199c5b092f6 100644 --- a/x-pack/plugins/monitoring/public/components/chart/chart_target.js +++ b/x-pack/plugins/monitoring/public/components/chart/chart_target.js @@ -42,8 +42,8 @@ export class ChartTarget extends React.Component { filterByShow(seriesToShow) { if (seriesToShow) { - return metric => { - return seriesToShow.some(id => id.toLowerCase() === metric.id.toLowerCase()); + return (metric) => { + return seriesToShow.some((id) => id.toLowerCase() === metric.id.toLowerCase()); }; } return () => true; @@ -73,9 +73,7 @@ export class ChartTarget extends React.Component { } filterData(data, seriesToShow) { - return _(data) - .filter(this.filterByShow(seriesToShow)) - .value(); + return _(data).filter(this.filterByShow(seriesToShow)).value(); } async getOptions() { diff --git a/x-pack/plugins/monitoring/public/components/chart/get_title.js b/x-pack/plugins/monitoring/public/components/chart/get_title.js index 4450352add45f..bb40551fe8428 100644 --- a/x-pack/plugins/monitoring/public/components/chart/get_title.js +++ b/x-pack/plugins/monitoring/public/components/chart/get_title.js @@ -12,7 +12,7 @@ import { chain } from 'lodash'; */ export function getTitle(series = []) { return chain( - series.map(s => { + series.map((s) => { return s.metric.title || s.metric.label; }) ) diff --git a/x-pack/plugins/monitoring/public/components/chart/get_units.js b/x-pack/plugins/monitoring/public/components/chart/get_units.js index e8348059a6d4d..fd377cced122c 100644 --- a/x-pack/plugins/monitoring/public/components/chart/get_units.js +++ b/x-pack/plugins/monitoring/public/components/chart/get_units.js @@ -13,7 +13,7 @@ export function getUnits(series) { // For Bytes, find the largest unit from any data set's _last_ item if (units === 'B') { let maxLastBytes = 0; - forEach(series, s => { + forEach(series, (s) => { const lastDataPoint = last(s.data) || [null, 0]; maxLastBytes = Math.max(maxLastBytes, lastDataPoint[1]); // lastDataPoint[1] is the "y" value }); diff --git a/x-pack/plugins/monitoring/public/components/chart/horizontal_legend.js b/x-pack/plugins/monitoring/public/components/chart/horizontal_legend.js index ab322324ac200..738775ed8d4d9 100644 --- a/x-pack/plugins/monitoring/public/components/chart/horizontal_legend.js +++ b/x-pack/plugins/monitoring/public/components/chart/horizontal_legend.js @@ -71,7 +71,7 @@ export class HorizontalLegend extends React.Component { + + +
    diff --git a/x-pack/plugins/monitoring/public/views/access_denied/index.js b/x-pack/plugins/monitoring/public/views/access_denied/index.js index 856e59702963a..f7a4d03a26452 100644 --- a/x-pack/plugins/monitoring/public/views/access_denied/index.js +++ b/x-pack/plugins/monitoring/public/views/access_denied/index.js @@ -4,16 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { noop } from 'lodash'; +import { kbnBaseUrl } from '../../../../../../src/plugins/kibana_legacy/common/kbn_base_url'; import { uiRoutes } from '../../angular/helpers/routes'; -import { Legacy } from '../../legacy_shims'; import template from './index.html'; const tryPrivilege = ($http, kbnUrl) => { return $http .get('../api/monitoring/v1/check_access') .then(() => kbnUrl.redirect('/home')) - .catch(noop); + .catch(() => true); }; uiRoutes.when('/access-denied', { @@ -31,17 +30,13 @@ uiRoutes.when('/access-denied', { }, }, controllerAs: 'accessDenied', - controller($scope, $injector) { - const $window = $injector.get('$window'); - const kbnBaseUrl = $injector.get('kbnBaseUrl'); + controller: function ($scope, $injector) { const $http = $injector.get('$http'); const kbnUrl = $injector.get('kbnUrl'); const $interval = $injector.get('$interval'); // The template's "Back to Kibana" button click handler - this.goToKibana = () => { - $window.location.href = Legacy.shims.getBasePath() + kbnBaseUrl; - }; + this.goToKibanaURL = kbnBaseUrl; // keep trying to load data in the background const accessPoller = $interval(() => tryPrivilege($http, kbnUrl), 5 * 1000); // every 5 seconds diff --git a/x-pack/plugins/monitoring/public/views/alerts/index.js b/x-pack/plugins/monitoring/public/views/alerts/index.js index 2e7a34c0aef29..ce27e1a9df26d 100644 --- a/x-pack/plugins/monitoring/public/views/alerts/index.js +++ b/x-pack/plugins/monitoring/public/views/alerts/index.js @@ -41,14 +41,14 @@ function getPageData($injector) { return $http .post(url, data) - .then(response => { + .then((response) => { const result = get(response, 'data', []); if (KIBANA_ALERTING_ENABLED) { return result.alerts; } return result; }) - .catch(err => { + .catch((err) => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); }); @@ -88,7 +88,7 @@ uiRoutes.when('/alerts', { this.data = $route.current.locals.alerts; - const renderReact = data => { + const renderReact = (data) => { const app = data.message ? (

    {data.message}

    ) : ( @@ -121,7 +121,7 @@ uiRoutes.when('/alerts', { }; $scope.$watch( () => this.data, - data => renderReact(data) + (data) => renderReact(data) ); } }, diff --git a/x-pack/plugins/monitoring/public/views/apm/instance/index.js b/x-pack/plugins/monitoring/public/views/apm/instance/index.js index 982857ab5aea4..c53a3a39f0523 100644 --- a/x-pack/plugins/monitoring/public/views/apm/instance/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/instance/index.js @@ -23,7 +23,7 @@ import { CODE_PATH_APM } from '../../../../common/constants'; uiRoutes.when('/apm/instances/:uuid', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_APM] }); }, @@ -54,7 +54,7 @@ uiRoutes.when('/apm/instances/:uuid', { $scope.$watch( () => this.data, - data => { + (data) => { title($scope.cluster, `APM - ${get(data, 'apmSummary.name')}`); this.renderReact(data); } diff --git a/x-pack/plugins/monitoring/public/views/apm/instances/index.js b/x-pack/plugins/monitoring/public/views/apm/instances/index.js index 8cd0b03e89e04..82f1155e89e4e 100644 --- a/x-pack/plugins/monitoring/public/views/apm/instances/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/instances/index.js @@ -18,7 +18,7 @@ import { APM_SYSTEM_ID, CODE_PATH_APM } from '../../../../common/constants'; uiRoutes.when('/apm/instances', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_APM] }); }, @@ -51,7 +51,7 @@ uiRoutes.when('/apm/instances', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact(data); } ); diff --git a/x-pack/plugins/monitoring/public/views/apm/overview/index.js b/x-pack/plugins/monitoring/public/views/apm/overview/index.js index 1fdd441e62e22..7e6c9dd116c98 100644 --- a/x-pack/plugins/monitoring/public/views/apm/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/overview/index.js @@ -16,7 +16,7 @@ import { CODE_PATH_APM } from '../../../../common/constants'; uiRoutes.when('/apm', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_APM] }); }, @@ -40,7 +40,7 @@ uiRoutes.when('/apm', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact(data); } ); diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js index e5e59f2f8e826..e189491a3be03 100644 --- a/x-pack/plugins/monitoring/public/views/base_controller.js +++ b/x-pack/plugins/monitoring/public/views/base_controller.js @@ -19,7 +19,7 @@ import { updateSetupModeData, getSetupModeState } from '../lib/setup_mode'; * * @param {string} timezone */ -const getOffsetInMS = timezone => { +const getOffsetInMS = (timezone) => { if (timezone === 'Browser') { return 0; } @@ -189,7 +189,7 @@ export class MonitoringViewBaseController { $executor.destroy(); }); - this.setTitle = title => titleService($scope.cluster, title); + this.setTitle = (title) => titleService($scope.cluster, title); } renderReact(component) { diff --git a/x-pack/plugins/monitoring/public/views/base_table_controller.js b/x-pack/plugins/monitoring/public/views/base_table_controller.js index 2275608c473dd..af9b4b5d2b6b9 100644 --- a/x-pack/plugins/monitoring/public/views/base_table_controller.js +++ b/x-pack/plugins/monitoring/public/views/base_table_controller.js @@ -45,7 +45,7 @@ export class MonitoringViewBaseTableController extends MonitoringViewBaseControl this.sortKey = sortKey; this.sortOrder = sortOrder; - this.onNewState = newState => { + this.onNewState = (newState) => { setLocalStorageData(storage, newState); }; } diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js index 7c9c459218529..aaa8360251c7e 100644 --- a/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js @@ -22,8 +22,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/index.js b/x-pack/plugins/monitoring/public/views/beats/beat/index.js index 9d5f9b4d562a6..8b5aeb016080f 100644 --- a/x-pack/plugins/monitoring/public/views/beats/beat/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/beat/index.js @@ -16,7 +16,7 @@ import { CODE_PATH_BEATS } from '../../../../common/constants'; uiRoutes.when('/beats/beat/:beatUuid', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_BEATS] }); }, diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js index 77942303afcc2..d9985a8b2666b 100644 --- a/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js @@ -21,8 +21,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/index.js b/x-pack/plugins/monitoring/public/views/beats/listing/index.js index 7d011089fdd7d..18a7ac6709437 100644 --- a/x-pack/plugins/monitoring/public/views/beats/listing/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/listing/index.js @@ -19,7 +19,7 @@ import { CODE_PATH_BEATS, BEATS_SYSTEM_ID } from '../../../../common/constants'; uiRoutes.when('/beats/beats', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_BEATS] }); }, diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js index e5d576961b797..8063067f72a98 100644 --- a/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js @@ -21,8 +21,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/index.js b/x-pack/plugins/monitoring/public/views/beats/overview/index.js index 0eb39ef372263..36c20490f8616 100644 --- a/x-pack/plugins/monitoring/public/views/beats/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/overview/index.js @@ -16,7 +16,7 @@ import { CODE_PATH_BEATS } from '../../../../common/constants'; uiRoutes.when('/beats', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_BEATS] }); }, diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js index 42be4f02f5c94..ede6ccd3279c6 100644 --- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js +++ b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js @@ -14,7 +14,7 @@ import { CODE_PATH_ALL } from '../../../../common/constants'; const CODE_PATHS = [CODE_PATH_ALL]; -const getPageData = $injector => { +const getPageData = ($injector) => { const monitoringClusters = $injector.get('monitoringClusters'); return monitoringClusters(undefined, undefined, CODE_PATHS); }; @@ -25,7 +25,7 @@ uiRoutes resolve: { clusters: (Private, kbnUrl) => { const routeInit = Private(routeInitProvider); - return routeInit({ codePaths: CODE_PATHS, fetchAllClusters: true }).then(clusters => { + return routeInit({ codePaths: CODE_PATHS, fetchAllClusters: true }).then((clusters) => { if (!clusters || !clusters.length) { kbnUrl.changePath('/no-data'); return Promise.reject(); @@ -60,7 +60,7 @@ uiRoutes $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact(
    { + const changeUrl = (target) => { $scope.$evalAsync(() => { kbnUrl.changePath(target); }); @@ -64,7 +64,7 @@ uiRoutes.when('/overview', { $scope.$watch( () => this.data, - async data => { + async (data) => { if (isEmpty(data)) { return; } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js index d4a86b00a7505..19050010e038b 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js @@ -21,8 +21,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js index 88d3a3614243f..a1c543cf045e8 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js @@ -17,7 +17,7 @@ import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants'; uiRoutes.when('/elasticsearch/ccr', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] }); }, @@ -38,7 +38,7 @@ uiRoutes.when('/elasticsearch/ccr', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact(data); } ); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js index 20f39edbb6a75..0f724edd92090 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js @@ -22,8 +22,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js index 260422d322a2d..05da852700f62 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js @@ -18,7 +18,7 @@ import { CODE_PATH_ELASTICSEARCH } from '../../../../../common/constants'; uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] }); }, @@ -47,12 +47,12 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact(data); } ); - this.renderReact = props => { + this.renderReact = (props) => { super.renderReact(); }; } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js index 9fbaf6c00725d..f4b0f0789bae1 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js @@ -34,8 +34,8 @@ function getPageData($injector) { }, is_advanced: true, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -45,7 +45,7 @@ function getPageData($injector) { uiRoutes.when('/elasticsearch/indices/:index/advanced', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] }); }, @@ -75,7 +75,7 @@ uiRoutes.when('/elasticsearch/indices/:index/advanced', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact( response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -79,7 +79,7 @@ uiRoutes.when('/elasticsearch/indices/:index', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.shards) { return; } @@ -88,7 +88,7 @@ uiRoutes.when('/elasticsearch/indices/:index', { $scope.totalCount = shards.length; $scope.showing = transformer(shards, data.nodes); $scope.labels = labels.node; - if (shards.some(shard => shard.state === 'UNASSIGNED')) { + if (shards.some((shard) => shard.state === 'UNASSIGNED')) { $scope.labels = labels.indexWithUnassigned; } else { $scope.labels = labels.index; diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js index ee177c3e8ed04..69a5a4242bccb 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js @@ -52,7 +52,7 @@ uiRoutes.when('/elasticsearch/indices', { this.isCcrEnabled = $scope.cluster.isCcrEnabled; // for binding - const toggleShowSystemIndices = isChecked => { + const toggleShowSystemIndices = (isChecked) => { // flip the boolean showSystemIndices = isChecked; // preserve setting in localStorage @@ -63,7 +63,7 @@ uiRoutes.when('/elasticsearch/indices', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact(data); } ); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js index 0b50a04d53036..fef855f8b33e1 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js @@ -20,8 +20,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js index d1dd81223ad5e..79cb06b955aad 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js @@ -16,7 +16,7 @@ import { CODE_PATH_ELASTICSEARCH, CODE_PATH_ML } from '../../../../common/consta uiRoutes.when('/elasticsearch/ml_jobs', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH, CODE_PATH_ML] }); }, diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js index 5c45509fce37c..8157e5b5f67c7 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js @@ -34,8 +34,8 @@ function getPageData($injector) { }, is_advanced: true, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -45,7 +45,7 @@ function getPageData($injector) { uiRoutes.when('/elasticsearch/nodes/:node/advanced', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] }); }, @@ -63,7 +63,7 @@ uiRoutes.when('/elasticsearch/nodes/:node/advanced', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.nodeSummary) { return; } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js index 6aaa8aa452f68..631ae777beb04 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js @@ -26,8 +26,8 @@ export function getPageData($injector) { }, is_advanced: false, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js index c34be490d1711..1ef0f8b66227b 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js @@ -23,7 +23,7 @@ import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants'; uiRoutes.when('/elasticsearch/nodes/:node', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] }); }, @@ -56,18 +56,18 @@ uiRoutes.when('/elasticsearch/nodes/:node', { const callPageData = partial(getPageData, $injector); // show/hide system indices in shard allocation view $scope.showSystemIndices = features.isEnabled('showSystemIndices', false); - $scope.toggleShowSystemIndices = isChecked => { + $scope.toggleShowSystemIndices = (isChecked) => { $scope.showSystemIndices = isChecked; // preserve setting in localStorage features.update('showSystemIndices', isChecked); // update the page - callPageData().then(data => (this.data = data)); + callPageData().then((data) => (this.data = data)); }; const transformer = nodesByIndices(); $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.shards) { return; } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js index db4aaca15c00e..4029b730ad715 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js @@ -53,10 +53,12 @@ uiRoutes.when('/elasticsearch/nodes', { ...routeOptions, }); - const promise = globalState.cluster_uuid ? getNodes() : new Promise(resolve => resolve({})); + const promise = globalState.cluster_uuid + ? getNodes() + : new Promise((resolve) => resolve({})); return promise - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js index 7cdd7dfae0af0..b8738ad6fa9fd 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js @@ -46,7 +46,7 @@ export class ElasticsearchOverviewController extends MonitoringViewBaseControlle initScope($scope) { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact(data, $scope.cluster); } ); @@ -67,7 +67,7 @@ export class ElasticsearchOverviewController extends MonitoringViewBaseControlle } filterShardActivityData(shardActivity) { - return shardActivity.filter(row => { + return shardActivity.filter((row) => { return this.showShardActivityHistory || row.stage !== 'DONE'; }); } diff --git a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js b/x-pack/plugins/monitoring/public/views/kibana/instance/index.js index b743b4e49f096..802c0e3d30d5b 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js +++ b/x-pack/plugins/monitoring/public/views/kibana/instance/index.js @@ -43,8 +43,8 @@ function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -74,7 +74,7 @@ uiRoutes.when('/kibana/instances/:uuid', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.metrics) { return; } diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js b/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js index 9f78bd07ecbf8..bb1ae43017e33 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js @@ -21,8 +21,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js index d179928ded693..01b02f609ea26 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js +++ b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js @@ -67,7 +67,7 @@ uiRoutes.when('/kibana/instances', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data) { return; } diff --git a/x-pack/plugins/monitoring/public/views/kibana/overview/index.js b/x-pack/plugins/monitoring/public/views/kibana/overview/index.js index b0be4f7862a91..57cee268a0c31 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/kibana/overview/index.js @@ -41,8 +41,8 @@ function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -52,7 +52,7 @@ function getPageData($injector) { uiRoutes.when('/kibana', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_KIBANA] }); }, @@ -72,7 +72,7 @@ uiRoutes.when('/kibana', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.clusterStatus) { return; } diff --git a/x-pack/plugins/monitoring/public/views/license/index.js b/x-pack/plugins/monitoring/public/views/license/index.js index 46e93a8f01f45..2fbefcd4d59c9 100644 --- a/x-pack/plugins/monitoring/public/views/license/index.js +++ b/x-pack/plugins/monitoring/public/views/license/index.js @@ -13,7 +13,7 @@ import { CODE_PATH_LICENSE } from '../../../common/constants'; uiRoutes.when('/license', { template, resolve: { - clusters: Private => { + clusters: (Private) => { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_LICENSE] }); }, diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js index 4099a99f122f2..de4cc1eb7e2d1 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js @@ -44,8 +44,8 @@ function getPageData($injector) { }, is_advanced: true, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -73,7 +73,7 @@ uiRoutes.when('/logstash/node/:uuid/advanced', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.nodeSummary) { return; } diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/index.js index 141761d8cc11a..9dfb7cd24432c 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/index.js @@ -44,8 +44,8 @@ function getPageData($injector) { }, is_advanced: false, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -73,7 +73,7 @@ uiRoutes.when('/logstash/node/:uuid', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.nodeSummary) { return; } diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js index 442e8533c18f6..78426a503337f 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js @@ -41,8 +41,8 @@ const getPageData = ($injector, _api = undefined, routeOptions = {}) => { }, ...routeOptions, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); }); @@ -86,7 +86,7 @@ uiRoutes.when('/logstash/node/:uuid/pipelines', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.nodeSummary) { return; } diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js index 1d5d6007814f4..28b35febb54cc 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js +++ b/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js @@ -21,8 +21,8 @@ export function getPageData($injector) { max: timeBounds.max.toISOString(), }, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js index 49b2a20f11ea2..102e245b541c4 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js @@ -38,7 +38,7 @@ uiRoutes.when('/logstash/nodes', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact( response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); @@ -42,7 +42,7 @@ function getPageData($injector) { uiRoutes.when('/logstash', { template, resolve: { - clusters: function(Private) { + clusters: function (Private) { const routeInit = Private(routeInitProvider); return routeInit({ codePaths: [CODE_PATH_LOGSTASH] }); }, @@ -60,7 +60,7 @@ uiRoutes.when('/logstash', { $scope.$watch( () => this.data, - data => { + (data) => { this.renderReact( response.data) - .then(data => { - data.versions = data.versions.map(version => { + .then((response) => response.data) + .then((data) => { + data.versions = data.versions.map((version) => { const relativeFirstSeen = formatTimestampToDuration( version.firstSeen, CALCULATE_DURATION_SINCE @@ -90,7 +90,7 @@ function getPageData($injector) { return data; }) - .catch(err => { + .catch((err) => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); }); @@ -124,9 +124,9 @@ uiRoutes.when('/logstash/pipelines/:id/:hash?', { $injector, }); - const timeseriesTooltipXValueFormatter = xValue => moment(xValue).format(dateFormat); + const timeseriesTooltipXValueFormatter = (xValue) => moment(xValue).format(dateFormat); - const setDetailVertexId = vertex => { + const setDetailVertexId = (vertex) => { if (!vertex) { detailVertexId = undefined; } else { @@ -138,7 +138,7 @@ uiRoutes.when('/logstash/pipelines/:id/:hash?', { $scope.$watch( () => this.data, - data => { + (data) => { if (!data || !data.pipeline) { return; } diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js index 4ddaba1e0a7c9..cf8d6663069fe 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js @@ -39,8 +39,8 @@ const getPageData = ($injector, _api = undefined, routeOptions = {}) => { }, ...routeOptions, }) - .then(response => response.data) - .catch(err => { + .then((response) => response.data) + .catch((err) => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); }); @@ -87,7 +87,7 @@ uiRoutes.when('/logstash/pipelines', { cluster_uuid: globalState.cluster_uuid, }); - const renderReact = pageData => { + const renderReact = (pageData) => { if (!pageData) { return; } @@ -104,7 +104,7 @@ uiRoutes.when('/logstash/pipelines', { super.renderReact( this.onBrush({ xaxis })} + onBrush={(xaxis) => this.onBrush({ xaxis })} stats={pageData.clusterStatus} data={pageData.pipelines} {...this.getPaginationTableProps(pagination)} @@ -120,7 +120,7 @@ uiRoutes.when('/logstash/pipelines', { $scope.$watch( () => this.data, - pageData => { + (pageData) => { renderReact(pageData); } ); diff --git a/x-pack/plugins/monitoring/public/views/no_data/__tests__/model_updater.test.js b/x-pack/plugins/monitoring/public/views/no_data/__tests__/model_updater.test.js index 5d71a630296b3..8be378be84189 100644 --- a/x-pack/plugins/monitoring/public/views/no_data/__tests__/model_updater.test.js +++ b/x-pack/plugins/monitoring/public/views/no_data/__tests__/model_updater.test.js @@ -14,7 +14,7 @@ describe('Model Updater for Angular Controller with React Components', () => { beforeEach(() => { $scope = {}; - $scope.$evalAsync = cb => cb(); + $scope.$evalAsync = (cb) => cb(); model = {}; diff --git a/x-pack/plugins/monitoring/public/views/no_data/controller.js b/x-pack/plugins/monitoring/public/views/no_data/controller.js index 14c30da2ce999..3e6c07e936b95 100644 --- a/x-pack/plugins/monitoring/public/views/no_data/controller.js +++ b/x-pack/plugins/monitoring/public/views/no_data/controller.js @@ -82,7 +82,7 @@ export class NoDataController extends MonitoringViewBaseController { true ); - this.changePath = path => kbnUrl.changePath(path); + this.changePath = (path) => kbnUrl.changePath(path); } getDefaultModel() { diff --git a/x-pack/plugins/monitoring/public/views/no_data/model_updater.js b/x-pack/plugins/monitoring/public/views/no_data/model_updater.js index c9c23324951f4..aa07a595f2a5c 100644 --- a/x-pack/plugins/monitoring/public/views/no_data/model_updater.js +++ b/x-pack/plugins/monitoring/public/views/no_data/model_updater.js @@ -25,7 +25,7 @@ export class ModelUpdater { const { $scope, model } = this; const keys = Object.keys(properties); $scope.$evalAsync(() => { - keys.forEach(key => { + keys.forEach((key) => { if (Array.isArray(model[key])) { model[key].push(properties[key]); } else { diff --git a/x-pack/plugins/monitoring/server/__tests__/deprecations.js b/x-pack/plugins/monitoring/server/__tests__/deprecations.js index 5fc5debfa139e..42621ea3549b4 100644 --- a/x-pack/plugins/monitoring/server/__tests__/deprecations.js +++ b/x-pack/plugins/monitoring/server/__tests__/deprecations.js @@ -9,20 +9,20 @@ import expect from '@kbn/expect'; import { deprecations as deprecationsModule } from '../deprecations'; import sinon from 'sinon'; -describe('monitoring plugin deprecations', function() { +describe('monitoring plugin deprecations', function () { let transformDeprecations; const rename = sinon.stub().returns(() => {}); const fromPath = 'monitoring'; - before(function() { + before(function () { const deprecations = deprecationsModule({ rename }); transformDeprecations = (settings, fromPath, log = noop) => { - deprecations.forEach(deprecation => deprecation(settings, fromPath, log)); + deprecations.forEach((deprecation) => deprecation(settings, fromPath, log)); }; }); - describe('cluster_alerts.email_notifications.email_address', function() { - it(`shouldn't log when email notifications are disabled`, function() { + describe('cluster_alerts.email_notifications.email_address', function () { + it(`shouldn't log when email notifications are disabled`, function () { const settings = { cluster_alerts: { email_notifications: { @@ -36,7 +36,7 @@ describe('monitoring plugin deprecations', function() { expect(log.called).to.be(false); }); - it(`shouldn't log when cluster alerts are disabled`, function() { + it(`shouldn't log when cluster alerts are disabled`, function () { const settings = { cluster_alerts: { enabled: false, @@ -51,7 +51,7 @@ describe('monitoring plugin deprecations', function() { expect(log.called).to.be(false); }); - it(`shouldn't log when email_address is specified`, function() { + it(`shouldn't log when email_address is specified`, function () { const settings = { cluster_alerts: { enabled: true, @@ -67,7 +67,7 @@ describe('monitoring plugin deprecations', function() { expect(log.called).to.be(false); }); - it(`should log when email_address is missing, but alerts/notifications are both enabled`, function() { + it(`should log when email_address is missing, but alerts/notifications are both enabled`, function () { const settings = { cluster_alerts: { enabled: true, @@ -83,7 +83,7 @@ describe('monitoring plugin deprecations', function() { }); }); - describe('elasticsearch.username', function() { + describe('elasticsearch.username', function () { it('logs a warning if elasticsearch.username is set to "elastic"', () => { const settings = { elasticsearch: { username: 'elastic' } }; diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts index bcc1a8abe5cb0..6262036037712 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts @@ -10,7 +10,7 @@ import { AlertCommonParams, AlertCommonState, AlertClusterStatePerClusterState } import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; import { executeActions } from '../lib/alerts/cluster_state.lib'; import { AlertClusterStateState } from './enums'; -import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../alerts/server/mocks'; jest.mock('../lib/alerts/cluster_state.lib', () => ({ executeActions: jest.fn(), diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts index 6567d1c6def31..5b6521179002a 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; -import { AlertType } from '../../../alerting/server'; +import { AlertType } from '../../../alerts/server'; import { executeActions, getUiMessage } from '../lib/alerts/cluster_state.lib'; import { AlertCommonExecutorOptions, diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index f9d2ec3e1d48e..fb8d10884fdc7 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -16,7 +16,7 @@ import { } from './types'; import { executeActions } from '../lib/alerts/license_expiration.lib'; import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; -import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../alerts/server/mocks'; jest.mock('../lib/alerts/license_expiration.lib', () => ({ executeActions: jest.fn(), @@ -112,9 +112,7 @@ describe('getLicenseExpiration', () => { }); it('should fire actions if going to expire', async () => { - const expiryDateMS = moment() - .add(7, 'days') - .valueOf(); + const expiryDateMS = moment().add(7, 'days').valueOf(); const license = { status: 'active', type: 'gold', @@ -134,9 +132,7 @@ describe('getLicenseExpiration', () => { }); it('should fire actions if the user fixed their license', async () => { - const expiryDateMS = moment() - .add(365, 'days') - .valueOf(); + const expiryDateMS = moment().add(365, 'days').valueOf(); const license = { status: 'active', type: 'gold', @@ -157,9 +153,7 @@ describe('getLicenseExpiration', () => { }); it('should not fire actions for trial license that expire in more than 14 days', async () => { - const expiryDateMS = moment() - .add(20, 'days') - .valueOf(); + const expiryDateMS = moment().add(20, 'days').valueOf(); const license = { status: 'active', type: 'trial', @@ -173,9 +167,7 @@ describe('getLicenseExpiration', () => { }); it('should fire actions for trial license that in 14 days or less', async () => { - const expiryDateMS = moment() - .add(7, 'days') - .valueOf(); + const expiryDateMS = moment().add(7, 'days').valueOf(); const license = { status: 'active', type: 'trial', diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts index 00402bca57a7e..d57f1a7655b18 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; -import { AlertType } from '../../../../plugins/alerting/server'; +import { AlertType } from '../../../alerts/server'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; import { AlertCommonState, diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index b689d008b51a7..67c74635b4e36 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Moment } from 'moment'; -import { AlertExecutorOptions } from '../../../alerting/server'; +import { AlertExecutorOptions } from '../../../alerts/server'; import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from './enums'; export interface AlertLicense { diff --git a/x-pack/plugins/monitoring/server/cloud/__tests__/cloud_service.js b/x-pack/plugins/monitoring/server/cloud/__tests__/cloud_service.js index a95a890aa3ce6..1f6bda9833a01 100644 --- a/x-pack/plugins/monitoring/server/cloud/__tests__/cloud_service.js +++ b/x-pack/plugins/monitoring/server/cloud/__tests__/cloud_service.js @@ -113,7 +113,7 @@ describe('CloudService', () => { }); it('expects unusable bodies', async () => { - const parseBody = parsedBody => { + const parseBody = (parsedBody) => { expect(parsedBody).to.eql(body); return null; @@ -125,7 +125,7 @@ describe('CloudService', () => { it('uses parsed object to create response', async () => { const serviceResponse = new CloudServiceResponse('a123', true, { id: 'xyz' }); - const parseBody = parsedBody => { + const parseBody = (parsedBody) => { expect(parsedBody).to.eql(body); return serviceResponse; diff --git a/x-pack/plugins/monitoring/server/cloud/aws.js b/x-pack/plugins/monitoring/server/cloud/aws.js index a9911b1a5804c..67871a26287fc 100644 --- a/x-pack/plugins/monitoring/server/cloud/aws.js +++ b/x-pack/plugins/monitoring/server/cloud/aws.js @@ -36,7 +36,7 @@ export class AWSCloudService extends CloudService { return ( promisify(request)(req) - .then(response => this._parseResponse(response.body, body => this._parseBody(body))) + .then((response) => this._parseResponse(response.body, (body) => this._parseBody(body))) // fall back to file detection .catch(() => this._tryToDetectUuid()) ); @@ -100,7 +100,7 @@ export class AWSCloudService extends CloudService { _tryToDetectUuid() { // Windows does not have an easy way to check if (!this._isWindows) { - return promisify(this._fs.readFile)('/sys/hypervisor/uuid', 'utf8').then(uuid => { + return promisify(this._fs.readFile)('/sys/hypervisor/uuid', 'utf8').then((uuid) => { if (isString(uuid)) { // Some AWS APIs return it lowercase (like the file did in testing), while others return it uppercase uuid = uuid.trim().toLowerCase(); diff --git a/x-pack/plugins/monitoring/server/cloud/azure.js b/x-pack/plugins/monitoring/server/cloud/azure.js index 631caf905b61f..fb39b687daf0c 100644 --- a/x-pack/plugins/monitoring/server/cloud/azure.js +++ b/x-pack/plugins/monitoring/server/cloud/azure.js @@ -32,8 +32,8 @@ class AzureCloudService extends CloudService { return ( promisify(request)(req) // Note: there is no fallback option for Azure - .then(response => { - return this._parseResponse(response.body, body => this._parseBody(body)); + .then((response) => { + return this._parseResponse(response.body, (body) => this._parseBody(body)); }) ); } diff --git a/x-pack/plugins/monitoring/server/cloud/gcp.js b/x-pack/plugins/monitoring/server/cloud/gcp.js index 8abb5ff39aedf..9f37c28c29e1c 100644 --- a/x-pack/plugins/monitoring/server/cloud/gcp.js +++ b/x-pack/plugins/monitoring/server/cloud/gcp.js @@ -23,7 +23,7 @@ class GCPCloudService extends CloudService { const fields = ['id', 'machine-type', 'zone']; const create = this._createRequestForField; - const allRequests = fields.map(field => promisify(request)(create(field))); + const allRequests = fields.map((field) => promisify(request)(create(field))); return ( Promise.all(allRequests) /* @@ -31,8 +31,8 @@ class GCPCloudService extends CloudService { responses are arrays containing [fullResponse, body]; because GCP returns plaintext, we have no way of validating without using the response code */ - .then(responses => { - return responses.map(response => { + .then((responses) => { + return responses.map((response) => { return this._extractBody(response, response.body); }); }) diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js index aa94adb57e657..3c0460db71791 100644 --- a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js +++ b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js @@ -58,7 +58,7 @@ describe('Alerts Cluster Search', () => { '.monitoring-alerts', { cluster_uuid: 'cluster-1234' }, checkLicense - ).then(alerts => { + ).then((alerts) => { expect(alerts).to.eql(mockAlerts); expect(callWithRequestStub.getCall(0).args[2].body.size).to.be.undefined; }); @@ -72,7 +72,7 @@ describe('Alerts Cluster Search', () => { { cluster_uuid: 'cluster-1234' }, checkLicense, { size: 3 } - ).then(alerts => { + ).then((alerts) => { expect(alerts).to.eql(mockAlerts); expect(callWithRequestStub.getCall(0).args[2].body.size).to.be(3); }); @@ -91,7 +91,7 @@ describe('Alerts Cluster Search', () => { }; return alertsClusterSearch(mockReq, '.monitoring-alerts', cluster, checkLicense, { size: 3, - }).then(alerts => { + }).then((alerts) => { expect(alerts).to.have.length(3); expect(alerts[0]).to.eql(mockAlerts[0]); expect(alerts[1]).to.eql({ @@ -125,7 +125,7 @@ describe('Alerts Cluster Search', () => { }; return alertsClusterSearch(mockReq, '.monitoring-alerts', cluster, checkLicense, { size: 3, - }).then(alerts => { + }).then((alerts) => { expect(alerts).to.have.length(1); expect(alerts[0]).to.eql({ metadata: { @@ -159,7 +159,7 @@ describe('Alerts Cluster Search', () => { '.monitoring-alerts', { cluster_uuid: 'cluster-1234' }, checkLicense - ).then(alerts => { + ).then((alerts) => { const result = { message: 'monitoring cluster license check fail' }; expect(alerts).to.eql(result); expect(checkLicense.called).to.be(false); @@ -181,7 +181,7 @@ describe('Alerts Cluster Search', () => { '.monitoring-alerts', { cluster_uuid: 'cluster-1234' }, checkLicense - ).then(alerts => { + ).then((alerts) => { const result = { message: 'prod goes boom' }; expect(alerts).to.eql(result); expect(checkLicense.calledOnce).to.be(true); diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js index efd9a933e1f81..a64ff2e2b6080 100644 --- a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js +++ b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js @@ -74,7 +74,7 @@ describe('Alerts Clusters Aggregation', () => { it('aggregates alert count summary by cluster', () => { const { mockReq } = createStubs(mockQueryResult, featureStub); return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - result => { + (result) => { expect(result).to.eql({ alertsMeta: { enabled: true }, 'cluster-abc0': undefined, @@ -116,7 +116,7 @@ describe('Alerts Clusters Aggregation', () => { '.monitoring-alerts', newClusters, checkLicense - ).then(result => { + ).then((result) => { expect(result).to.eql({ alertsMeta: { enabled: true }, 'cluster-abc0': { @@ -164,7 +164,7 @@ describe('Alerts Clusters Aggregation', () => { const { mockReq } = createStubs(mockQueryResult, featureStub); return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - result => { + (result) => { expect(result).to.eql({ alertsMeta: { enabled: false, message: 'monitoring cluster license is fail' }, }); @@ -182,7 +182,7 @@ describe('Alerts Clusters Aggregation', () => { const { mockReq } = createStubs(mockQueryResult, featureStub); return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - result => { + (result) => { expect(result).to.eql({ alertsMeta: { enabled: true }, 'cluster-abc0': { diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/fixtures/create_stubs.js b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/fixtures/create_stubs.js index 9fc53cea2f4ee..c57c6cff59e9a 100644 --- a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/fixtures/create_stubs.js +++ b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/fixtures/create_stubs.js @@ -10,10 +10,7 @@ export function createStubs(mockQueryResult, featureStub) { const callWithRequestStub = sinon.stub().returns(Promise.resolve(mockQueryResult)); const getClusterStub = sinon.stub().returns({ callWithRequest: callWithRequestStub }); const configStub = sinon.stub().returns({ - get: sinon - .stub() - .withArgs('xpack.monitoring.cluster_alerts.enabled') - .returns(true), + get: sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true), }); return { callWithRequestStub, diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/verify_monitoring_license.js b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/verify_monitoring_license.js index 965ea0db510b0..08385e8d96a80 100644 --- a/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/verify_monitoring_license.js +++ b/x-pack/plugins/monitoring/server/cluster_alerts/__tests__/verify_monitoring_license.js @@ -10,10 +10,7 @@ import sinon from 'sinon'; describe('Monitoring Verify License', () => { describe('Disabled by Configuration', () => { - const get = sinon - .stub() - .withArgs('xpack.monitoring.cluster_alerts.enabled') - .returns(false); + const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(false); const server = { config: sinon.stub().returns({ get }) }; it('verifyMonitoringLicense returns false without checking the license', () => { @@ -28,10 +25,7 @@ describe('Monitoring Verify License', () => { describe('Enabled by Configuration', () => { it('verifyMonitoringLicense returns false if enabled by configuration, but not by license', () => { - const get = sinon - .stub() - .withArgs('xpack.monitoring.cluster_alerts.enabled') - .returns(true); + const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true); const server = { config: sinon.stub().returns({ get }), plugins: { monitoring: { info: {} } }, @@ -39,10 +33,7 @@ describe('Monitoring Verify License', () => { const getLicenseCheckResults = sinon .stub() .returns({ clusterAlerts: { enabled: false }, message: 'failed!!' }); - const feature = sinon - .stub() - .withArgs('monitoring') - .returns({ getLicenseCheckResults }); + const feature = sinon.stub().withArgs('monitoring').returns({ getLicenseCheckResults }); server.plugins.monitoring.info = { feature }; @@ -57,19 +48,13 @@ describe('Monitoring Verify License', () => { }); it('verifyMonitoringLicense returns true if enabled by configuration and by license', () => { - const get = sinon - .stub() - .withArgs('xpack.monitoring.cluster_alerts.enabled') - .returns(true); + const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true); const server = { config: sinon.stub().returns({ get }), plugins: { monitoring: { info: {} } }, }; const getLicenseCheckResults = sinon.stub().returns({ clusterAlerts: { enabled: true } }); - const feature = sinon - .stub() - .withArgs('monitoring') - .returns({ getLicenseCheckResults }); + const feature = sinon.stub().withArgs('monitoring').returns({ getLicenseCheckResults }); server.plugins.monitoring.info = { feature }; @@ -85,10 +70,7 @@ describe('Monitoring Verify License', () => { }); it('Monitoring feature info cannot be determined', () => { - const get = sinon - .stub() - .withArgs('xpack.monitoring.cluster_alerts.enabled') - .returns(true); + const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true); const server = { config: sinon.stub().returns({ get }), plugins: { monitoring: undefined }, // simulate race condition diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js index eff9875d794ad..c03b3338ba60d 100644 --- a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js +++ b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js @@ -61,7 +61,7 @@ export function appendStaticAlerts(cluster, alerts, size) { // we can put it over any resolved alert, or anything with a lower severity (which is currently none) // the alerts array is pre-sorted from highest severity to lowest; unresolved alerts are at the bottom const alertIndex = alerts.findIndex( - alert => alert.resolved_timestamp || alert.metadata.severity < staticAlert.metadata.severity + (alert) => alert.resolved_timestamp || alert.metadata.severity < staticAlert.metadata.severity ); if (alertIndex !== -1) { @@ -214,9 +214,9 @@ export function alertsClusterSearch(req, alertsIndex, cluster, checkLicense, opt } const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(result => { + return callWithRequest(req, 'search', params).then((result) => { const hits = get(result, 'hits.hits', []); - const alerts = hits.map(alert => alert._source); + const alerts = hits.map((alert) => alert._source); return appendStaticAlerts(cluster, alerts, size); }); diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js index 79a7cac61b396..55cd60a3622b4 100644 --- a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js +++ b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js @@ -65,7 +65,7 @@ export function alertsClustersAggregation(req, alertsIndex, clusters, checkLicen }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(result => { + return callWithRequest(req, 'search', params).then((result) => { const buckets = get(result.aggregations, 'group_by_cluster.buckets'); const meta = { alertsMeta: { enabled: true } }; diff --git a/x-pack/plugins/monitoring/server/config.ts b/x-pack/plugins/monitoring/server/config.ts index ad5bf95090186..a1eddee725e90 100644 --- a/x-pack/plugins/monitoring/server/config.ts +++ b/x-pack/plugins/monitoring/server/config.ts @@ -64,7 +64,7 @@ export const configSchema = schema.object({ alwaysPresentCertificate: schema.boolean({ defaultValue: false }), }, { - validate: rawConfig => { + validate: (rawConfig) => { if (rawConfig.key && rawConfig.keystore.path) { return 'cannot use [key] when [keystore.path] is specified'; } @@ -80,7 +80,7 @@ export const configSchema = schema.object({ schema.contextRef('dev'), false, schema.boolean({ - validate: rawValue => { + validate: (rawValue) => { if (rawValue === true) { return '"ignoreVersionMismatch" can only be set to true in development mode'; } @@ -115,7 +115,7 @@ export const configSchema = schema.object({ schema.contextRef('dist'), false, schema.string({ - validate: rawConfig => { + validate: (rawConfig) => { if (rawConfig === 'elastic') { return ( 'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' + @@ -160,7 +160,7 @@ export const configSchema = schema.object({ alwaysPresentCertificate: schema.boolean({ defaultValue: false }), }, { - validate: rawConfig => { + validate: (rawConfig) => { if (rawConfig.key && rawConfig.keystore.path) { return 'cannot use [key] when [keystore.path] is specified'; } @@ -176,7 +176,7 @@ export const configSchema = schema.object({ schema.contextRef('dev'), false, schema.boolean({ - validate: rawValue => { + validate: (rawValue) => { if (rawValue === true) { return '"ignoreVersionMismatch" can only be set to true in development mode'; } diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js b/x-pack/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js index 2fefdbd4f0943..3421f5d3830d6 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js @@ -23,10 +23,12 @@ class MockCollectorSet { return !!x.isUsageCollector; } areAllCollectorsReady() { - return this.mockCollectors.every(collector => collector.isReady()); + return this.mockCollectors.every((collector) => collector.isReady()); } getCollectorByType(type) { - return this.mockCollectors.find(collector => collector.type === type) || this.mockCollectors[0]; + return ( + this.mockCollectors.find((collector) => collector.type === type) || this.mockCollectors[0] + ); } getFilteredCollectorSet(filter) { return new MockCollectorSet(this.mockServer, this.mockCollectors.filter(filter)); @@ -48,7 +50,7 @@ describe('BulkUploader', () => { .stub() .withArgs('monitoring.bulk') .callsFake(() => { - return new Promise(resolve => setTimeout(resolve, CHECK_DELAY + 1)); + return new Promise((resolve) => setTimeout(resolve, CHECK_DELAY + 1)); }), }; @@ -65,13 +67,13 @@ describe('BulkUploader', () => { }; }); - it('should skip bulk upload if payload is empty', done => { + it('should skip bulk upload if payload is empty', (done) => { const collectors = new MockCollectorSet(server, [ { type: 'type_collector_test', fetch: noop, // empty payloads, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, }, ]); @@ -105,19 +107,19 @@ describe('BulkUploader', () => { }, CHECK_DELAY); }); - it('should not upload if some collectors are not ready', done => { + it('should not upload if some collectors are not ready', (done) => { const collectors = new MockCollectorSet(server, [ { type: 'type_collector_test', fetch: noop, // empty payloads, isReady: () => false, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, }, { type: 'type_collector_test2', fetch: noop, // empty payloads, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, }, ]); @@ -148,12 +150,12 @@ describe('BulkUploader', () => { }, CHECK_DELAY); }); - it('should run the bulk upload handler', done => { + it('should run the bulk upload handler', (done) => { const collectors = new MockCollectorSet(server, [ { fetch: () => ({ type: 'type_collector_test', result: { testData: 12345 } }), isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, }, ]); const uploader = new BulkUploader({ ...server, interval: FETCH_INTERVAL }); @@ -181,7 +183,7 @@ describe('BulkUploader', () => { }, CHECK_DELAY); }); - it('does not call UsageCollectors if last reported is within the usageInterval', done => { + it('does not call UsageCollectors if last reported is within the usageInterval', (done) => { const usageCollectorFetch = sinon.stub(); const collectorFetch = sinon .stub() @@ -191,13 +193,13 @@ describe('BulkUploader', () => { { fetch: usageCollectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: true, }, { fetch: collectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: false, }, ]); @@ -223,7 +225,7 @@ describe('BulkUploader', () => { { fetch: usageCollectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: true, }, ]); @@ -253,13 +255,13 @@ describe('BulkUploader', () => { { fetch: statsCollectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: false, }, { fetch: usageCollectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: true, }, ]); @@ -280,7 +282,7 @@ describe('BulkUploader', () => { expect(statsCollectorFetch.callCount).to.eql(3); }); - it('calls UsageCollectors if last reported exceeds during a _usageInterval', done => { + it('calls UsageCollectors if last reported exceeds during a _usageInterval', (done) => { const usageCollectorFetch = sinon.stub(); const collectorFetch = sinon .stub() @@ -290,13 +292,13 @@ describe('BulkUploader', () => { { fetch: usageCollectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: true, }, { fetch: collectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: false, }, ]); @@ -313,7 +315,7 @@ describe('BulkUploader', () => { }, CHECK_DELAY); }); - it('uses a direct connection to the monitoring cluster, when configured', done => { + it('uses a direct connection to the monitoring cluster, when configured', (done) => { const dateInIndex = '2020.02.10'; const oldNow = moment.now; moment.now = () => 1581310800000; @@ -322,12 +324,12 @@ describe('BulkUploader', () => { callWithInternalUser: sinon .stub() .withArgs('monitoring.bulk') - .callsFake(arg => { + .callsFake((arg) => { let resolution = null; if (arg === 'info') { resolution = { cluster_uuid: prodClusterUuid }; } - return new Promise(resolve => resolve(resolution)); + return new Promise((resolve) => resolve(resolution)); }), }; const monitoringCluster = { @@ -335,7 +337,7 @@ describe('BulkUploader', () => { .stub() .withArgs('bulk') .callsFake(() => { - return new Promise(resolve => setTimeout(resolve, CHECK_DELAY + 1)); + return new Promise((resolve) => setTimeout(resolve, CHECK_DELAY + 1)); }), }; @@ -348,7 +350,7 @@ describe('BulkUploader', () => { { fetch: collectorFetch, isReady: () => true, - formatForBulkUpload: result => result, + formatForBulkUpload: (result) => result, isUsageCollector: false, }, ]); @@ -356,7 +358,7 @@ describe('BulkUploader', () => { ...server, elasticsearchPlugin: { createCluster: () => monitoringCluster, - getCluster: name => { + getCluster: (name) => { if (name === 'admin' || name === 'data') { return prodCluster; } @@ -364,7 +366,7 @@ describe('BulkUploader', () => { }, }, config: { - get: key => { + get: (key) => { if (key === 'monitoring.elasticsearch') { return { hosts: ['http://localhost:9200'], diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js b/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js index 0722a80dc2c11..6035837bac85d 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js @@ -50,15 +50,15 @@ export class BulkUploader { this._usageInterval = TELEMETRY_COLLECTION_INTERVAL; this._log = log; - this._cluster = elasticsearch.createClient('admin', { + this._cluster = elasticsearch.legacy.createClient('admin', { plugins: [monitoringBulk], }); if (hasMonitoringCluster(config.elasticsearch)) { this._log.info(`Detected direct connection to monitoring cluster`); this._hasDirectConnectionToMonitoringCluster = true; - this._cluster = elasticsearch.createClient('monitoring-direct', config.elasticsearch); - elasticsearch.adminClient.callAsInternalUser('info').then(data => { + this._cluster = elasticsearch.legacy.createClient('monitoring-direct', config.elasticsearch); + elasticsearch.legacy.client.callAsInternalUser('info').then((data) => { this._productionClusterUuid = get(data, 'cluster_uuid'); }); } @@ -75,7 +75,7 @@ export class BulkUploader { const successfulUploadInLastDay = this._lastFetchUsageTime && this._lastFetchUsageTime + this._usageInterval > Date.now(); - return usageCollection.getFilteredCollectorSet(c => { + return usageCollection.getFilteredCollectorSet((c) => { // this is internal bulk upload, so filter out API-only collectors if (c.ignoreForInternalUploader) { return false; @@ -272,7 +272,7 @@ export class BulkUploader { } static checkPayloadTypesUnique(payload) { - const ids = payload.map(item => item[0].index._type); + const ids = payload.map((item) => item[0].index._type); const uniques = uniq(ids); if (ids.length !== uniques.length) { throw new Error('Duplicate collector type identifiers found in payload! ' + ids.join(',')); diff --git a/x-pack/plugins/monitoring/server/lib/__tests__/calculate_auto.js b/x-pack/plugins/monitoring/server/lib/__tests__/calculate_auto.js index ddc20a1221d31..518da3deb1edb 100644 --- a/x-pack/plugins/monitoring/server/lib/__tests__/calculate_auto.js +++ b/x-pack/plugins/monitoring/server/lib/__tests__/calculate_auto.js @@ -27,7 +27,7 @@ describe('Calculating Time Intervals Based on Size of Buckets', () => { [10000, 1], ]; - _.each(tuples, t => { + _.each(tuples, (t) => { it(`Bucket Size: ${t[0]} - Time Interval: ${t[1]}`, () => { const result = calculateAuto(t[0], duration); expect(result.milliseconds()).to.be.eql(t[1]); diff --git a/x-pack/plugins/monitoring/server/lib/__tests__/calculate_overall_status.js b/x-pack/plugins/monitoring/server/lib/__tests__/calculate_overall_status.js index 7a4fbd2a26e66..38d59f9af4f5a 100644 --- a/x-pack/plugins/monitoring/server/lib/__tests__/calculate_overall_status.js +++ b/x-pack/plugins/monitoring/server/lib/__tests__/calculate_overall_status.js @@ -24,13 +24,13 @@ describe('Calculate Kibana Cluster Helath', () => { ['red'], ]; - greens.forEach(set => { + greens.forEach((set) => { expect(calculateOverallStatus(set)).to.be('green'); }); - yellows.forEach(set => { + yellows.forEach((set) => { expect(calculateOverallStatus(set)).to.be('yellow'); }); - reds.forEach(set => { + reds.forEach((set) => { expect(calculateOverallStatus(set)).to.be('red'); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/__tests__/create_query.js b/x-pack/plugins/monitoring/server/lib/__tests__/create_query.js index 5e51136913778..7d5661ccd7560 100644 --- a/x-pack/plugins/monitoring/server/lib/__tests__/create_query.js +++ b/x-pack/plugins/monitoring/server/lib/__tests__/create_query.js @@ -69,7 +69,7 @@ describe('Create Query', () => { const options = {}; // missing metric object return createQuery(options); } - expect(callCreateQuery).to.throwException(e => { + expect(callCreateQuery).to.throwException((e) => { expect(e).to.be.a(MissingRequiredError); }); }); @@ -80,7 +80,7 @@ describe('Create Query', () => { delete options.metric.uuidField; return createQuery(options); } - expect(callCreateQuery).to.throwException(e => { + expect(callCreateQuery).to.throwException((e) => { expect(e).to.be.a(MissingRequiredError); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/__tests__/helpers.js b/x-pack/plugins/monitoring/server/lib/__tests__/helpers.js index 798bc4beeb0a9..b88ddbde2cc38 100644 --- a/x-pack/plugins/monitoring/server/lib/__tests__/helpers.js +++ b/x-pack/plugins/monitoring/server/lib/__tests__/helpers.js @@ -82,7 +82,7 @@ export const response = { }, }; -export const defaultResponseSort = handleResponse => { +export const defaultResponseSort = (handleResponse) => { const responseMulti = { hits: { hits: [] } }; const hit = response.hits.hits[0]; const version = ['6.6.2', '7.0.0-rc1', '6.7.1']; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts index ae66d603507ca..c4553d87980da 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { AlertInstance } from '../../../../alerting/server'; +import { AlertInstance } from '../../../../alerts/server'; import { AlertCommonCluster, AlertCommonPerClusterMessage, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts index 66ea30d5f2e96..3fcc3a2c98993 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts @@ -22,7 +22,7 @@ export async function fetchClusterState( filter: [ { terms: { - cluster_uuid: clusters.map(cluster => cluster.clusterUuid), + cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 5b05c907e796e..a65cba493dab9 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -22,7 +22,7 @@ export async function fetchLicenses( filter: [ { terms: { - cluster_uuid: clusters.map(cluster => cluster.clusterUuid), + cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index bf6ee965d3b2f..614658baf5c79 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -6,7 +6,7 @@ import moment from 'moment'; import { Logger } from '../../../../../../src/core/server'; import { AlertCommonPerClusterState } from '../../alerts/types'; -import { AlertsClient } from '../../../../alerting/server'; +import { AlertsClient } from '../../../../alerts/server'; export async function fetchStatus( alertsClient: AlertsClient, @@ -17,7 +17,7 @@ export async function fetchStatus( ): Promise { const statuses = await Promise.all( alertTypes.map( - type => + (type) => new Promise(async (resolve, reject) => { // We need to get the id from the alertTypeId const alerts = await alertsClient.find({ diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts index b562fde2a0810..7fdbc79685f7c 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts @@ -6,8 +6,8 @@ export function getCcsIndexPattern(indexPattern: string, remotes: string[]): string { return `${indexPattern},${indexPattern .split(',') - .map(pattern => { - return remotes.map(remoteName => `${remoteName}:${pattern}`).join(','); + .map((pattern) => { + return remotes.map((remoteName) => `${remoteName}:${pattern}`).join(','); }) .join(',')}`; } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts index 83a9e26e4c589..cfaaeb36535a0 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts @@ -6,7 +6,7 @@ import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'kibana/server'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { AlertServices } from '../../../../alerting/server'; +import { AlertServices } from '../../../../alerts/server'; import { AlertCommonCluster } from '../../alerts/types'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants'; import { fetchAvailableCcs } from './fetch_available_ccs'; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts index cfe9f02b9bd6a..97ef2790b516d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -5,7 +5,7 @@ */ import { Moment } from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { AlertInstance } from '../../../../alerting/server'; +import { AlertInstance } from '../../../../alerts/server'; import { AlertCommonPerClusterMessageLinkToken, AlertCommonPerClusterMessageTimeToken, diff --git a/x-pack/plugins/monitoring/server/lib/apm/_apm_stats.js b/x-pack/plugins/monitoring/server/lib/apm/_apm_stats.js index e52338c24cd33..c5840176f3c06 100644 --- a/x-pack/plugins/monitoring/server/lib/apm/_apm_stats.js +++ b/x-pack/plugins/monitoring/server/lib/apm/_apm_stats.js @@ -25,7 +25,7 @@ export const apmAggFilterPath = [ 'aggregations.max_mem_total_total.value', ]; -export const apmUuidsAgg = maxBucketSize => ({ +export const apmUuidsAgg = (maxBucketSize) => ({ total: { cardinality: { field: 'beats_stats.beat.uuid', @@ -92,7 +92,7 @@ export const apmUuidsAgg = maxBucketSize => ({ }, }); -export const apmAggResponseHandler = response => { +export const apmAggResponseHandler = (response) => { const apmTotal = get(response, 'aggregations.total.value', null); const eventsTotalMax = get(response, 'aggregations.max_events_total.value', null); diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js b/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js index a24936dc0f832..fffbfe55abb8d 100644 --- a/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js +++ b/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js @@ -38,7 +38,7 @@ export function getApmsForClusters(req, apmIndexPattern, clusters) { const maxBucketSize = config.get('monitoring.ui.max_bucket_size'); return Promise.all( - clusters.map(async cluster => { + clusters.map(async (cluster) => { const clusterUuid = cluster.cluster_uuid; const params = { index: apmIndexPattern, diff --git a/x-pack/plugins/monitoring/server/lib/beats/__tests__/create_beats_query.js b/x-pack/plugins/monitoring/server/lib/beats/__tests__/create_beats_query.js index 4b3c5f11cf396..b26720d0c1031 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/__tests__/create_beats_query.js +++ b/x-pack/plugins/monitoring/server/lib/beats/__tests__/create_beats_query.js @@ -35,7 +35,7 @@ describe('createBeatsQuery', () => { const filters1 = [fauxFilter1]; const filters2 = [fauxFilter2, fauxFilter1]; - [filters1, filters2].forEach(filters => { + [filters1, filters2].forEach((filters) => { const query = createBeatsQuery({ filters }); const queryFilters = query.bool.filter; const filterCount = queryFilters.length; diff --git a/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.js b/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.js index 9aa807fc785fc..cf5a99525cc4c 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.js +++ b/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.js @@ -26,7 +26,7 @@ export const beatsAggFilterPath = [ 'aggregations.max_bytes_sent_total.value', ]; -export const beatsUuidsAgg = maxBucketSize => ({ +export const beatsUuidsAgg = (maxBucketSize) => ({ types: { terms: { field: 'beats_stats.beat.type', @@ -97,7 +97,7 @@ export const beatsUuidsAgg = maxBucketSize => ({ }, }); -export const beatsAggResponseHandler = response => { +export const beatsAggResponseHandler = (response) => { // beat types stat const buckets = get(response, 'aggregations.types.buckets', []); const beatTotal = get(response, 'aggregations.total.value', null); diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js b/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js index 624abb894e508..b33140cd74ba0 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js +++ b/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js @@ -37,7 +37,7 @@ export function getBeatsForClusters(req, beatsIndexPattern, clusters) { const maxBucketSize = config.get('monitoring.ui.max_bucket_size'); return Promise.all( - clusters.map(async cluster => { + clusters.map(async (cluster) => { const clusterUuid = cluster.cluster_uuid; const params = { index: beatsIndexPattern, diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js b/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js index 1139489728dbf..f630903d4e29d 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js +++ b/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js @@ -11,9 +11,9 @@ import { createBeatsQuery } from './create_beats_query'; export function handleResponse(response) { const aggs = get(response, 'aggregations'); - const getTimeRangeCount = name => { + const getTimeRangeCount = (name) => { const lastActiveBuckets = get(aggs, 'active_counts.buckets', []); - const rangeBucket = lastActiveBuckets.find(bucket => bucket.key === name); + const rangeBucket = lastActiveBuckets.find((bucket) => bucket.key === name); return get(rangeBucket, 'uuids.buckets.length'); }; diff --git a/x-pack/plugins/monitoring/server/lib/calculate_auto.js b/x-pack/plugins/monitoring/server/lib/calculate_auto.js index 44bebe619e77c..142dd9c7d9df5 100644 --- a/x-pack/plugins/monitoring/server/lib/calculate_auto.js +++ b/x-pack/plugins/monitoring/server/lib/calculate_auto.js @@ -50,7 +50,7 @@ function find(rules, check) { return moment.duration(ms, 'ms'); } - return function(buckets, duration) { + return function (buckets, duration) { const interval = pick(buckets, duration); if (interval) { return moment.duration(interval._data); diff --git a/x-pack/plugins/monitoring/server/lib/ccs_utils.js b/x-pack/plugins/monitoring/server/lib/ccs_utils.js index f600fafd892c5..dab1e87435c86 100644 --- a/x-pack/plugins/monitoring/server/lib/ccs_utils.js +++ b/x-pack/plugins/monitoring/server/lib/ccs_utils.js @@ -31,7 +31,7 @@ export function prefixIndexPattern(config, indexPattern, ccs) { } const patterns = indexPattern.split(','); - const prefixedPattern = patterns.map(pattern => `${ccs}:${pattern}`).join(','); + const prefixedPattern = patterns.map((pattern) => `${ccs}:${pattern}`).join(','); // if a wildcard is used, then we also want to search the local indices if (ccs === '*') { diff --git a/x-pack/plugins/monitoring/server/lib/cluster/__test__/get_clusters_summary.test.js b/x-pack/plugins/monitoring/server/lib/cluster/__test__/get_clusters_summary.test.js index 470c8bc830c33..a835344082b01 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/__test__/get_clusters_summary.test.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/__test__/get_clusters_summary.test.js @@ -26,7 +26,7 @@ describe('getClustersSummary', () => { }); it('should log and throw an exception if a cluster does not have a license', () => { - const fakeClusters = clusters.map(cluster => ({ + const fakeClusters = clusters.map((cluster) => ({ ...cluster, license: undefined, })); diff --git a/x-pack/plugins/monitoring/server/lib/cluster/__tests__/flag_supported_clusters.js b/x-pack/plugins/monitoring/server/lib/cluster/__tests__/flag_supported_clusters.js index c5f24c7fc0245..577bf4606c7aa 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/__tests__/flag_supported_clusters.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/__tests__/flag_supported_clusters.js @@ -13,10 +13,7 @@ const mockReq = (log, queryResult = {}) => { server: { config() { return { - get: sinon - .stub() - .withArgs('server.uuid') - .returns('kibana-1234'), + get: sinon.stub().withArgs('server.uuid').returns('kibana-1234'), }; }, plugins: { @@ -57,7 +54,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(resultClusters => { + )(clusters).then((resultClusters) => { expect(resultClusters).to.eql([ { ...goldLicense(), isSupported: true }, {}, // no license @@ -89,7 +86,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(resultClusters => { + )(clusters).then((resultClusters) => { expect(resultClusters).to.eql([ { cluster_uuid: 'supported_cluster_uuid', @@ -128,7 +125,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(resultClusters => { + )(clusters).then((resultClusters) => { expect(resultClusters).to.eql([ { cluster_uuid: 'supported_cluster_uuid_1', @@ -171,7 +168,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(resultClusters => { + )(clusters).then((resultClusters) => { expect(resultClusters).to.eql([ { cluster_uuid: 'supported_cluster_uuid', @@ -208,7 +205,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(resultClusters => { + )(clusters).then((resultClusters) => { expect(resultClusters).to.eql([ { cluster_uuid: 'supported_cluster_uuid', @@ -245,7 +242,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(resultClusters => { + )(clusters).then((resultClusters) => { expect(resultClusters).to.eql([ { cluster_uuid: 'supported_cluster_uuid_1', @@ -283,7 +280,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(result => { + )(clusters).then((result) => { expect(result).to.eql([{ isSupported: true, ...basicLicense() }]); sinon.assert.calledWith( logStub, @@ -300,7 +297,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(result => { + )(clusters).then((result) => { expect(result).to.eql([{ isSupported: true, ...goldLicense() }]); sinon.assert.calledWith( logStub, @@ -318,7 +315,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(result => { + )(clusters).then((result) => { expect(result).to.eql([{ ...deletedLicense() }]); sinon.assert.calledWith( logStub, @@ -336,7 +333,7 @@ describe('Flag Supported Clusters', () => { return flagSupportedClusters( req, kbnIndices - )(clusters).then(result => { + )(clusters).then((result) => { expect(result).to.eql([{ ...standaloneCluster(), isSupported: true }]); sinon.assert.calledWith( logStub, diff --git a/x-pack/plugins/monitoring/server/lib/cluster/__tests__/get_clusters_state.js b/x-pack/plugins/monitoring/server/lib/cluster/__tests__/get_clusters_state.js index 38051fcbb1f3a..d1bc3a0a7e381 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/__tests__/get_clusters_state.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/__tests__/get_clusters_state.js @@ -55,13 +55,7 @@ describe('get_clusters_state', () => { }); it('does not filter out an unavailable cluster', () => { - set( - response, - '.hits.hits[0]._source.timestamp', - moment() - .subtract(30, 'days') - .format() - ); + set(response, '.hits.hits[0]._source.timestamp', moment().subtract(30, 'days').format()); const result = handleResponse(response, clusters); expect(result).to.be(clusters); expect(result.length).to.be(1); diff --git a/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.js b/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.js index 1739f5dc33038..03de24916a6db 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.js @@ -72,9 +72,9 @@ export function flagSupportedClusters(req, kbnIndexPattern) { checkParam(kbnIndexPattern, 'kbnIndexPattern in cluster/flagSupportedClusters'); const config = req.server.config(); - const serverLog = msg => req.getLogger('supported-clusters').debug(msg); - const flagAllSupported = clusters => { - clusters.forEach(cluster => { + const serverLog = (msg) => req.getLogger('supported-clusters').debug(msg); + const flagAllSupported = (clusters) => { + clusters.forEach((cluster) => { if (cluster.license) { cluster.isSupported = true; } @@ -82,7 +82,7 @@ export function flagSupportedClusters(req, kbnIndexPattern) { return clusters; }; - return async function(clusters) { + return async function (clusters) { // Standalone clusters are automatically supported in the UI so ignore those for // our calculations here let linkedClusterCount = 0; @@ -123,7 +123,7 @@ export function flagSupportedClusters(req, kbnIndexPattern) { serverLog( 'Found some basic license clusters in monitoring data. Only non-basic will be supported.' ); - clusters.forEach(cluster => { + clusters.forEach((cluster) => { if (cluster.license && cluster.license.type !== 'basic') { cluster.isSupported = true; } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.js b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.js index cdb0a9f2e7a62..a167837969bd0 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.js @@ -28,7 +28,7 @@ export function getClusterLicense(req, esIndexPattern, clusterUuid) { }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(response => { + return callWithRequest(req, 'search', params).then((response) => { return get(response, 'hits.hits[0]._source.license', {}); }); } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js index 79f1ea0a60883..fa0dff9c09431 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js @@ -27,7 +27,7 @@ export function getClusterStats(req, esIndexPattern, clusterUuid) { } // passing clusterUuid so `get_clusters` will filter for single cluster - return getClustersStats(req, esIndexPattern, clusterUuid).then(clusters => { + return getClustersStats(req, esIndexPattern, clusterUuid).then((clusters) => { if (!clusters || clusters.length === 0) { throw notFound( i18n.translate('xpack.monitoring.clusterStats.uuidNotFoundErrorMessage', { diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index 1bddede52207b..5ed8d6b01aba5 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -147,7 +147,7 @@ export async function getClustersFromRequest( clusters, checkLicenseForAlerts ); - clusters.forEach(cluster => { + clusters.forEach((cluster) => { cluster.alerts = { alertsMeta: { enabled: clustersAlerts.alertsMeta.enabled, @@ -165,7 +165,7 @@ export async function getClustersFromRequest( ? await getKibanasForClusters(req, kbnIndexPattern, clusters) : []; // add the kibana data to each cluster - kibanas.forEach(kibana => { + kibanas.forEach((kibana) => { const clusterIndex = findIndex(clusters, { cluster_uuid: kibana.clusterUuid }); set(clusters[clusterIndex], 'kibana', kibana.stats); }); @@ -174,7 +174,7 @@ export async function getClustersFromRequest( if (isInCodePath(codePaths, [CODE_PATH_LOGSTASH])) { const logstashes = await getLogstashForClusters(req, lsIndexPattern, clusters); const pipelines = await getLogstashPipelineIds(req, lsIndexPattern, { clusterUuid }, 1); - logstashes.forEach(logstash => { + logstashes.forEach((logstash) => { const clusterIndex = findIndex(clusters, { cluster_uuid: logstash.clusterUuid }); // withhold LS overview stats until there is at least 1 pipeline @@ -189,7 +189,7 @@ export async function getClustersFromRequest( const beatsByCluster = isInCodePath(codePaths, [CODE_PATH_BEATS]) ? await getBeatsForClusters(req, beatsIndexPattern, clusters) : []; - beatsByCluster.forEach(beats => { + beatsByCluster.forEach((beats) => { const clusterIndex = findIndex(clusters, { cluster_uuid: beats.clusterUuid }); set(clusters[clusterIndex], 'beats', beats.stats); }); @@ -198,7 +198,7 @@ export async function getClustersFromRequest( const apmsByCluster = isInCodePath(codePaths, [CODE_PATH_APM]) ? await getApmsForClusters(req, apmIndexPattern, clusters) : []; - apmsByCluster.forEach(apm => { + apmsByCluster.forEach((apm) => { const clusterIndex = findIndex(clusters, { cluster_uuid: apm.clusterUuid }); set(clusters[clusterIndex], 'apm', apm.stats); }); diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.js index 103d79e9f3652..33e4ec96676b2 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.js @@ -18,7 +18,7 @@ import { checkParam } from '../error_missing_required'; export function handleResponse(response, clusters) { const hits = get(response, 'hits.hits', []); - hits.forEach(hit => { + hits.forEach((hit) => { const currentCluster = get(hit, '_source', {}); if (currentCluster) { @@ -43,8 +43,8 @@ export function getClustersState(req, esIndexPattern, clusters) { checkParam(esIndexPattern, 'esIndexPattern in cluster/getClustersHealth'); const clusterUuids = clusters - .filter(cluster => !cluster.cluster_state) - .map(cluster => cluster.cluster_uuid); + .filter((cluster) => !cluster.cluster_state) + .map((cluster) => cluster.cluster_uuid); // we only need to fetch the cluster state if we don't already have it // newer documents (those from the version 6 schema and later already have the cluster state with cluster stats) @@ -76,7 +76,7 @@ export function getClustersState(req, esIndexPattern, clusters) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(response => + return callWithRequest(req, 'search', params).then((response) => handleResponse(response, clusters) ); } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.js index 54dc58a374c2c..945bf1f2e19a2 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.js @@ -22,9 +22,9 @@ import { getClustersState } from './get_clusters_state'; export function getClustersStats(req, esIndexPattern, clusterUuid) { return ( fetchClusterStats(req, esIndexPattern, clusterUuid) - .then(response => handleClusterStats(response, req.server)) + .then((response) => handleClusterStats(response, req.server)) // augment older documents (e.g., from 2.x - 5.4) with their cluster_state - .then(clusters => getClustersState(req, esIndexPattern, clusters)) + .then((clusters) => getClustersState(req, esIndexPattern, clusters)) ); } @@ -85,7 +85,7 @@ export function handleClusterStats(response) { const hits = get(response, 'hits.hits', []); return hits - .map(hit => { + .map((hit) => { const cluster = get(hit, '_source'); if (cluster) { diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_summary.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_summary.js index 64c225b937443..ec6e8d5aca926 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_summary.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_summary.js @@ -10,7 +10,7 @@ import { LOGGING_TAG } from '../../../common/constants'; import { MonitoringLicenseError } from '../errors/custom_errors'; export function getClustersSummary(server, clusters, kibanaUuid, isCcrEnabled) { - return clusters.map(cluster => { + return clusters.map((cluster) => { const { isSupported, cluster_uuid: clusterUuid, diff --git a/x-pack/plugins/monitoring/server/lib/details/get_metrics.js b/x-pack/plugins/monitoring/server/lib/details/get_metrics.js index 0c4736e91ea10..25737f21cfc91 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_metrics.js +++ b/x-pack/plugins/monitoring/server/lib/details/get_metrics.js @@ -37,7 +37,7 @@ export async function getMetrics( min = max - numOfBuckets * bucketSize * 1000; } - return Bluebird.map(metricSet, metric => { + return Bluebird.map(metricSet, (metric) => { // metric names match the literal metric name, but they can be supplied in groups or individually let metricNames; @@ -47,7 +47,7 @@ export async function getMetrics( metricNames = [metric]; } - return Bluebird.map(metricNames, metricName => { + return Bluebird.map(metricNames, (metricName) => { return getSeries(req, indexPattern, metricName, metricOptions, filters, groupBy, { min, max, @@ -55,7 +55,7 @@ export async function getMetrics( timezone, }); }); - }).then(rows => { + }).then((rows) => { const data = {}; metricSet.forEach((key, index) => { // keyName must match the value stored in the html template diff --git a/x-pack/plugins/monitoring/server/lib/details/get_series.js b/x-pack/plugins/monitoring/server/lib/details/get_series.js index a9269daa8f74e..abe2499ff0e5e 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_series.js +++ b/x-pack/plugins/monitoring/server/lib/details/get_series.js @@ -196,7 +196,7 @@ function findLastUsableBucketIndex(buckets, max, firstUsableBucketIndex, bucketS return -1; } -const formatBucketSize = bucketSizeInSeconds => { +const formatBucketSize = (bucketSizeInSeconds) => { const now = moment(); const timestamp = moment(now).add(bucketSizeInSeconds, 'seconds'); // clone the `now` object @@ -260,7 +260,7 @@ function handleSeries(metric, groupBy, min, max, bucketSizeInSeconds, timezone, data = buckets .slice(firstUsableBucketIndex, lastUsableBucketIndex + 1) // take only the buckets we know are usable - .map(bucket => [ + .map((bucket) => [ formatUTCTimestampForTimezone(bucket.key, timezone), calculation(bucket, key, metric, bucketSizeInSeconds), ]); // map buckets to X/Y coords for Flot charting @@ -278,7 +278,7 @@ function handleSeries(metric, groupBy, min, max, bucketSizeInSeconds, timezone, } if (groupBy) { - return get(response, 'aggregations.groupBy.buckets', []).map(bucket => { + return get(response, 'aggregations.groupBy.buckets', []).map((bucket) => { return { groupedBy: bucket.key, ...getAggregatedData(get(bucket, 'check.buckets', [])), diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js index 41ba904377d29..876938198c6f2 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js @@ -53,7 +53,7 @@ export function uncovertMetricNames(byDateBucketResponse) { const unconverted = {}; for (const metricName of LISTING_METRICS_NAMES) { unconverted[metricName] = { - buckets: byDateBucketResponse.buckets.map(bucket => { + buckets: byDateBucketResponse.buckets.map((bucket) => { const { // eslint-disable-next-line camelcase key_as_string, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.js index d58144ac57290..db8c89c364463 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.js @@ -21,7 +21,7 @@ import { ElasticsearchMetric } from '../metrics'; * @returns {boolean} true to keep */ export function filterOldShardActivity(startMs) { - return activity => { + return (activity) => { // either it's still going and there is no stop time, or the stop time happened after we started looking for one return !_.isNumber(activity.stop_time_in_millis) || activity.stop_time_in_millis >= startMs; }; @@ -67,7 +67,7 @@ export function getLastRecovery(req, esIndexPattern) { }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(resp => { + return callWithRequest(req, 'search', params).then((resp) => { return handleLastRecoveries(resp, start); }); } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.js index 8aef402f881e8..c6575393590f0 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.js @@ -16,7 +16,7 @@ import { ML_SUPPORTED_LICENSES } from '../../../common/constants'; */ export function handleResponse(response) { const hits = get(response, 'hits.hits', []); - return hits.map(hit => get(hit, '_source.job_stats')); + return hits.map((hit) => get(hit, '_source.job_stats')); } export function getMlJobs(req, esIndexPattern) { @@ -80,7 +80,7 @@ export function getMlJobsForCluster(req, esIndexPattern, cluster) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(response => { + return callWithRequest(req, 'search', params).then((response) => { return get(response, 'aggregations.jobs_count.value', 0); }); } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.js index 010a44e8a0b08..524eaca191eec 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.js @@ -11,7 +11,7 @@ import { ElasticsearchMetric } from '../../metrics'; import { i18n } from '@kbn/i18n'; export function handleResponse(shardStats, indexUuid) { - return response => { + return (response) => { const indexStats = get(response, 'hits.hits[0]._source.index_stats'); const primaries = get(indexStats, 'primaries'); const total = get(indexStats, 'total'); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.js index 938a9b9d55e43..c087d20a97db1 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.js @@ -15,7 +15,7 @@ import { i18n } from '@kbn/i18n'; export function handleResponse(resp, min, max, shardStats) { // map the hits const hits = get(resp, 'hits.hits', []); - return hits.map(hit => { + return hits.map((hit) => { const stats = get(hit, '_source.index_stats'); const earliestStats = get(hit, 'inner_hits.earliest.hits.hits[0]._source.index_stats'); @@ -137,7 +137,7 @@ export function getIndices(req, esIndexPattern, showSystemIndices = false, shard }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(resp => + return callWithRequest(req, 'search', params).then((resp) => handleResponse(resp, min, max, shardStats) ); } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/lookups.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/lookups.js index 7c24e27dc5e80..8aeb5871b6781 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/lookups.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/lookups.js @@ -20,10 +20,10 @@ describe('Node Types Lookups', () => { }); it('Has usable values', () => { - _.each(nodeTypeClass, value => { + _.each(nodeTypeClass, (value) => { expect(value).to.be.a('string'); }); - _.each(nodeTypeLabel, value => { + _.each(nodeTypeLabel, (value) => { expect(value).to.be.a('string'); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js index 8036d0bce372f..6abb392e58818 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js @@ -14,16 +14,16 @@ import { getNodeTypeClassLabel } from './get_node_type_class_label'; import { i18n } from '@kbn/i18n'; export function handleResponse(clusterState, shardStats, nodeUuid) { - return response => { + return (response) => { let nodeSummary = {}; const nodeStatsHits = get(response, 'hits.hits', []); - const nodes = nodeStatsHits.map(hit => hit._source.source_node); // using [0] value because query results are sorted desc per timestamp + const nodes = nodeStatsHits.map((hit) => hit._source.source_node); // using [0] value because query results are sorted desc per timestamp const node = nodes[0] || getDefaultNodeFromId(nodeUuid); const sourceStats = get(response, 'hits.hits[0]._source.node_stats'); const clusterNode = get(clusterState, ['nodes', nodeUuid]); const stats = { resolver: nodeUuid, - node_ids: nodes.map(node => node.uuid), + node_ids: nodes.map((node) => node.uuid), attributes: node.attributes, transport_address: node.transport_address, name: node.name, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_live_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_live_nodes.js index 415ff18c9f341..0936b2781406a 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_live_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_live_nodes.js @@ -12,7 +12,7 @@ export async function getLivesNodes(req) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data'); const { nodes } = await callWithRequest(req, 'transport.request', params); - return Object.keys(nodes).map(nodeId => ({ + return Object.keys(nodes).map((nodeId) => ({ ...nodes[nodeId], id: nodeId, resolver: nodeId, // Maintain parity for UI diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js index 42a3199449324..1cc42217c64f7 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js @@ -20,7 +20,7 @@ import { convertMetricNames } from '../../convert_metric_names'; export function getMetricAggs(listingMetrics) { let aggItems = {}; - listingMetrics.forEach(metricName => { + listingMetrics.forEach((metricName) => { const metric = metrics[metricName]; let metricAgg = null; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js index 4ce2591109740..573f1792e5f8a 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js @@ -52,5 +52,5 @@ export async function getNodeIds(req, indexPattern, { clusterUuid }, size) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); const response = await callWithRequest(req, 'search', params); - return get(response, 'aggregations.composite_data.buckets', []).map(bucket => bucket.key); + return get(response, 'aggregations.composite_data.buckets', []).map((bucket) => bucket.key); } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js index d431ec47adbcb..682da324ee72f 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js @@ -48,7 +48,7 @@ export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, n calculateAuto(100, duration).asSeconds() ); - const uuidsToInclude = pageOfNodes.map(node => node.uuid); + const uuidsToInclude = pageOfNodes.map((node) => node.uuid); const filters = [ { terms: { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js index 66393ef69df1d..a797359ed6ae1 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js @@ -56,13 +56,13 @@ export async function getPaginatedNodes( const filters = [ { terms: { - 'source_node.name': nodes.map(node => node.name), + 'source_node.name': nodes.map((node) => node.name), }, }, ]; const groupBy = { field: `source_node.uuid`, - include: nodes.map(node => node.uuid), + include: nodes.map((node) => node.uuid), size: config.get('monitoring.ui.max_bucket_size'), }; const metricSeriesData = await getMetrics( @@ -81,7 +81,7 @@ export async function getPaginatedNodes( const metricList = metricSeriesData[metricName]; for (const metricItem of metricList[0]) { - const node = nodes.find(node => node.uuid === metricItem.groupedBy); + const node = nodes.find((node) => node.uuid === metricItem.groupedBy); if (!node) { continue; } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js index 651fd20d77554..62cf138c99506 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js @@ -46,7 +46,7 @@ export function handleResponse( const nodesMetrics = mapNodesMetrics(metricsForNodes, nodesInfo, timeOptions); // summarize the metrics of online nodes // nodesInfo is the source of truth for the nodeIds, where nodesMetrics will lack metrics for offline nodes - const nodes = pageOfNodes.map(node => ({ + const nodes = pageOfNodes.map((node) => ({ ...nodesInfo[node.uuid], ...nodesMetrics[node.uuid], resolver: node.uuid, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.js index 877ba9f074d79..4e5e439ff90d5 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.js @@ -70,8 +70,8 @@ function reduceMetric(metricName, metricBuckets, { min: startTime, max: endTime, const metric = metrics[metricName]; const mappedData = metricBuckets .filter(partialBucketFilter) // buckets with whole start/end time range - .map(bucket => mapBuckets(bucket, metric)) - .filter(result => Boolean(result.y) || result.y === 0); // take only non-null values + .map((bucket) => mapBuckets(bucket, metric)) + .filter((result) => Boolean(result.y) || result.y === 0); // take only non-null values /* it's possible that no data exists for the type of metric. For example, * node_cgroup_throttled data could be completely null if there is no cgroup @@ -94,7 +94,7 @@ function reduceMetric(metricName, metricBuckets, { min: startTime, max: endTime, function reduceAllMetrics(metricSet, timeOptions) { const metrics = {}; - Object.keys(metricSet).forEach(metricName => { + Object.keys(metricSet).forEach((metricName) => { const metricBuckets = get(metricSet, [metricName, 'buckets']); metrics[metricName] = reduceMetric(metricName, metricBuckets, timeOptions); // append summarized metric data }); @@ -113,7 +113,7 @@ function reduceAllMetrics(metricSet, timeOptions) { */ export function mapNodesMetrics(metricsForNodes, nodesInfo, timeOptions) { const metricRows = {}; - Object.keys(metricsForNodes).forEach(nodeId => { + Object.keys(metricsForNodes).forEach((nodeId) => { if (nodesInfo[nodeId].isOnline) { // only do the work of mapping metrics if the node is online const metricSet = metricsForNodes[nodeId]; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.js index d0f845a8b0ed4..8782875775530 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.js @@ -10,5 +10,5 @@ export function sortNodes(nodes, sort) { return nodes; } - return sortByOrder(nodes, node => node[sort.field], sort.direction); + return sortByOrder(nodes, (node) => node[sort.field], sort.direction); } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/__tests__/get_shard_stats.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/__tests__/get_shard_stats.js index 38bb0c7e9a1e9..d999e1ae6bc50 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/__tests__/get_shard_stats.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/__tests__/get_shard_stats.js @@ -50,7 +50,7 @@ describe('getShardStats handler', () => { }); it('for yellow cluster status - has unassigned replica', () => { - const climateBucket = resp.aggregations.indices.buckets.find(b => b.key === 'climate'); + const climateBucket = resp.aggregations.indices.buckets.find((b) => b.key === 'climate'); climateBucket.states.buckets = [ { key: 'STARTED', @@ -99,7 +99,7 @@ describe('getShardStats handler', () => { }); it('for red cluster status - has unassigned primary', () => { - const climateBucket = resp.aggregations.indices.buckets.find(b => b.key === 'climate'); + const climateBucket = resp.aggregations.indices.buckets.find((b) => b.key === 'climate'); climateBucket.states.buckets = [ { key: 'STARTED', diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/calculate_shard_stat_indices_totals.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/calculate_shard_stat_indices_totals.js index 24be3c66d9ad7..0f5afebe89c01 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/calculate_shard_stat_indices_totals.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/calculate_shard_stat_indices_totals.js @@ -9,7 +9,7 @@ */ export function calculateIndicesTotals(indices) { // create datasets for each index - const metrics = Object.keys(indices).map(i => { + const metrics = Object.keys(indices).map((i) => { const index = indices[i]; return { primary: index.primary, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.js index c77bcc4f62e61..e8728e9c53ec5 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.js @@ -67,10 +67,10 @@ export async function getIndicesUnassignedShardStats(req, esIndexPattern, cluste const index = bucket.key; const states = get(bucket, 'state.primary.buckets', []); const unassignedReplica = states - .filter(state => state.key_as_string === 'false') + .filter((state) => state.key_as_string === 'false') .reduce((total, state) => total + state.doc_count, 0); const unassignedPrimary = states - .filter(state => state.key_as_string === 'true') + .filter((state) => state.key_as_string === 'true') .reduce((total, state) => total + state.doc_count, 0); let status = 'green'; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.test.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.test.js index a899b48cdd434..366ffee70a993 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.test.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.test.js @@ -25,7 +25,7 @@ describe('getIndicesUnassignedShardStats', () => { callWithRequest: () => ({ aggregations: { indices: { - buckets: Object.keys(indices).map(id => ({ + buckets: Object.keys(indices).map((id) => ({ key: id, state: { primary: { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.test.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.test.js index 023f12db1bf46..1c5100d69887a 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.test.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.test.js @@ -25,7 +25,7 @@ describe('getNodeShardCount', () => { callWithRequest: () => ({ aggregations: { nodes: { - buckets: Object.keys(nodes).map(id => ({ + buckets: Object.keys(nodes).map((id) => ({ key: id, doc_count: nodes[id].shardCount, })), diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js index b7dd99e7079dc..f677b1bab1b30 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js @@ -36,7 +36,7 @@ describe('get_shard_allocation', () => { describe('handleResponse', () => { it('deduplicates shards', () => { const nextTimestamp = '2018-07-06T00:00:01.259Z'; - const hits = shards.map(shard => { + const hits = shards.map((shard) => { return { _source: { ...exampleShardSource, @@ -47,7 +47,7 @@ describe('get_shard_allocation', () => { // duplicate all of them; this is how a response would really come back, with only the timestamp changed hits.concat( - hits.map(hit => { + hits.map((hit) => { return { ...hit, timestamp: nextTimestamp, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js index 132e9d6b01dbe..1154655ab6a22 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js @@ -71,7 +71,7 @@ export function getShardStats( }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(resp => { + return callWithRequest(req, 'search', params).then((resp) => { return handleResponse(resp, includeNodes, includeIndices, cluster); }); } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.js index 6421bca79546f..5708c1f5ad88a 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.js @@ -16,7 +16,7 @@ import { calculateNodeType } from '../nodes'; export function normalizeNodeShards(masterNode) { return (nodes, node) => { if (node.key && node.node_ids) { - const nodeIds = node.node_ids.buckets.map(b => b.key); + const nodeIds = node.node_ids.buckets.map((b) => b.key); const _node = { ...node, node_ids: nodeIds, @@ -37,19 +37,19 @@ export function normalizeNodeShards(masterNode) { }; } -const countShards = shardBuckets => { +const countShards = (shardBuckets) => { let primaryShards = 0; let replicaShards = 0; - shardBuckets.forEach(shard => { + shardBuckets.forEach((shard) => { const primaryMap = get(shard, 'primary.buckets', []); - const primaryBucket = primaryMap.find(b => b.key_as_string === 'true'); + const primaryBucket = primaryMap.find((b) => b.key_as_string === 'true'); if (primaryBucket !== undefined) { primaryShards += primaryBucket.doc_count; } - const replicaBucket = primaryMap.find(b => b.key_as_string === 'false'); + const replicaBucket = primaryMap.find((b) => b.key_as_string === 'false'); if (replicaBucket !== undefined) { replicaShards += replicaBucket.doc_count; } @@ -68,7 +68,7 @@ const countShards = shardBuckets => { */ export function normalizeIndexShards(indices, index) { const stateBuckets = get(index, 'states.buckets', []); - const [assignedShardBuckets, unassignedShardBuckets] = partition(stateBuckets, b => { + const [assignedShardBuckets, unassignedShardBuckets] = partition(stateBuckets, (b) => { return b.key === 'STARTED' || b.key === 'RELOCATING'; }); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/cluster.js b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/cluster.js index 5f11b5d8b2a79..426ae70bad93d 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/cluster.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/cluster.js @@ -18,7 +18,7 @@ describe('Elasticsearch Cluster Settings', () => { return result; }; - const getReq = response => { + const getReq = (response) => { return { server: { newPlatform: { @@ -53,7 +53,7 @@ describe('Elasticsearch Cluster Settings', () => { const setting = { xpack: { monitoring: { collection: { interval: -1 } } }, }; - const makeExpected = source => ({ + const makeExpected = (source) => ({ found: true, reason: { context: `cluster ${source}`, @@ -83,7 +83,7 @@ describe('Elasticsearch Cluster Settings', () => { const setting = { xpack: { monitoring: { exporters: { myCoolExporter: {} } } }, }; - const makeExpected = source => ({ + const makeExpected = (source) => ({ found: true, reason: { context: `cluster ${source}`, @@ -113,7 +113,7 @@ describe('Elasticsearch Cluster Settings', () => { const setting = { xpack: { monitoring: { enabled: 'false' } }, }; - const makeExpected = source => ({ + const makeExpected = (source) => ({ found: true, reason: { context: `cluster ${source}`, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/nodes.js index 3f3db5f817725..4604698ece9f4 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/__tests__/nodes.js @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { checkNodesSettings } from '../'; describe('Elasticsearch Nodes Settings', () => { - const getReq = response => { + const getReq = (response) => { return { server: { plugins: { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/find_reason.js b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/find_reason.js index 7c577c9ac8e18..70dfc0c6cf06f 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/find_reason.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/find_reason.js @@ -10,7 +10,7 @@ import { get } from 'lodash'; * Return true if the settings property is enabled or is using its default state of enabled * Note: this assumes that a 0 corresponds to disabled */ -const isEnabledOrDefault = property => { +const isEnabledOrDefault = (property) => { return property === undefined || (Boolean(property) && property !== 'false'); }; @@ -67,7 +67,7 @@ export function findReason(settingsSource, context, isCloudEnabled) { /* * find if all exporters are disabled or if all enabled exporters are remote */ - const allEnabled = exporterKeys.filter(key => { + const allEnabled = exporterKeys.filter((key) => { return isEnabledOrDefault(exportersFromPacked[key].enabled); }); @@ -81,12 +81,12 @@ export function findReason(settingsSource, context, isCloudEnabled) { }; } - const allEnabledLocal = exporterKeys.filter(key => { + const allEnabledLocal = exporterKeys.filter((key) => { const exporter = exportersFromPacked[key]; return exporter.type === 'local' && isEnabledOrDefault(exporter.enabled); }); - const allEnabledRemote = exporterKeys.filter(key => { + const allEnabledRemote = exporterKeys.filter((key) => { const exporter = exportersFromPacked[key]; return exporter.type !== 'local' && isEnabledOrDefault(exporter.enabled); }); diff --git a/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js b/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js index c122ce581a71c..b0c4d35a5b095 100644 --- a/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js +++ b/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js @@ -7,7 +7,7 @@ import { forbidden } from 'boom'; import { i18n } from '@kbn/i18n'; -const getStatusCode = err => { +const getStatusCode = (err) => { return err.isBoom ? err.output.statusCode : err.statusCode; }; diff --git a/x-pack/plugins/monitoring/server/lib/filter_partial_buckets.js b/x-pack/plugins/monitoring/server/lib/filter_partial_buckets.js index f25629cb9ebbd..99f0d5faa00e3 100644 --- a/x-pack/plugins/monitoring/server/lib/filter_partial_buckets.js +++ b/x-pack/plugins/monitoring/server/lib/filter_partial_buckets.js @@ -18,7 +18,7 @@ function getDelta(t1, t2) { } export function filterPartialBuckets(min, max, bucketSize, options = {}) { - return bucket => { + return (bucket) => { const bucketTime = getTime(bucket); // timestamp is too late to be complete if (getDelta(max, bucketTime.add(bucketSize, 'seconds')) < 0) { diff --git a/x-pack/plugins/monitoring/server/lib/kibana/__tests__/get_kibana_info.js b/x-pack/plugins/monitoring/server/lib/kibana/__tests__/get_kibana_info.js index 0aef763330761..d06d9b202f80c 100644 --- a/x-pack/plugins/monitoring/server/lib/kibana/__tests__/get_kibana_info.js +++ b/x-pack/plugins/monitoring/server/lib/kibana/__tests__/get_kibana_info.js @@ -50,9 +50,7 @@ describe('get_kibana_info', () => { { _source: { kibana_stats: { - timestamp: moment() - .subtract(11, 'minutes') - .format(), + timestamp: moment().subtract(11, 'minutes').format(), kibana: { data: 123, }, diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.js b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.js index c272c38f00d55..f0e3f961a498f 100644 --- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.js +++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.js @@ -63,10 +63,10 @@ export function getKibanas(req, kbnIndexPattern, { clusterUuid }) { }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(resp => { + return callWithRequest(req, 'search', params).then((resp) => { const instances = get(resp, 'hits.hits', []); - return instances.map(hit => { + return instances.map((hit) => { return { ...get(hit, '_source.kibana_stats'), availability: calculateAvailability(get(hit, '_source.timestamp')), diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js index e50e8bda3c907..77985f868da2d 100644 --- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js +++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js @@ -30,7 +30,7 @@ export function getKibanasForClusters(req, kbnIndexPattern, clusters) { const start = req.payload.timeRange.min; const end = req.payload.timeRange.max; - return Bluebird.map(clusters, cluster => { + return Bluebird.map(clusters, (cluster) => { const clusterUuid = cluster.cluster_uuid; const metric = KibanaClusterMetric.getMetricFields(); const params = { @@ -160,7 +160,7 @@ export function getKibanasForClusters(req, kbnIndexPattern, clusters) { }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(result => { + return callWithRequest(req, 'search', params).then((result) => { const aggregations = get(result, 'aggregations', {}); const kibanaUuids = get(aggregations, 'kibana_uuids.buckets', []); const statusBuckets = get(aggregations, 'status.buckets', []); @@ -177,12 +177,12 @@ export function getKibanasForClusters(req, kbnIndexPattern, clusters) { if (kibanaUuids.length) { // get instance status by finding the latest status bucket const latestTimestamp = chain(statusBuckets) - .map(bucket => bucket.max_timestamp.value) + .map((bucket) => bucket.max_timestamp.value) .max() .value(); const latestBucket = find( statusBuckets, - bucket => bucket.max_timestamp.value === latestTimestamp + (bucket) => bucket.max_timestamp.value === latestTimestamp ); status = get(latestBucket, 'key'); diff --git a/x-pack/plugins/monitoring/server/lib/logs/get_log_types.js b/x-pack/plugins/monitoring/server/lib/logs/get_log_types.js index f14eb7d805d55..fd7b5d457409f 100644 --- a/x-pack/plugins/monitoring/server/lib/logs/get_log_types.js +++ b/x-pack/plugins/monitoring/server/lib/logs/get_log_types.js @@ -19,10 +19,10 @@ async function handleResponse(response, req, filebeatIndexPattern, opts) { const typeBuckets = get(response, 'aggregations.types.buckets', []); if (typeBuckets.length) { result.enabled = true; - result.types = typeBuckets.map(typeBucket => { + result.types = typeBuckets.map((typeBucket) => { return { type: typeBucket.key.split('.')[1], - levels: typeBucket.levels.buckets.map(levelBucket => { + levels: typeBucket.levels.buckets.map((levelBucket) => { return { level: levelBucket.key.toLowerCase(), count: levelBucket.doc_count, diff --git a/x-pack/plugins/monitoring/server/lib/logs/get_logs.js b/x-pack/plugins/monitoring/server/lib/logs/get_logs.js index b876e3ba05d70..bb453e09454af 100644 --- a/x-pack/plugins/monitoring/server/lib/logs/get_logs.js +++ b/x-pack/plugins/monitoring/server/lib/logs/get_logs.js @@ -23,7 +23,7 @@ async function handleResponse(response, req, filebeatIndexPattern, opts) { const hits = get(response, 'hits.hits', []); if (hits.length) { result.enabled = true; - result.logs = hits.map(hit => { + result.logs = hits.map((hit) => { const source = hit._source; const type = get(source, 'event.dataset').split('.')[1]; const utcTimestamp = moment(get(source, '@timestamp')).valueOf(); diff --git a/x-pack/plugins/monitoring/server/lib/logstash/__tests__/get_node_info.js b/x-pack/plugins/monitoring/server/lib/logstash/__tests__/get_node_info.js index b594b94ee93d1..04db0d7492529 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/__tests__/get_node_info.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/__tests__/get_node_info.js @@ -67,9 +67,7 @@ describe('get_logstash_info', () => { { _source: { logstash_stats: { - timestamp: moment() - .subtract(11, 'minutes') - .format(), + timestamp: moment().subtract(11, 'minutes').format(), logstash: { host: 'myhost', }, @@ -115,9 +113,7 @@ describe('get_logstash_info', () => { { _source: { logstash_stats: { - timestamp: moment() - .subtract(11, 'minutes') - .format(), + timestamp: moment().subtract(11, 'minutes').format(), logstash: { host: 'myhost', }, diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_cluster_status.js b/x-pack/plugins/monitoring/server/lib/logstash/get_cluster_status.js index 4328d2897f3ee..9233860315794 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_cluster_status.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_cluster_status.js @@ -22,7 +22,7 @@ export function getClusterStatus(req, lsIndexPattern, { clusterUuid }) { checkParam(lsIndexPattern, 'lsIndexPattern in logstash/getClusterStatus'); const clusters = [{ cluster_uuid: clusterUuid }]; - return getLogstashForClusters(req, lsIndexPattern, clusters).then(clusterStatus => + return getLogstashForClusters(req, lsIndexPattern, clusters).then((clusterStatus) => get(clusterStatus, '[0].stats') ); } diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js b/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js index 55baa3cf10b50..9bce6228fbf11 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js @@ -13,9 +13,9 @@ import { LOGSTASH } from '../../../common/constants'; const { MEMORY, PERSISTED } = LOGSTASH.QUEUE_TYPES; -const getQueueTypes = queueBuckets => { - const memory = queueBuckets.find(bucket => bucket.key === MEMORY); - const persisted = queueBuckets.find(bucket => bucket.key === PERSISTED); +const getQueueTypes = (queueBuckets) => { + const memory = queueBuckets.find((bucket) => bucket.key === MEMORY); + const persisted = queueBuckets.find((bucket) => bucket.key === PERSISTED); return { [MEMORY]: get(memory, 'num_pipelines.value', 0), [PERSISTED]: get(persisted, 'num_pipelines.value', 0), @@ -42,7 +42,7 @@ export function getLogstashForClusters(req, lsIndexPattern, clusters) { const end = req.payload.timeRange.max; const config = req.server.config(); - return Bluebird.map(clusters, cluster => { + return Bluebird.map(clusters, (cluster) => { const clusterUuid = cluster.cluster_uuid; const params = { index: lsIndexPattern, @@ -177,7 +177,7 @@ export function getLogstashForClusters(req, lsIndexPattern, clusters) { }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(result => { + return callWithRequest(req, 'search', params).then((result) => { const aggregations = get(result, 'aggregations', {}); const logstashUuids = get(aggregations, 'logstash_uuids.buckets', []); const logstashVersions = get(aggregations, 'logstash_versions.buckets', []); @@ -209,7 +209,7 @@ export function getLogstashForClusters(req, lsIndexPattern, clusters) { max_uptime: maxUptime, pipeline_count: get(aggregations, 'pipelines_nested.pipelines.value', 0), queue_types: getQueueTypes(get(aggregations, 'pipelines_nested.queue_types.buckets', [])), - versions: logstashVersions.map(versionBucket => versionBucket.key), + versions: logstashVersions.map((versionBucket) => versionBucket.key), }, }; }); diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.js b/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.js index 06696abdb031f..57adaff9be1c4 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.js @@ -64,10 +64,10 @@ export function getNodes(req, lsIndexPattern, { clusterUuid }) { }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then(resp => { + return callWithRequest(req, 'search', params).then((resp) => { const instances = get(resp, 'hits.hits', []); - return instances.map(hit => { + return instances.map((hit) => { return { ...get(hit, '_source.logstash_stats'), availability: calculateAvailability(get(hit, '_source.timestamp')), diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js index 81eca5bbff9cb..fb1946d249823 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js @@ -81,7 +81,7 @@ function processPipelinesAPIResponse(response, throughputMetricKey, nodesCountMe // Normalize metric names for shared component code // Calculate latest throughput and node count for each pipeline - processedResponse.pipelines.forEach(pipeline => { + processedResponse.pipelines.forEach((pipeline) => { pipeline.metrics = { throughput: pipeline.metrics[throughputMetricKey], nodesCount: pipeline.metrics[nodesCountMetricKey], @@ -96,8 +96,8 @@ function processPipelinesAPIResponse(response, throughputMetricKey, nodesCountMe async function getPaginatedThroughputData(pipelines, req, lsIndexPattern, throughputMetric) { const metricSeriesData = Object.values( await Promise.all( - pipelines.map(pipeline => { - return new Promise(async resolve => { + pipelines.map((pipeline) => { + return new Promise(async (resolve) => { const data = await getMetrics( req, lsIndexPattern, @@ -141,7 +141,7 @@ async function getPaginatedNodesData(pipelines, req, lsIndexPattern, nodesCountM if (!Object.keys(pipelinesMap).length) { return; } - pipelines.forEach(pipeline => void (pipeline[nodesCountMetric] = pipelinesMap[pipeline.id])); + pipelines.forEach((pipeline) => void (pipeline[nodesCountMetric] = pipelinesMap[pipeline.id])); } async function getPipelines(req, lsIndexPattern, pipelines, throughputMetric, nodesCountMetric) { @@ -156,8 +156,8 @@ async function getPipelines(req, lsIndexPattern, pipelines, throughputMetric, no const pipeline = { id, metrics: { - [throughputMetric]: throughputPipelines.find(p => p.id === id).metrics[throughputMetric], - [nodesCountMetric]: nodePipelines.find(p => p.id === id).metrics[nodesCountMetric], + [throughputMetric]: throughputPipelines.find((p) => p.id === id).metrics[throughputMetric], + [nodesCountMetric]: nodePipelines.find((p) => p.id === id).metrics[nodesCountMetric], }, }; return pipeline; @@ -167,8 +167,8 @@ async function getPipelines(req, lsIndexPattern, pipelines, throughputMetric, no async function getThroughputPipelines(req, lsIndexPattern, pipelines, throughputMetric) { const metricsResponse = await Promise.all( - pipelines.map(pipeline => { - return new Promise(async resolve => { + pipelines.map((pipeline) => { + return new Promise(async (resolve) => { const data = await getMetrics(req, lsIndexPattern, [throughputMetric], [], { pipeline, }); diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js index 35a4295de298b..a2665aeb041f8 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js @@ -71,7 +71,7 @@ export function _enrichStateWithStatsAggregation( const vertices = logstashState.pipeline.representation.graph.vertices; const verticesById = {}; - vertices.forEach(vertex => { + vertices.forEach((vertex) => { verticesById[vertex.id] = vertex; vertex.stats = {}; }); @@ -82,7 +82,7 @@ export function _enrichStateWithStatsAggregation( const verticesWithStatsBuckets = statsAggregation.aggregations.pipelines.scoped.vertices.vertex_id.buckets; - verticesWithStatsBuckets.forEach(vertexStatsBucket => { + verticesWithStatsBuckets.forEach((vertexStatsBucket) => { // Each vertexStats bucket contains a list of stats for a single vertex within a single timeseries interval const vertexId = vertexStatsBucket.key; const vertex = verticesById[vertexId]; diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js index 0773ab8948564..0ddae0348fde8 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js @@ -68,8 +68,8 @@ export async function getLogstashPipelineIds( const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); const response = await callWithRequest(req, 'search', params); - return get(response, 'aggregations.nest.id.buckets', []).map(bucket => ({ + return get(response, 'aggregations.nest.id.buckets', []).map((bucket) => ({ id: bucket.key, - nodeIds: get(bucket, 'unnest.nodes.buckets', []).map(item => item.key), + nodeIds: get(bucket, 'unnest.nodes.buckets', []).map((item) => item.key), })); } diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js index 7521389c379ea..91ac158b22494 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js @@ -98,7 +98,7 @@ export function _handleResponse(response) { 'aggregations.pipelines.scoped.by_pipeline_hash.buckets', [] ); - return pipelineHashes.map(pipelineHash => ({ + return pipelineHashes.map((pipelineHash) => ({ hash: pipelineHash.key, firstSeen: get(pipelineHash, 'path_to_root.first_seen.value'), lastSeen: get(pipelineHash, 'path_to_root.last_seen.value'), diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js index 134dd88b36ce6..295d86c52b801 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js @@ -81,12 +81,12 @@ export function _enrichVertexStateWithStatsAggregation( const vertices = logstashState.pipeline.representation.graph.vertices; // First, filter out the vertex we care about - const vertex = vertices.find(v => v.id === vertexId); + const vertex = vertices.find((v) => v.id === vertexId); vertex.stats = {}; // Next, iterate over timeseries metrics and attach them to vertex const timeSeriesBuckets = vertexStatsAggregation.aggregations.timeseries.buckets; - timeSeriesBuckets.forEach(timeSeriesBucket => { + timeSeriesBuckets.forEach((timeSeriesBucket) => { // each bucket calculates stats for total pipeline CPU time for the associated timeseries const totalDurationStats = timeSeriesBucket.pipelines.scoped.total_processor_duration_stats; const totalProcessorsDurationInMillis = totalDurationStats.max - totalDurationStats.min; @@ -100,7 +100,7 @@ export function _enrichVertexStateWithStatsAggregation( totalProcessorsDurationInMillis, timeseriesIntervalInSeconds ); - Object.keys(vertexStats).forEach(stat => { + Object.keys(vertexStats).forEach((stat) => { if (!vertex.stats.hasOwnProperty(stat)) { vertex.stats[stat] = { data: [] }; } diff --git a/x-pack/plugins/monitoring/server/lib/logstash/sort_pipelines.js b/x-pack/plugins/monitoring/server/lib/logstash/sort_pipelines.js index 994e910c8ec4b..2a5c15ece4b40 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/sort_pipelines.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/sort_pipelines.js @@ -10,5 +10,5 @@ export function sortPipelines(pipelines, sort) { return pipelines; } - return sortByOrder(pipelines, pipeline => pipeline[sort.field], sort.direction); + return sortByOrder(pipelines, (pipeline) => pipeline[sort.field], sort.direction); } diff --git a/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.js b/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.js index 2b4addb7cfa0c..260c313011129 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.js @@ -50,7 +50,7 @@ export class QuotaMetric extends Metric { }, }; - this.calculation = bucket => { + this.calculation = (bucket) => { const quota = get(bucket, 'quota.value'); const deltaUsageDerivNormalizedValue = get(bucket, 'usage_deriv.normalized_value'); const periodsDerivNormalizedValue = get(bucket, 'periods_deriv.normalized_value'); diff --git a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/__test__/latency_calculation.test.js b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/__test__/latency_calculation.test.js index 29f793ddfdeda..3e7ef15c0b12a 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/__test__/latency_calculation.test.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/__test__/latency_calculation.test.js @@ -7,7 +7,7 @@ import { LatencyMetric } from '../classes'; describe('LatencyMetric for Query/Index Metric derivatives', () => { - const getLatencyMetric = metricType => { + const getLatencyMetric = (metricType) => { return new LatencyMetric({ metric: metricType, field: metricType, diff --git a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js index d3a43ed651827..8f72b330484a7 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js @@ -55,7 +55,7 @@ export class DifferenceMetric extends ElasticsearchMetric { this.getFields = () => [`${fieldSource}.${metric}`, `${fieldSource}.${metric2}`]; - this.calculation = bucket => { + this.calculation = (bucket) => { return _.get(bucket, 'metric_max.value') - _.get(bucket, 'metric2_max.value'); }; } @@ -116,7 +116,7 @@ export class LatencyMetric extends ElasticsearchMetric { }, }; - this.calculation = bucket => { + this.calculation = (bucket) => { const timeInMillisDeriv = _.get(bucket, 'event_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'event_total_deriv.normalized_value', null); @@ -244,7 +244,7 @@ export class WriteThreadPoolQueueMetric extends ElasticsearchMetric { }, }; - this.calculation = bucket => { + this.calculation = (bucket) => { const index = _.get(bucket, 'index.value', null); const bulk = _.get(bucket, 'bulk.value', null); const write = _.get(bucket, 'write.value', null); @@ -303,13 +303,13 @@ export class WriteThreadPoolRejectedMetric extends ElasticsearchMetric { }, }; - this.calculation = bucket => { + this.calculation = (bucket) => { const index = _.get(bucket, 'index_deriv.normalized_value', null); const bulk = _.get(bucket, 'bulk_deriv.normalized_value', null); const write = _.get(bucket, 'write_deriv.normalized_value', null); if (index !== null || bulk !== null || write !== null) { - const valueOrZero = value => (value < 0 ? 0 : value || 0); + const valueOrZero = (value) => (value < 0 ? 0 : value || 0); return valueOrZero(index) + valueOrZero(bulk) + valueOrZero(write); } @@ -329,7 +329,7 @@ export class MillisecondsToSecondsMetric extends ElasticsearchMetric { }), }); - this.calculation = bucket => { + this.calculation = (bucket) => { return _.get(bucket, 'metric.value') / 1000; }; } diff --git a/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.js b/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.js index 1f45d76ecff28..e84da93091cf6 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.js @@ -90,7 +90,7 @@ export class LogstashEventsLatencyClusterMetric extends LogstashClusterMetric { }, }; - this.calculation = bucket => { + this.calculation = (bucket) => { const timeInMillisDeriv = _.get(bucket, 'events_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'events_total_deriv.normalized_value', null); @@ -189,7 +189,7 @@ export class LogstashEventsLatencyMetric extends LogstashMetric { }, }; - this.calculation = bucket => { + this.calculation = (bucket) => { const timeInMillisDeriv = _.get(bucket, 'events_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'events_total_deriv.normalized_value', null); @@ -242,7 +242,7 @@ export class LogstashPipelineQueueSizeMetric extends LogstashMetric { }, }; - this.calculation = bucket => _.get(bucket, 'pipelines.total_queue_size_for_node.value'); + this.calculation = (bucket) => _.get(bucket, 'pipelines.total_queue_size_for_node.value'); } } @@ -312,7 +312,7 @@ export class LogstashPipelineNodeCountMetric extends LogstashMetric { this.getDateHistogramSubAggs = ({ pageOfPipelines }) => { const termAggExtras = {}; if (pageOfPipelines) { - termAggExtras.include = pageOfPipelines.map(pipeline => pipeline.id); + termAggExtras.include = pageOfPipelines.map((pipeline) => pipeline.id); } return { pipelines_nested: { @@ -344,10 +344,10 @@ export class LogstashPipelineNodeCountMetric extends LogstashMetric { }; }; - this.calculation = bucket => { + this.calculation = (bucket) => { const pipelineNodesCounts = {}; const pipelineBuckets = _.get(bucket, 'pipelines_nested.by_pipeline_id.buckets', []); - pipelineBuckets.forEach(pipelineBucket => { + pipelineBuckets.forEach((pipelineBucket) => { pipelineNodesCounts[pipelineBucket.key] = _.get(pipelineBucket, 'to_root.node_count.value'); }); diff --git a/x-pack/plugins/monitoring/server/lib/pagination/filter.js b/x-pack/plugins/monitoring/server/lib/pagination/filter.js index 7cc91d8deeb32..7592f2bb3afde 100644 --- a/x-pack/plugins/monitoring/server/lib/pagination/filter.js +++ b/x-pack/plugins/monitoring/server/lib/pagination/filter.js @@ -13,7 +13,7 @@ function defaultFilterFn(value, query) { } export function filter(data, queryText, fields, filterFn = defaultFilterFn) { - return data.filter(item => { + return data.filter((item) => { for (const field of fields) { if (filterFn(get(item, field), queryText)) { return true; diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.test.js b/x-pack/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.test.js index 75ca6434c4e7a..e56627369475b 100644 --- a/x-pack/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.test.js +++ b/x-pack/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.test.js @@ -26,10 +26,7 @@ const mockReq = (searchResult = {}, securityEnabled = true, userHasPermissions = }, config() { return { - get: sinon - .stub() - .withArgs('server.uuid') - .returns('kibana-1234'), + get: sinon.stub().withArgs('server.uuid').returns('kibana-1234'), }; }, usage: { diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index 22fd117595a84..607503673276b 100644 --- a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -18,7 +18,7 @@ import { getLivesNodes } from '../../elasticsearch/nodes/get_nodes/get_live_node const NUMBER_OF_SECONDS_AGO_TO_LOOK = 30; -const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nodeUuid) => { +const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nodeUuid, size) => { const start = get(req.payload, 'timeRange.min') || `now-${NUMBER_OF_SECONDS_AGO_TO_LOOK}s`; const end = get(req.payload, 'timeRange.max') || 'now'; @@ -73,6 +73,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod es_uuids: { terms: { field: 'node_stats.node_id', + size, }, aggs: { by_timestamp: { @@ -85,6 +86,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod kibana_uuids: { terms: { field: 'kibana_stats.kibana.uuid', + size, }, aggs: { by_timestamp: { @@ -97,6 +99,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod beats_uuids: { terms: { field: 'beats_stats.beat.uuid', + size, }, aggs: { by_timestamp: { @@ -107,11 +110,13 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod beat_type: { terms: { field: 'beats_stats.beat.type', + size, }, }, cluster_uuid: { terms: { field: 'cluster_uuid', + size, }, }, }, @@ -119,6 +124,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod logstash_uuids: { terms: { field: 'logstash_stats.logstash.uuid', + size, }, aggs: { by_timestamp: { @@ -129,6 +135,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod cluster_uuid: { terms: { field: 'cluster_uuid', + size, }, }, }, @@ -348,6 +355,7 @@ export const getCollectionStatus = async ( ) => { const config = req.server.config(); const kibanaUuid = config.get('server.uuid'); + const size = config.get('monitoring.ui.max_bucket_size'); const hasPermissions = await hasNecessaryPermissions(req); if (!hasPermissions) { @@ -369,7 +377,7 @@ export const getCollectionStatus = async ( ]; const [recentDocuments, detectedProducts] = await Promise.all([ - await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid, nodeUuid), + await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid, nodeUuid, size), await detectProducts(req, isLiveCluster), ]); @@ -382,7 +390,7 @@ export const getCollectionStatus = async ( const status = PRODUCTS.reduce((products, product) => { const token = product.token || product.name; - const indexBuckets = indicesBuckets.filter(bucket => bucket.key.includes(token)); + const indexBuckets = indicesBuckets.filter((bucket) => bucket.key.includes(token)); const uuidBucketName = getUuidBucketName(product.name); const productStatus = { diff --git a/x-pack/plugins/monitoring/server/license_service.ts b/x-pack/plugins/monitoring/server/license_service.ts index 01746a550ced6..396e8cd566e1f 100644 --- a/x-pack/plugins/monitoring/server/license_service.ts +++ b/x-pack/plugins/monitoring/server/license_service.ts @@ -31,7 +31,7 @@ export class LicenseService { ); let rawLicense: Readonly | undefined; - let licenseSubscription: Subscription | undefined = license$.subscribe(nextRawLicense => { + let licenseSubscription: Subscription | undefined = license$.subscribe((nextRawLicense) => { rawLicense = nextRawLicense; }); diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index a45e80ac71d65..f4f38f70b1ccb 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -47,7 +47,7 @@ import { MonitoringLicenseService } from './types'; import { PluginStartContract as AlertingPluginStartContract, PluginSetupContract as AlertingPluginSetupContract, -} from '../../alerting/server'; +} from '../../alerts/server'; import { getLicenseExpiration } from './alerts/license_expiration'; import { getClusterState } from './alerts/cluster_state'; import { InfraPluginSetup } from '../../infra/server'; @@ -61,12 +61,12 @@ interface PluginsSetup { usageCollection?: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetupContract; - alerting: AlertingPluginSetupContract; + alerts: AlertingPluginSetupContract; infra: InfraPluginSetup; } interface PluginsStart { - alerting: AlertingPluginStartContract; + alerts: AlertingPluginStartContract; } interface MonitoringCoreConfig { @@ -131,7 +131,7 @@ export class Plugin { this.legacyShimDependencies = { router: core.http.createRouter(), instanceUuid: core.uuid.getInstanceUuid(), - esDataClient: core.elasticsearch.dataClient, + esDataClient: core.elasticsearch.legacy.client, kibanaStatsCollector: plugins.usageCollection?.getCollectorByType( KIBANA_STATS_TYPE_MONITORING ), @@ -142,7 +142,7 @@ export class Plugin { const cluster = (this.cluster = instantiateClient( config.ui.elasticsearch, this.log, - core.elasticsearch.createClient + core.elasticsearch.legacy.createClient )); // Start our license service which will ensure @@ -156,7 +156,7 @@ export class Plugin { await this.licenseService.refresh(); if (KIBANA_ALERTING_ENABLED) { - plugins.alerting.registerType( + plugins.alerts.registerType( getLicenseExpiration( async () => { const coreStart = (await core.getStartServices())[0]; @@ -167,7 +167,7 @@ export class Plugin { config.ui.ccs.enabled ) ); - plugins.alerting.registerType( + plugins.alerts.registerType( getClusterState( async () => { const coreStart = (await core.getStartServices())[0]; @@ -357,7 +357,7 @@ export class Plugin { payload: req.body, getKibanaStatsCollector: () => this.legacyShimDependencies.kibanaStatsCollector, getUiSettingsService: () => context.core.uiSettings.client, - getAlertsClient: () => plugins.alerting.getAlertsClientWithRequest(req), + getAlertsClient: () => plugins.alerts.getAlertsClientWithRequest(req), server: { config: legacyConfigWrapper, newPlatform: { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js index 14f3ca8b5cb55..688caac9b60b1 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js @@ -43,7 +43,7 @@ export function legacyClusterAlertsRoute(server) { end: req.payload.timeRange.max, }; - return getClusterLicense(req, esIndexPattern, clusterUuid).then(license => + return getClusterLicense(req, esIndexPattern, clusterUuid).then((license) => alertsClusterSearch( req, alertsIndex, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/_get_apm_cluster_status.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/_get_apm_cluster_status.js index d930440355cd2..4b758d42fa612 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/_get_apm_cluster_status.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/_get_apm_cluster_status.js @@ -9,5 +9,5 @@ import { getApmsForClusters } from '../../../../lib/apm/get_apms_for_clusters'; export const getApmClusterStatus = (req, apmIndexPattern, { clusterUuid }) => { const clusters = [{ cluster_uuid: clusterUuid }]; - return getApmsForClusters(req, apmIndexPattern, clusters).then(apms => get(apms, '[0].stats')); + return getApmsForClusters(req, apmIndexPattern, clusters).then((apms) => get(apms, '[0].stats')); }; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/check_access/check_access.js b/x-pack/plugins/monitoring/server/routes/api/v1/check_access/check_access.js index e01a044f76fd5..bc843252d49c9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/check_access/check_access.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/check_access/check_access.js @@ -15,7 +15,7 @@ export function checkAccessRoute(server) { server.route({ method: 'GET', path: '/api/monitoring/v1/check_access', - handler: async req => { + handler: async (req) => { const response = {}; try { await verifyMonitoringAuth(req); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js index 240cb84539dbf..0035411d92f66 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js @@ -32,7 +32,7 @@ export function clusterRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { const config = server.config(); await verifyCcsAvailability(req); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js index 014f22c1ffe19..67de8d79df95d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js @@ -30,7 +30,7 @@ export function clustersRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { let clusters = []; const config = server.config(); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js index b16a83199a728..9999ba774b28d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js @@ -205,10 +205,10 @@ export function ccrRoute(server) { const fullStats = get(response, 'hits.hits').reduce((accum, hit) => { const innerHits = get(hit, 'inner_hits.by_shard.hits.hits'); - const innerHitsSource = innerHits.map(innerHit => get(innerHit, '_source.ccr_stats')); + const innerHitsSource = innerHits.map((innerHit) => get(innerHit, '_source.ccr_stats')); const grouped = groupBy( innerHitsSource, - stat => `${stat.follower_index}:${stat.shard_id}` + (stat) => `${stat.follower_index}:${stat.shard_id}` ); return { @@ -248,7 +248,7 @@ export function ccrRoute(server) { return accum; }, []); - stat.error = (stat.shards.find(shard => shard.error) || {}).error; + stat.error = (stat.shards.find((shard) => shard.error) || {}).error; stat.opsSynced = stat.shards.reduce((sum, { opsSynced }) => sum + opsSynced, 0); stat.syncLagTime = stat.shards.reduce( (max, { syncLagTime }) => Math.max(max, syncLagTime), diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js index 0d589146878de..3928d09e603c2 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js @@ -38,7 +38,7 @@ export function esIndexRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { try { const config = server.config(); const ccs = req.payload.ccs; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js index 0156ea2222171..d41d386c4c2bd 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js @@ -64,7 +64,7 @@ export function esNodeRoute(server) { const showCgroupMetricsElasticsearch = config.get( 'monitoring.ui.container.elasticsearch.enabled' ); - const metricCpu = metricSet.find(m => m.name === 'node_cpu_metric'); + const metricCpu = metricSet.find((m) => m.name === 'node_cpu_metric'); if (showCgroupMetricsElasticsearch) { metricCpu.keys = ['node_cgroup_quota_as_cpu_utilization']; } else { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/_get_kibana_cluster_status.js b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/_get_kibana_cluster_status.js index 2097727f47708..892c560c7f5ee 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/_get_kibana_cluster_status.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/_get_kibana_cluster_status.js @@ -9,7 +9,7 @@ import { getKibanasForClusters } from '../../../../lib/kibana/get_kibanas_for_cl export const getKibanaClusterStatus = (req, kbnIndexPattern, { clusterUuid }) => { const clusters = [{ cluster_uuid: clusterUuid }]; - return getKibanasForClusters(req, kbnIndexPattern, clusters).then(kibanas => + return getKibanasForClusters(req, kbnIndexPattern, clusters).then((kibanas) => get(kibanas, '[0].stats') ); }; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js index f2a337b3cfa20..4f039afc61f0b 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js @@ -61,7 +61,7 @@ export function logstashNodeRoute(server) { metricSet = metricSetOverview; // set the cgroup option if needed const showCgroupMetricsLogstash = config.get('monitoring.ui.container.logstash.enabled'); - const metricCpu = metricSet.find(m => m.name === 'logstash_node_cpu_metric'); + const metricCpu = metricSet.find((m) => m.name === 'logstash_node_cpu_metric'); if (showCgroupMetricsLogstash) { metricCpu.keys = ['logstash_node_cgroup_quota_as_cpu_utilization']; } else { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js index 8f19d164ad5d3..4c13dd496c545 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js @@ -46,7 +46,7 @@ export function logstashPipelineRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js index f96a534a1eab0..1a625cd2737a8 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js @@ -31,7 +31,7 @@ export function logstashClusterPipelineIdsRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { const config = server.config(); const { ccs } = req.payload; const clusterUuid = req.params.clusterUuid; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js index 4491b0289dcf7..b67d6b10eca00 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js @@ -43,7 +43,7 @@ export function logstashClusterPipelinesRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { const config = server.config(); const { ccs, pagination, sort, queryText } = req.payload; const clusterUuid = req.params.clusterUuid; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js index b14b24ba3e81a..3c059dd43745e 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js @@ -44,7 +44,7 @@ export function logstashNodePipelinesRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { const config = server.config(); const { ccs, pagination, sort, queryText } = req.payload; const { clusterUuid, logstashUuid } = req.params; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js b/x-pack/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js index a4b811b88ee84..96d4087b15b2d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js @@ -44,7 +44,7 @@ export function clusterSetupStatusRoute(server) { ), }, }, - handler: async req => { + handler: async (req) => { let status = null; // NOTE using try/catch because checkMonitoringAuth is expected to throw diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/setup/disable_elasticsearch_internal_collection.js b/x-pack/plugins/monitoring/server/routes/api/v1/setup/disable_elasticsearch_internal_collection.js index d3bb5523c1a94..1d2e6f43a8a8f 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/setup/disable_elasticsearch_internal_collection.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/setup/disable_elasticsearch_internal_collection.js @@ -19,7 +19,7 @@ export function disableElasticsearchInternalCollectionRoute(server) { }), }, }, - handler: async req => { + handler: async (req) => { // NOTE using try/catch because checkMonitoringAuth is expected to throw // an error when current logged-in user doesn't have permission to read // the monitoring data. `try/catch` makes it a little more explicit. diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js b/x-pack/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js index 1612e501faa5f..f4164a75ba32d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js @@ -46,7 +46,7 @@ export function nodeSetupStatusRoute(server) { ), }, }, - handler: async req => { + handler: async (req) => { let status = null; // NOTE using try/catch because checkMonitoringAuth is expected to throw diff --git a/x-pack/plugins/monitoring/server/routes/index.js b/x-pack/plugins/monitoring/server/routes/index.js index fb3ad8627559f..0aefed4d9a507 100644 --- a/x-pack/plugins/monitoring/server/routes/index.js +++ b/x-pack/plugins/monitoring/server/routes/index.js @@ -10,7 +10,7 @@ import * as uiRoutes from './api/v1/ui'; // namespace import export function requireUIRoutes(server) { const routes = Object.keys(uiRoutes); - routes.forEach(route => { + routes.forEach((route) => { const registerRoute = uiRoutes[route]; // computed reference to module objects imported via namespace registerRoute(server); }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts index b180730acfd36..45fdf1997d214 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts @@ -31,7 +31,7 @@ export const getAllStats: StatsGetter = async ( { callCluster, start, end }, { maxBucketSize } ) => { - const clusterUuids = clustersDetails.map(clusterDetails => clusterDetails.clusterUuid); + const clusterUuids = clustersDetails.map((clusterDetails) => clusterDetails.clusterUuid); const [esClusters, kibana, logstash, beats] = await Promise.all([ getElasticsearchStats(callCluster, clusterUuids, maxBucketSize), // cluster_stats, stack_stats.xpack, cluster_name/uuid, license, version @@ -65,7 +65,7 @@ export function handleAllStats( beats: PromiseReturnType; } ) { - return clusters.map(cluster => { + return clusters.map((cluster) => { const stats = { ...cluster, stack_stats: { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts index bf904e46516e0..0585ec2c08274 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts @@ -164,7 +164,7 @@ export function processResults( }: BeatsProcessOptions ) { const currHits = results?.hits?.hits || []; - currHits.forEach(hit => { + currHits.forEach((hit) => { const clusterUuid = hit._source.cluster_uuid; if (clusters[clusterUuid] === undefined) { clusters[clusterUuid] = getBaseStats(); @@ -211,7 +211,7 @@ export function processResults( const stateInput = hit._source.beats_state?.state?.input; if (stateInput !== undefined) { const inputSet = clusterInputSets[clusterUuid]; - stateInput.names.forEach(name => inputSet.add(name)); + stateInput.names.forEach((name) => inputSet.add(name)); clusters[clusterUuid].input.names = Array.from(inputSet); clusters[clusterUuid].input.count += stateInput.count; } @@ -220,7 +220,7 @@ export function processResults( const statsType = hit._source.beats_state?.beat?.type; if (stateModule !== undefined) { const moduleSet = clusterModuleSets[clusterUuid]; - stateModule.names.forEach(name => moduleSet.add(statsType + '.' + name)); + stateModule.names.forEach((name) => moduleSet.add(statsType + '.' + name)); clusters[clusterUuid].module.names = Array.from(moduleSet); clusters[clusterUuid].module.count += stateModule.count; } diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts index e8133afd95c20..519dcc38875f5 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts @@ -21,8 +21,8 @@ describe('get_cluster_uuids', () => { }, }; const expectedUuids = response.aggregations.cluster_uuids.buckets - .map(bucket => bucket.key) - .map(expectedUuid => ({ clusterUuid: expectedUuid })); + .map((bucket) => bucket.key) + .map((expectedUuid) => ({ clusterUuid: expectedUuid })); const start = new Date().toISOString(); const end = new Date().toISOString(); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts index db025450ba833..9d4657b0ad799 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts @@ -62,7 +62,7 @@ export function fetchClusterUuids( export function handleClusterUuidsResponse(response: any): ClusterDetails[] { const uuidBuckets: any[] = get(response, 'aggregations.cluster_uuids.buckets', []); - return uuidBuckets.map(uuidBucket => ({ + return uuidBuckets.map((uuidBucket) => ({ clusterUuid: uuidBucket.key as string, })); } diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts index 52fb989bfd20f..e30229e1891f5 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts @@ -22,8 +22,8 @@ describe('get_es_stats', () => { ], }, }; - const expectedClusters = response.hits.hits.map(hit => hit._source); - const clusterUuids = expectedClusters.map(cluster => cluster.cluster_uuid); + const expectedClusters = response.hits.hits.map((hit) => hit._source); + const clusterUuids = expectedClusters.map((cluster) => cluster.cluster_uuid); const maxBucketSize = 1; describe('getElasticsearchStats', () => { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts index 37b6cbcf249d6..708bef31d8ac8 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts @@ -86,5 +86,5 @@ export interface ESClusterStats { export function handleElasticsearchStats(response: SearchResponse) { const clusters = response.hits?.hits || []; - return clusters.map(cluster => cluster._source); + return clusters.map((cluster) => cluster._source); } diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts index 87a1fb7e6e5cf..0f6a86af79e45 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts @@ -153,7 +153,7 @@ function groupInstancesByCluster( const clusterMap = new Map(); // hits are sorted arbitrarily by product UUID - instances.map(instance => { + instances.map((instance) => { const clusterUuid = instance._source.cluster_uuid; const version: string | undefined = get( instance, diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts index 16fa47afdb692..4812d9522d7ae 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts @@ -18,8 +18,8 @@ describe('get_licenses', () => { ], }, }; - const expectedClusters = response.hits.hits.map(hit => hit._source); - const clusterUuids = expectedClusters.map(cluster => ({ clusterUuid: cluster.cluster_uuid })); + const expectedClusters = response.hits.hits.map((hit) => hit._source); + const clusterUuids = expectedClusters.map((cluster) => ({ clusterUuid: cluster.cluster_uuid })); const expectedLicenses = { abc: { type: 'basic' }, xyz: { type: 'basic' }, diff --git a/x-pack/plugins/observability/public/components/action_menu.tsx b/x-pack/plugins/observability/public/components/action_menu.tsx index a5f59ecd5506f..ea79f4d08d701 100644 --- a/x-pack/plugins/observability/public/components/action_menu.tsx +++ b/x-pack/plugins/observability/public/components/action_menu.tsx @@ -20,7 +20,7 @@ import styled from 'styled-components'; type Props = EuiPopoverProps & HTMLAttributes; -export const SectionTitle: React.FC<{}> = props => ( +export const SectionTitle: React.FC<{}> = (props) => ( <>
    {props.children}
    @@ -29,7 +29,7 @@ export const SectionTitle: React.FC<{}> = props => ( ); -export const SectionSubtitle: React.FC<{}> = props => ( +export const SectionSubtitle: React.FC<{}> = (props) => ( <> {props.children} @@ -38,7 +38,7 @@ export const SectionSubtitle: React.FC<{}> = props => ( ); -export const SectionLinks: React.FC<{}> = props => ( +export const SectionLinks: React.FC<{}> = (props) => ( {props.children} @@ -54,10 +54,10 @@ export const Section = styled.div` `; export type SectionLinkProps = EuiListGroupItemProps; -export const SectionLink: React.FC = props => ( +export const SectionLink: React.FC = (props) => ( ); -export const ActionMenuDivider: React.FC<{}> = props => ; +export const ActionMenuDivider: React.FC<{}> = (props) => ; -export const ActionMenu: React.FC = props => ; +export const ActionMenu: React.FC = (props) => ; diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index 6ea9f82d11ab9..f57e1a774a8e2 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -39,7 +39,7 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { getScopedAnnotationsClient: (requestContext: RequestHandlerContext, request: KibanaRequest) => { return createAnnotationsClient({ index, - apiCaller: core.elasticsearch.dataClient.asScoped(request).callAsCurrentUser, + apiCaller: requestContext.core.elasticsearch.legacy.client.callAsCurrentUser, logger, license: requestContext.licensing?.license, }); diff --git a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts index 21ebfcd6205e7..5d0fdc65117bf 100644 --- a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts @@ -50,7 +50,7 @@ export function registerAnnotationAPIs({ }); } - const apiCaller = core.elasticsearch.dataClient.asScoped(request).callAsCurrentUser; + const apiCaller = context.core.elasticsearch.legacy.client.callAsCurrentUser; const client = createAnnotationsClient({ index, diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 86cac2f340e44..ee328c4deb082 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -38,7 +38,7 @@ export class ObservabilityPlugin implements Plugin { core, index: config.annotations.index, context: this.initContext, - }).catch(err => { + }).catch((err) => { const logger = this.initContext.logger.get('annotations'); logger.warn(err); throw err; diff --git a/x-pack/plugins/observability/server/utils/create_or_update_index.ts b/x-pack/plugins/observability/server/utils/create_or_update_index.ts index 2c6f3dbefdeb1..aa12b2227b7d2 100644 --- a/x-pack/plugins/observability/server/utils/create_or_update_index.ts +++ b/x-pack/plugins/observability/server/utils/create_or_update_index.ts @@ -62,7 +62,7 @@ export async function createOrUpdateIndex({ } }, { - onFailedAttempt: e => { + onFailedAttempt: (e) => { logger.warn(`Could not create index: '${index}'. Retrying...`); logger.warn(e); }, diff --git a/x-pack/plugins/oss_telemetry/server/lib/get_next_midnight.test.ts b/x-pack/plugins/oss_telemetry/server/lib/get_next_midnight.test.ts index c20e4a0b4be54..3bafb84d61157 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/get_next_midnight.test.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/get_next_midnight.test.ts @@ -9,10 +9,7 @@ import { getNextMidnight } from './get_next_midnight'; describe('getNextMidnight', () => { test('Returns the next time and date of midnight as an iso string', () => { - const nextMidnightMoment = moment() - .add(1, 'days') - .startOf('day') - .toDate(); + const nextMidnightMoment = moment().add(1, 'days').startOf('day').toDate(); expect(getNextMidnight()).toEqual(nextMidnightMoment); }); diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts index f60c44e548f3f..b15ead36a75f6 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts @@ -6,6 +6,8 @@ import { Observable } from 'rxjs'; import _, { countBy, groupBy, mapValues } from 'lodash'; +import { first } from 'rxjs/operators'; + import { APICaller, IClusterClient } from 'src/core/server'; import { getNextMidnight } from '../../get_next_midnight'; import { TaskInstance } from '../../../../../task_manager/server'; @@ -56,7 +58,7 @@ async function getStats(callCluster: APICaller, index: string) { const visTypes = groupBy(visSummaries, 'type'); // get the final result - return mapValues(visTypes, curr => { + return mapValues(visTypes, (curr) => { const total = curr.length; const spacesBreakdown = countBy(curr, 'space'); const spaceCounts: number[] = _.values(spacesBreakdown); @@ -80,7 +82,7 @@ export function visualizationsTaskRunner( let error; try { - const index = (await config.toPromise()).kibana.index; + const index = (await config.pipe(first()).toPromise()).kibana.index; stats = await getStats((await esClientPromise).callAsInternalUser, index); } catch (err) { if (err.constructor === Error) { diff --git a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts index 4d703db3dcc64..428909dc7feed 100644 --- a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts +++ b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts @@ -60,7 +60,7 @@ export const getMockTaskFetch = ( docs: ConcreteTaskInstance[] = defaultMockTaskDocs ): Partial> => { return { - fetch: jest.fn(fetchOpts => { + fetch: jest.fn((fetchOpts) => { return Promise.resolve({ docs, searchAfter: [] }); }), } as Partial>; @@ -70,7 +70,7 @@ export const getMockThrowingTaskFetch = ( throws: Error ): Partial> => { return { - fetch: jest.fn(fetchOpts => { + fetch: jest.fn((fetchOpts) => { throw throws; }), } as Partial>; diff --git a/x-pack/plugins/painless_lab/public/application/components/main.tsx b/x-pack/plugins/painless_lab/public/application/components/main.tsx index 3a1e0cd6948b3..5216149596d28 100644 --- a/x-pack/plugins/painless_lab/public/application/components/main.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/main.tsx @@ -74,7 +74,7 @@ export const Main: React.FunctionComponent = () => { - updatePayload({ code: nextCode })} /> + updatePayload({ code: nextCode })} /> diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx index 47efd524f092a..eab91b0b0c212 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx @@ -64,7 +64,7 @@ export const ContextTab: FunctionComponent = () => { updatePayload({ context: nextContext })} + onChange={(nextContext) => updatePayload({ context: nextContext })} itemLayoutAlign="top" hasDividers fullWidth @@ -103,7 +103,7 @@ export const ContextTab: FunctionComponent = () => { { + onChange={(e) => { const nextIndex = e.target.value; updatePayload({ index: nextIndex }); }} @@ -143,7 +143,7 @@ export const ContextTab: FunctionComponent = () => { languageId="json" height={150} value={query} - onChange={nextQuery => updatePayload({ query: nextQuery })} + onChange={(nextQuery) => updatePayload({ query: nextQuery })} options={{ fontSize: 12, minimap: { @@ -182,7 +182,7 @@ export const ContextTab: FunctionComponent = () => { languageId="json" height={400} value={document} - onChange={nextDocument => updatePayload({ document: nextDocument })} + onChange={(nextDocument) => updatePayload({ document: nextDocument })} options={{ fontSize: 12, minimap: { diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx index 7c8bce0f7b21b..b707fd493bcdd 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx @@ -14,7 +14,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { monaco } from '@kbn/ui-shared-deps/monaco'; +import { monaco } from '@kbn/monaco'; import { i18n } from '@kbn/i18n'; import { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public'; @@ -61,7 +61,7 @@ export const ParametersTab: FunctionComponent = () => { languageId="json" height={600} value={payload.parameters} - onChange={nextParams => updatePayload({ parameters: nextParams })} + onChange={(nextParams) => updatePayload({ parameters: nextParams })} options={{ fontSize: 12, minimap: { diff --git a/x-pack/plugins/painless_lab/public/application/lib/format.test.ts b/x-pack/plugins/painless_lab/public/application/lib/format.test.ts index 5f0022ebbc089..72638c0a95c09 100644 --- a/x-pack/plugins/painless_lab/public/application/lib/format.test.ts +++ b/x-pack/plugins/painless_lab/public/application/lib/format.test.ts @@ -8,7 +8,7 @@ import { PayloadFormat } from '../types'; import { formatRequestPayload } from './format'; describe('formatRequestPayload', () => { - Object.values(PayloadFormat).forEach(format => { + Object.values(PayloadFormat).forEach((format) => { describe(`${format} formats`, () => { test('no script', () => { expect(formatRequestPayload({}, format)).toMatchSnapshot(); diff --git a/x-pack/plugins/painless_lab/public/lib/monaco_painless_lang.ts b/x-pack/plugins/painless_lab/public/lib/monaco_painless_lang.ts index 602697064a768..4278c77b4c791 100644 --- a/x-pack/plugins/painless_lab/public/lib/monaco_painless_lang.ts +++ b/x-pack/plugins/painless_lab/public/lib/monaco_painless_lang.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as monaco from 'monaco-editor'; +import { monaco } from '@kbn/monaco'; /** * Extends the default type for a Monarch language so we can use diff --git a/x-pack/plugins/painless_lab/public/plugin.tsx b/x-pack/plugins/painless_lab/public/plugin.tsx index 745a78ed10595..1ea6991d6023e 100644 --- a/x-pack/plugins/painless_lab/public/plugin.tsx +++ b/x-pack/plugins/painless_lab/public/plugin.tsx @@ -106,7 +106,7 @@ export class PainlessLabUIPlugin implements Plugin { + licensing.license$.subscribe((license) => { if (!checkLicenseStatus(license).valid && !devTool.isDisabled()) { devTool.disable(); } else if (devTool.isDisabled()) { diff --git a/x-pack/plugins/painless_lab/public/services/language_service.ts b/x-pack/plugins/painless_lab/public/services/language_service.ts index efff9cd0e78d5..68ac3ca290ad8 100644 --- a/x-pack/plugins/painless_lab/public/services/language_service.ts +++ b/x-pack/plugins/painless_lab/public/services/language_service.ts @@ -7,7 +7,7 @@ // It is important that we use this specific monaco instance so that // editor settings are registered against the instance our React component // uses. -import { monaco } from '@kbn/ui-shared-deps/monaco'; +import { monaco } from '@kbn/monaco'; // @ts-ignore import workerSrc from 'raw-loader!monaco-editor/min/vs/base/worker/workerMain.js'; diff --git a/x-pack/plugins/painless_lab/server/routes/api/execute.ts b/x-pack/plugins/painless_lab/server/routes/api/execute.ts index 55adb5e0410cc..7d415761f04cb 100644 --- a/x-pack/plugins/painless_lab/server/routes/api/execute.ts +++ b/x-pack/plugins/painless_lab/server/routes/api/execute.ts @@ -23,7 +23,7 @@ export function registerExecuteRoute({ router, license }: RouteDependencies) { const body = req.body; try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const response = await callAsCurrentUser('scriptsPainlessExecute', { body, }); diff --git a/x-pack/plugins/painless_lab/server/services/license.ts b/x-pack/plugins/painless_lab/server/services/license.ts index 0a4748bd0ace0..9b68acd073c4a 100644 --- a/x-pack/plugins/painless_lab/server/services/license.ts +++ b/x-pack/plugins/painless_lab/server/services/license.ts @@ -35,7 +35,7 @@ export class License { { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } ) { - licensing.license$.subscribe(license => { + licensing.license$.subscribe((license) => { const { state, message } = license.check(pluginId, minimumLicenseType); const hasRequiredLicense = state === 'valid'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/http_requests.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/http_requests.js index 38ee96e478f46..182700b0c8fbd 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/http_requests.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/http_requests.js @@ -7,14 +7,14 @@ import sinon from 'sinon'; // Register helpers to mock HTTP Requests -const registerHttpRequestMockHelpers = server => { +const registerHttpRequestMockHelpers = (server) => { const mockResponse = (defaultResponse, response) => [ 200, { 'Content-Type': 'application/json' }, JSON.stringify({ ...defaultResponse, ...response }), ]; - const setLoadRemoteClustersResponse = response => { + const setLoadRemoteClustersResponse = (response) => { const defaultResponse = []; server.respondWith('GET', '/api/remote_clusters', [ @@ -24,7 +24,7 @@ const registerHttpRequestMockHelpers = server => { ]); }; - const setDeleteRemoteClusterResponse = response => { + const setDeleteRemoteClusterResponse = (response) => { const defaultResponse = { itemsDeleted: [], errors: [], diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_add.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_add.helpers.js index dd1d5d2187176..c8badc92ab11c 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_add.helpers.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_add.helpers.js @@ -14,13 +14,13 @@ import { registerRouter } from '../../../public/application/services/routing'; const testBedConfig = { store: createRemoteClustersStore, memoryRouter: { - onRouter: router => registerRouter(router), + onRouter: (router) => registerRouter(router), }, }; const initTestBed = registerTestBed(RemoteClusterAdd, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); // User actions diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_edit.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_edit.helpers.js index 426aea90e5a99..294861405b9d5 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_edit.helpers.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_edit.helpers.js @@ -16,7 +16,7 @@ import { REMOTE_CLUSTER_EDIT_NAME } from './constants'; const testBedConfig = { store: createRemoteClustersStore, memoryRouter: { - onRouter: router => registerRouter(router), + onRouter: (router) => registerRouter(router), // The remote cluster name to edit is read from the router ":id" param // so we first set it in our initial entries initialEntries: [`/${REMOTE_CLUSTER_EDIT_NAME}`], diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_list.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_list.helpers.js index 1d5bc52038ffc..ce9638d95bd28 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_list.helpers.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_list.helpers.js @@ -14,13 +14,13 @@ import { registerRouter } from '../../../public/application/services/routing'; const testBedConfig = { store: createRemoteClustersStore, memoryRouter: { - onRouter: router => registerRouter(router), + onRouter: (router) => registerRouter(router), }, }; const initTestBed = registerTestBed(RemoteClusterList, testBedConfig); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); const EUI_TABLE = 'remoteClusterListTable'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js index 569c9a6c56c5a..6e1b2f6266584 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js @@ -97,7 +97,7 @@ describe('Create Remote cluster', () => { }); test('should only allow alpha-numeric characters, "-" (dash) and "_" (underscore)', () => { - const expectInvalidChar = char => { + const expectInvalidChar = (char) => { if (char === '-' || char === '_') { return; } @@ -129,9 +129,9 @@ describe('Create Remote cluster', () => { test('should only allow alpha-numeric characters and "-" (dash) in the node "host" part', () => { actions.clickSaveForm(); // display form errors - const notInArray = array => value => array.indexOf(value) < 0; + const notInArray = (array) => (value) => array.indexOf(value) < 0; - const expectInvalidChar = char => { + const expectInvalidChar = (char) => { form.setComboBoxValue('remoteClusterFormSeedsInput', `192.16${char}:3000`); expect(form.getErrorsMessages()).toContain( `Seed node must use host:port format. Example: 127.0.0.1:9400, localhost:9400. Hosts can only consist of letters, numbers, and dashes.` @@ -168,9 +168,9 @@ describe('Create Remote cluster', () => { test('should only allow alpha-numeric characters and "-" (dash) in the proxy address "host" part', () => { actions.clickSaveForm(); // display form errors - const notInArray = array => value => array.indexOf(value) < 0; + const notInArray = (array) => (value) => array.indexOf(value) < 0; - const expectInvalidChar = char => { + const expectInvalidChar = (char) => { form.setInputValue('remoteClusterFormProxyAddressInput', `192.16${char}:3000`); expect(form.getErrorsMessages()).toContain( 'Address must use host:port format. Example: 127.0.0.1:9400, localhost:9400. Hosts can only consist of letters, numbers, and dashes.' diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js index 44b28eb9e783e..9489eb86681b8 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js @@ -316,11 +316,7 @@ describe('', () => { test('should have a "Status" section', () => { actions.clickRemoteClusterAt(0); - expect( - find('remoteClusterDetailPanelStatusSection') - .find('h3') - .text() - ).toEqual('Status'); + expect(find('remoteClusterDetailPanelStatusSection').find('h3').text()).toEqual('Status'); expect(exists('remoteClusterDetailPanelStatusValues')).toBe(true); }); diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts index 07dbe8da28d8a..373922f55551f 100644 --- a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts +++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts @@ -132,7 +132,7 @@ export function deserializeCluster( } // It's unnecessary to send undefined values back to the client, so we can remove them. - Object.keys(deserializedClusterObject).forEach(key => { + Object.keys(deserializedClusterObject).forEach((key) => { if (deserializedClusterObject[key as keyof Cluster] === undefined) { delete deserializedClusterObject[key as keyof Cluster]; } diff --git a/x-pack/plugins/remote_clusters/public/application/app.js b/x-pack/plugins/remote_clusters/public/application/app.js index 483b2f5b97e27..714887b039a42 100644 --- a/x-pack/plugins/remote_clusters/public/application/app.js +++ b/x-pack/plugins/remote_clusters/public/application/app.js @@ -6,9 +6,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Switch, Route, Redirect, withRouter } from 'react-router-dom'; +import { Switch, Route, Redirect, Router } from 'react-router-dom'; -import { CRUD_APP_BASE_PATH, UIM_APP_LOAD } from './constants'; +import { UIM_APP_LOAD } from './constants'; import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services'; import { RemoteClusterList, RemoteClusterAdd, RemoteClusterEdit } from './sections'; @@ -22,15 +22,16 @@ class AppComponent extends Component { constructor(...args) { super(...args); + setUserHasLeftApp(false); this.registerRouter(); } registerRouter() { // Share the router with the app without requiring React or context. - const { history, location } = this.props; + const { history } = this.props; registerRouter({ history, - route: { location }, + route: { location: history.location }, }); } @@ -45,17 +46,16 @@ class AppComponent extends Component { render() { return ( -
    + - - - - - + + + + -
    + ); } } -export const App = withRouter(AppComponent); +export const App = AppComponent; diff --git a/x-pack/plugins/remote_clusters/public/application/index.d.ts b/x-pack/plugins/remote_clusters/public/application/index.d.ts index b021dca51bacd..8b2af65e4fff7 100644 --- a/x-pack/plugins/remote_clusters/public/application/index.d.ts +++ b/x-pack/plugins/remote_clusters/public/application/index.d.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ScopedHistory } from 'kibana/public'; import { RegisterManagementAppArgs, I18nStart } from '../types'; export declare const renderApp: ( @@ -11,5 +12,6 @@ export declare const renderApp: ( I18nContext: I18nStart['Context'], appDependencies: { isCloudEnabled?: boolean; - } + }, + history: ScopedHistory ) => ReturnType; diff --git a/x-pack/plugins/remote_clusters/public/application/index.js b/x-pack/plugins/remote_clusters/public/application/index.js index cf6e855ba58df..25e171c9ef51d 100644 --- a/x-pack/plugins/remote_clusters/public/application/index.js +++ b/x-pack/plugins/remote_clusters/public/application/index.js @@ -6,7 +6,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { App } from './app'; @@ -15,14 +14,12 @@ import { AppContextProvider } from './app_context'; import './_hacks.scss'; -export const renderApp = (elem, I18nContext, appDependencies) => { +export const renderApp = (elem, I18nContext, appDependencies, history) => { render( - - - + , diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js index cff9166f2f30b..f4d04a0694f91 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js @@ -125,7 +125,7 @@ export class RemoteClusterForm extends Component { }; } - onFieldsChange = changedFields => { + onFieldsChange = (changedFields) => { this.setState(({ fields: prevFields, seedInput }) => { const newFields = { ...prevFields, @@ -191,7 +191,7 @@ export class RemoteClusterForm extends Component { save(cluster); }; - onCreateSeed = newSeed => { + onCreateSeed = (newSeed) => { // If the user just hit enter without typing anything, treat it as a no-op. if (!newSeed) { return; @@ -217,7 +217,7 @@ export class RemoteClusterForm extends Component { this.onFieldsChange({ seeds: newSeeds }); }; - onSeedsInputChange = seedInput => { + onSeedsInputChange = (seedInput) => { if (!seedInput) { // If empty seedInput ("") don't do anything. This happens // right after a seed is created. @@ -250,16 +250,16 @@ export class RemoteClusterForm extends Component { }); }; - onSeedsChange = seeds => { + onSeedsChange = (seeds) => { this.onFieldsChange({ seeds: seeds.map(({ label }) => label) }); }; - onSkipUnavailableChange = e => { + onSkipUnavailableChange = (e) => { const skipUnavailable = e.target.checked; this.onFieldsChange({ skipUnavailable }); }; - resetToDefault = fieldName => { + resetToDefault = (fieldName) => { this.onFieldsChange({ [fieldName]: defaultFields[fieldName], }); @@ -268,7 +268,7 @@ export class RemoteClusterForm extends Component { hasErrors = () => { const { fieldsErrors, localSeedErrors } = this.state; const errorValues = Object.values(fieldsErrors); - const hasErrors = errorValues.some(error => error != null) || localSeedErrors.length; + const hasErrors = errorValues.some((error) => error != null) || localSeedErrors.length; return hasErrors; }; @@ -285,7 +285,7 @@ export class RemoteClusterForm extends Component { const showErrors = areFormErrorsVisible || localSeedErrors.length !== 0; const errors = areFormErrorsVisible ? localSeedErrors.concat(errorsSeeds) : localSeedErrors; - const formattedSeeds = seeds.map(seed => ({ label: seed })); + const formattedSeeds = seeds.map((seed) => ({ label: seed })); return ( <> @@ -353,7 +353,9 @@ export class RemoteClusterForm extends Component { > this.onFieldsChange({ nodeConnections: Number(e.target.value) || null })} + onChange={(e) => + this.onFieldsChange({ nodeConnections: Number(e.target.value) || null }) + } fullWidth /> @@ -398,7 +400,7 @@ export class RemoteClusterForm extends Component { defaultMessage: 'host:port', } )} - onChange={e => this.onFieldsChange({ proxyAddress: e.target.value })} + onChange={(e) => this.onFieldsChange({ proxyAddress: e.target.value })} isInvalid={Boolean(areErrorsVisible && errorProxyAddress)} data-test-subj="remoteClusterFormProxyAddressInput" fullWidth @@ -442,7 +444,7 @@ export class RemoteClusterForm extends Component { > this.onFieldsChange({ serverName: e.target.value })} + onChange={(e) => this.onFieldsChange({ serverName: e.target.value })} isInvalid={Boolean(areErrorsVisible && errorServerName)} fullWidth /> @@ -466,7 +468,7 @@ export class RemoteClusterForm extends Component { > + onChange={(e) => this.onFieldsChange({ proxySocketConnections: Number(e.target.value) || null }) } fullWidth @@ -511,7 +513,7 @@ export class RemoteClusterForm extends Component { } checked={mode === PROXY_MODE} data-test-subj="remoteClusterFormConnectionModeToggle" - onChange={e => + onChange={(e) => this.onFieldsChange({ mode: e.target.checked ? PROXY_MODE : SNIFF_MODE }) } /> @@ -758,7 +760,7 @@ export class RemoteClusterForm extends Component { } else { errorBody = (
      - {cause.map(causeValue => ( + {cause.map((causeValue) => (
    • {causeValue}
    • ))}
    @@ -925,7 +927,7 @@ export class RemoteClusterForm extends Component { this.onFieldsChange({ name: e.target.value })} + onChange={(e) => this.onFieldsChange({ name: e.target.value })} fullWidth disabled={disabledName} data-test-subj="remoteClusterFormNameInput" diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.test.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.test.js index 48a2677c81dbb..a5c2b355863af 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.test.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.test.js @@ -41,7 +41,7 @@ describe('RemoteClusterForm', () => { 'remoteClusterFormSeedNodesFormRow', 'remoteClusterFormSkipUnavailableFormRow', 'remoteClusterFormGlobalError', - ].map(testSubject => { + ].map((testSubject) => { const mountedField = findTestSubject(component, testSubject); return takeMountedSnapshot(mountedField); }); diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.js index f551968bb5fd8..81c9ad7701ea7 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.js @@ -8,7 +8,7 @@ import { validateName } from './validate_name'; describe('validateName', () => { describe('rejects empty input', () => { - [' ', undefined, null].forEach(input => { + [' ', undefined, null].forEach((input) => { test(`'${input}'`, () => { expect(validateName(input)).toMatchSnapshot(); }); @@ -16,7 +16,7 @@ describe('validateName', () => { }); describe('rejects invalid characters', () => { - '!@#$%^&*()+?<> ,.'.split('').forEach(input => { + '!@#$%^&*()+?<> ,.'.split('').forEach((input) => { test(`'${input}'`, () => { expect(validateName(input)).toMatchSnapshot(); }); diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.js index 4fca4bf6e84e1..3f4be89d1cf5a 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.js @@ -8,7 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; export function validateSeeds(seeds, seedInput) { - const seedsHaveBeenCreated = seeds.some(seed => Boolean(seed.trim())); + const seedsHaveBeenCreated = seeds.some((seed) => Boolean(seed.trim())); if (seedsHaveBeenCreated) { return null; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.container.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.container.js index 36d9100f666e2..ee81afbd9e45a 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.container.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.container.js @@ -11,16 +11,16 @@ import { isAddingCluster, getAddClusterError } from '../../store/selectors'; import { addCluster, clearAddClusterErrors } from '../../store/actions'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { isAddingCluster: isAddingCluster(state), addClusterError: getAddClusterError(state), }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { - addCluster: cluster => { + addCluster: (cluster) => { dispatch(addCluster(cluster)); }, clearAddClusterErrors: () => { diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js index 4f9c5dcd38254..b13e833f60b18 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js @@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageContent } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { getRouter, redirect, extractQueryParams } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterPageTitle, RemoteClusterForm } from '../components'; @@ -32,7 +31,7 @@ export class RemoteClusterAdd extends PureComponent { this.props.clearAddClusterErrors(); } - save = clusterConfig => { + save = (clusterConfig) => { this.props.addCluster(clusterConfig); }; @@ -49,7 +48,7 @@ export class RemoteClusterAdd extends PureComponent { const decodedRedirect = decodeURIComponent(redirectUrl); redirect(decodedRedirect); } else { - history.push(CRUD_APP_BASE_PATH); + history.push('/list'); } }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.container.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.container.js index dbdaa3bcd97e5..51bce7b56bbf6 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.container.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.container.js @@ -22,7 +22,7 @@ import { openDetailPanel, } from '../../store/actions'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { isLoading: isLoading(state), cluster: getEditedCluster(state), @@ -31,21 +31,21 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { - startEditingCluster: clusterName => { + startEditingCluster: (clusterName) => { dispatch(startEditingCluster({ clusterName })); }, stopEditingCluster: () => { dispatch(stopEditingCluster()); }, - editCluster: cluster => { + editCluster: (cluster) => { dispatch(editCluster(cluster)); }, clearEditClusterErrors: () => { dispatch(clearEditClusterErrors()); }, - openDetailPanel: clusterName => { + openDetailPanel: (clusterName) => { dispatch(openDetailPanel({ name: clusterName })); }, }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js index d582fc2b0af1b..9018647600b8d 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js @@ -20,8 +20,8 @@ import { EuiTextColor, } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; -import { extractQueryParams, getRouter, getRouterLinkProps, redirect } from '../../services'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { extractQueryParams, getRouter, redirect } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterPageTitle, RemoteClusterForm, ConfiguredByNodeWarning } from '../components'; @@ -70,7 +70,7 @@ export class RemoteClusterEdit extends Component { this.props.stopEditingCluster(); } - save = clusterConfig => { + save = (clusterConfig) => { this.props.editCluster(clusterConfig); }; @@ -89,7 +89,7 @@ export class RemoteClusterEdit extends Component { const decodedRedirect = decodeURIComponent(redirectUrl); redirect(decodedRedirect); } else { - history.push(CRUD_APP_BASE_PATH); + history.push('/list'); openDetailPanel(clusterName); } }; @@ -143,7 +143,7 @@ export class RemoteClusterEdit extends Component { diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.container.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.container.js index 66a814bd7b682..1108902bc08eb 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.container.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.container.js @@ -10,9 +10,9 @@ import { removeClusters } from '../../../../store/actions'; import { RemoveClusterButtonProvider as RemoveClusterButtonProviderComponent } from './remove_cluster_button_provider'; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { - removeClusters: names => { + removeClusters: (names) => { dispatch(removeClusters(names)); }, }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js index b561a7a27b69e..53f4a3b653bd3 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js @@ -22,7 +22,7 @@ export class RemoveClusterButtonProvider extends Component { isModalOpen: false, }; - onMouseOverModal = event => { + onMouseOverModal = (event) => { // This component can sometimes be used inside of an EuiToolTip, in which case mousing over // the modal can trigger the tooltip. Stopping propagation prevents this. event.stopPropagation(); @@ -75,7 +75,7 @@ export class RemoveClusterButtonProvider extends Component { />

      - {clusterNames.map(name => ( + {clusterNames.map((name) => (
    • {name}
    • ))}
    diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.container.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.container.js index 68945f134459e..492a259a0e9c2 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.container.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.container.js @@ -16,7 +16,7 @@ import { import { closeDetailPanel } from '../../../store/actions'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { isOpen: isDetailPanelOpen(state), isLoading: isLoading(state), @@ -25,7 +25,7 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { closeDetailPanel: () => { dispatch(closeDetailPanel()); diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js index 94c5fd6ce7c3f..03be45c760244 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js @@ -30,12 +30,11 @@ import { EuiTextColor, EuiTitle, } from '@elastic/eui'; - -import { CRUD_APP_BASE_PATH } from '../../../constants'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { PROXY_MODE } from '../../../../../common/constants'; -import { getRouterLinkProps } from '../../../services'; import { ConfiguredByNodeWarning } from '../../components'; import { ConnectionStatus, RemoveClusterButtonProvider } from '../components'; +import { getRouter } from '../../../services'; import { proxyModeUrl } from '../../../services/documentation'; export class DetailPanel extends Component { @@ -114,7 +113,8 @@ export class DetailPanel extends Component { renderClusterWithDeprecatedSettingWarning( { hasDeprecatedProxySetting, isConfiguredByNode }, - clusterName + clusterName, + history ) { if (!hasDeprecatedProxySetting) { return null; @@ -156,7 +156,7 @@ export class DetailPanel extends Component { defaultMessage="{editLink} to update the settings." values={{ editLink: ( - + - {seeds.map(seed => ( + {seeds.map((seed) => ( {seed} @@ -448,7 +448,7 @@ export class DetailPanel extends Component { ); } - renderFlyoutBody() { + renderFlyoutBody(history) { const { cluster, clusterName } = this.props; return ( @@ -457,7 +457,7 @@ export class DetailPanel extends Component { {cluster && ( {this.renderClusterConfiguredByNodeWarning(cluster)} - {this.renderClusterWithDeprecatedSettingWarning(cluster, clusterName)} + {this.renderClusterWithDeprecatedSettingWarning(cluster, clusterName, history)} {this.renderCluster(cluster)} )} @@ -465,7 +465,7 @@ export class DetailPanel extends Component { ); } - renderFlyoutFooter() { + renderFlyoutFooter(history) { const { cluster, clusterName, closeDetailPanel } = this.props; return ( @@ -490,7 +490,7 @@ export class DetailPanel extends Component { - {removeCluster => ( + {(removeCluster) => ( - {this.renderFlyoutBody()} + {this.renderFlyoutBody(history)} - {this.renderFlyoutFooter()} + {this.renderFlyoutFooter(history)} ); } diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.container.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.container.js index 00929aa013cd0..0c01fff65cfcd 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.container.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.container.js @@ -24,7 +24,7 @@ import { import { RemoteClusterList as RemoteClusterListView } from './remote_cluster_list'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { clusters: getClustersList(state), isDetailPanelOpen: isDetailPanelOpen(state), @@ -35,7 +35,7 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { loadClusters: () => { dispatch(loadClusters()); @@ -43,7 +43,7 @@ const mapDispatchToProps = dispatch => { refreshClusters: () => { dispatch(refreshClusters()); }, - openDetailPanel: name => { + openDetailPanel: (name) => { dispatch(openDetailPanel({ name })); }, closeDetailPanel: () => { diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js index 207aa8045c011..6d40cbbeb82ae 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js @@ -28,8 +28,8 @@ import { EuiCallOut, } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; -import { getRouterLinkProps, extractQueryParams } from '../../services'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { extractQueryParams } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterTable } from './remote_cluster_table'; @@ -99,7 +99,7 @@ export class RemoteClusterList extends Component { {isAuthorized && ( @@ -185,7 +185,7 @@ export class RemoteClusterList extends Component { } actions={ { +const mapDispatchToProps = (dispatch) => { return { - openDetailPanel: clusterName => { + openDetailPanel: (clusterName) => { dispatch(openDetailPanel({ name: clusterName })); }, }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js index 739c6e26784ef..89e8f2965436a 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js @@ -19,24 +19,24 @@ import { EuiLink, EuiToolTip, } from '@elastic/eui'; - -import { CRUD_APP_BASE_PATH, UIM_SHOW_DETAILS_CLICK } from '../../../constants'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; +import { UIM_SHOW_DETAILS_CLICK } from '../../../constants'; import { PROXY_MODE } from '../../../../../common/constants'; -import { getRouterLinkProps, trackUiMetric, METRIC_TYPE } from '../../../services'; +import { trackUiMetric, METRIC_TYPE, getRouter } from '../../../services'; import { ConnectionStatus, RemoveClusterButtonProvider } from '../components'; const getFilteredClusters = (clusters, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); - return clusters.filter(cluster => { + return clusters.filter((cluster) => { const { name, seeds } = cluster; const normalizedName = name.toLowerCase(); if (normalizedName.toLowerCase().includes(normalizedSearchText)) { return true; } - return seeds.some(seed => seed.includes(normalizedSearchText)); + return seeds.some((seed) => seed.includes(normalizedSearchText)); }); } else { return clusters; @@ -94,6 +94,7 @@ export class RemoteClusterTable extends Component { render() { const { openDetailPanel } = this.props; const { selectedItems, filteredClusters } = this.state; + const { history } = getRouter(); const columns = [ { @@ -189,7 +190,7 @@ export class RemoteClusterTable extends Component { defaultMessage: 'Mode', }), sortable: true, - render: mode => + render: (mode) => mode === PROXY_MODE ? mode : i18n.translate('xpack.remoteClusters.remoteClusterList.table.sniffModeDescription', { @@ -256,7 +257,7 @@ export class RemoteClusterTable extends Component { iconType="pencil" color="primary" isDisabled={isConfiguredByNode} - {...getRouterLinkProps(`${CRUD_APP_BASE_PATH}/edit/${name}`)} + {...reactRouterNavigate(history, `/edit/${name}`)} disabled={isConfiguredByNode} /> @@ -282,7 +283,7 @@ export class RemoteClusterTable extends Component { return ( - {removeCluster => ( + {(removeCluster) => ( name)}> - {removeCluster => ( + {(removeCluster) => ( )} - ) : ( - undefined - ), + ) : undefined, onChange: this.onSearch, box: { incremental: true, @@ -343,7 +342,7 @@ export class RemoteClusterTable extends Component { }; const selection = { - onSelectionChange: selectedItems => this.setState({ selectedItems }), + onSelectionChange: (selectedItems) => this.setState({ selectedItems }), selectable: ({ isConfiguredByNode }) => !isConfiguredByNode, }; diff --git a/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts b/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts index f90a0d3456166..feec7d523e7c1 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts @@ -6,8 +6,6 @@ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../constants'; - interface Breadcrumb { text: string; href?: string; @@ -28,7 +26,7 @@ export function init(setGlobalBreadcrumbs: (breadcrumbs: Breadcrumb[]) => void): text: i18n.translate('xpack.remoteClusters.listBreadcrumbTitle', { defaultMessage: 'Remote Clusters', }), - href: `#${CRUD_APP_BASE_PATH}/list`, + href: `/list`, }, add: { text: i18n.translate('xpack.remoteClusters.addBreadcrumbTitle', { diff --git a/x-pack/plugins/remote_clusters/public/application/services/index.js b/x-pack/plugins/remote_clusters/public/application/services/index.js index 387a04b6e5d8c..ce8d06b6e2278 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/index.js +++ b/x-pack/plugins/remote_clusters/public/application/services/index.js @@ -14,12 +14,6 @@ export { isAddressValid, isPortValid } from './validate_address'; export { extractQueryParams } from './query_params'; -export { - setUserHasLeftApp, - getUserHasLeftApp, - registerRouter, - getRouter, - getRouterLinkProps, -} from './routing'; +export { setUserHasLeftApp, getUserHasLeftApp, registerRouter, getRouter } from './routing'; export { trackUiMetric, METRIC_TYPE } from './ui_metric'; diff --git a/x-pack/plugins/remote_clusters/public/application/services/redirect.ts b/x-pack/plugins/remote_clusters/public/application/services/redirect.ts index 00a97fa74c5ce..1130dbc77fc75 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/redirect.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/redirect.ts @@ -13,5 +13,5 @@ export function init(_navigateToApp: CoreStart['application']['navigateToApp']) } export function redirect(path: string) { - navigateToApp('kibana', { path: `#${path}` }); + navigateToApp('management', { path }); } diff --git a/x-pack/plugins/remote_clusters/public/application/services/routing.js b/x-pack/plugins/remote_clusters/public/application/services/routing.js index 3b78e73c6b3af..c86c9756cfcc8 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/routing.js +++ b/x-pack/plugins/remote_clusters/public/application/services/routing.js @@ -8,8 +8,6 @@ * This file based on guidance from https://github.com/elastic/eui/blob/master/wiki/react-router.md */ -import { createLocation } from 'history'; - let _userHasLeftApp = false; export function setUserHasLeftApp(userHasLeftApp) { @@ -20,11 +18,6 @@ export function getUserHasLeftApp() { return _userHasLeftApp; } -const isModifiedEvent = event => - !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); - -const isLeftClickEvent = event => event.button === 0; - let router; export function registerRouter(reactRouter) { router = reactRouter; @@ -33,35 +26,3 @@ export function registerRouter(reactRouter) { export function getRouter() { return router; } - -/** - * The logic for generating hrefs and onClick handlers from the `to` prop is largely borrowed from - * https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js. - */ -export function getRouterLinkProps(to) { - const location = - typeof to === 'string' ? createLocation(to, null, null, router.history.location) : to; - - const href = router.history.createHref(location); - - const onClick = event => { - if (event.defaultPrevented) { - return; - } - - // If target prop is set (e.g. to "_blank"), let browser handle link. - if (event.target.getAttribute('target')) { - return; - } - - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - } - - // Prevent regular link behavior, which causes a browser refresh. - event.preventDefault(); - router.history.push(location); - }; - - return { href, onClick }; -} diff --git a/x-pack/plugins/remote_clusters/public/application/services/validate_address.js b/x-pack/plugins/remote_clusters/public/application/services/validate_address.js index 7e12b9c06595d..070ca6d692aaa 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/validate_address.js +++ b/x-pack/plugins/remote_clusters/public/application/services/validate_address.js @@ -11,7 +11,7 @@ export function isAddressValid(seedNode) { const portParts = seedNode.split(':'); const parts = portParts[0].split('.'); - const containsInvalidCharacters = parts.some(part => { + const containsInvalidCharacters = parts.some((part) => { if (!part) { // no need to wait for regEx if the part is empty return true; diff --git a/x-pack/plugins/remote_clusters/public/application/services/validate_address.test.js b/x-pack/plugins/remote_clusters/public/application/services/validate_address.test.js index 2551f4fac7908..2bca4ffe4793f 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/validate_address.test.js +++ b/x-pack/plugins/remote_clusters/public/application/services/validate_address.test.js @@ -17,11 +17,13 @@ describe('Validate address', () => { expect(isAddressValid('____')).toBe(false); }); - ['/', '\\', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '=', '+', '?'].forEach(char => { - it(char, () => { - expect(isAddressValid(char)).toBe(false); - }); - }); + ['/', '\\', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '=', '+', '?'].forEach( + (char) => { + it(char, () => { + expect(isAddressValid(char)).toBe(false); + }); + } + ); }); describe('accepts', () => { diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js b/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js index 0b7838f48b137..d57fd37e791a1 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { addCluster as sendAddClusterRequest, getRouter, @@ -22,7 +21,7 @@ import { CLEAR_ADD_CLUSTER_ERRORS, } from '../action_types'; -export const addCluster = cluster => async dispatch => { +export const addCluster = (cluster) => async (dispatch) => { dispatch({ type: ADD_CLUSTER_START, }); @@ -31,7 +30,7 @@ export const addCluster = cluster => async dispatch => { await Promise.all([ sendAddClusterRequest(cluster), // Wait at least half a second to avoid a weird flicker of the saving feedback. - new Promise(resolve => setTimeout(resolve, 500)), + new Promise((resolve) => setTimeout(resolve, 500)), ]); } catch (error) { if (error) { @@ -108,13 +107,13 @@ export const addCluster = cluster => async dispatch => { // This will open the new job in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. history.push({ - pathname: `${CRUD_APP_BASE_PATH}/list`, + pathname: `/list`, search: `?cluster=${cluster.name}`, }); } }; -export const clearAddClusterErrors = () => dispatch => { +export const clearAddClusterErrors = () => (dispatch) => { dispatch({ type: CLEAR_ADD_CLUSTER_ERRORS, }); diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js index ff3ce5cdf9bab..57e8876faca2b 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js @@ -7,7 +7,7 @@ import { extractQueryParams, getRouter } from '../../services'; import { OPEN_DETAIL_PANEL, CLOSE_DETAIL_PANEL } from '../action_types'; -export const openDetailPanel = ({ name }) => dispatch => { +export const openDetailPanel = ({ name }) => (dispatch) => { const { history } = getRouter(); const search = history.location.search; const { cluster: clusterName } = extractQueryParams(search); @@ -25,7 +25,7 @@ export const openDetailPanel = ({ name }) => dispatch => { }); }; -export const closeDetailPanel = () => dispatch => { +export const closeDetailPanel = () => (dispatch) => { dispatch({ type: CLOSE_DETAIL_PANEL, }); diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js index 062704472521e..4fd8faeb7021e 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { toasts, fatalError } from '../../services/notification'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { loadClusters } from './load_clusters'; import { @@ -26,7 +25,7 @@ import { CLEAR_EDIT_CLUSTER_ERRORS, } from '../action_types'; -export const editCluster = cluster => async dispatch => { +export const editCluster = (cluster) => async (dispatch) => { dispatch({ type: EDIT_CLUSTER_SAVE, }); @@ -35,7 +34,7 @@ export const editCluster = cluster => async dispatch => { await Promise.all([ sendEditClusterRequest(cluster), // Wait at least half a second to avoid a weird flicker of the saving feedback. - new Promise(resolve => setTimeout(resolve, 500)), + new Promise((resolve) => setTimeout(resolve, 500)), ]); } catch (error) { if (error) { @@ -95,13 +94,13 @@ export const editCluster = cluster => async dispatch => { // This will open the edited cluster in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. history.push({ - pathname: `${CRUD_APP_BASE_PATH}/list`, + pathname: `/list`, search: `?cluster=${cluster.name}`, }); } }; -export const startEditingCluster = ({ clusterName }) => dispatch => { +export const startEditingCluster = ({ clusterName }) => (dispatch) => { dispatch(loadClusters()); dispatch({ @@ -110,7 +109,7 @@ export const startEditingCluster = ({ clusterName }) => dispatch => { }); }; -export const stopEditingCluster = () => dispatch => { +export const stopEditingCluster = () => (dispatch) => { // Load the clusters to refresh the one we just edited. dispatch(loadClusters()); @@ -119,7 +118,7 @@ export const stopEditingCluster = () => dispatch => { }); }; -export const clearEditClusterErrors = () => dispatch => { +export const clearEditClusterErrors = () => (dispatch) => { dispatch({ type: CLEAR_EDIT_CLUSTER_ERRORS, }); diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js b/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js index 85cd9540a485b..1107c128ad644 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js @@ -10,7 +10,7 @@ import { loadClusters as sendLoadClustersRequest, showApiError } from '../../ser import { LOAD_CLUSTERS_START, LOAD_CLUSTERS_SUCCESS, LOAD_CLUSTERS_FAILURE } from '../action_types'; -export const loadClusters = () => async dispatch => { +export const loadClusters = () => async (dispatch) => { dispatch({ type: LOAD_CLUSTERS_START, }); diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js b/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js index f74b07e9d4131..e77782f8b6945 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js @@ -10,7 +10,7 @@ import { loadClusters as sendLoadClustersRequest, showApiWarning } from '../../s import { REFRESH_CLUSTERS_SUCCESS } from '../action_types'; -export const refreshClusters = () => async dispatch => { +export const refreshClusters = () => async (dispatch) => { let clusters; try { clusters = await sendLoadClustersRequest(); diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/remove_clusters.js b/x-pack/plugins/remote_clusters/public/application/store/actions/remove_clusters.js index 8e22eac8b292b..f9f2263aa2fd8 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/remove_clusters.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/remove_clusters.js @@ -36,7 +36,7 @@ function getErrorTitle(count, name = null) { } } -export const removeClusters = names => async (dispatch, getState) => { +export const removeClusters = (names) => async (dispatch, getState) => { dispatch({ type: REMOVE_CLUSTERS_START, }); @@ -45,13 +45,13 @@ export const removeClusters = names => async (dispatch, getState) => { let errors = []; await Promise.all([ - sendRemoveClusterRequest(names.join(',')).then(response => { + sendRemoveClusterRequest(names.join(',')).then((response) => { ({ itemsDeleted, errors } = response); }), // Wait at least half a second to avoid a weird flicker of the saving feedback (only visible // when requests resolve very quickly). - new Promise(resolve => setTimeout(resolve, 500)), - ]).catch(error => { + new Promise((resolve) => setTimeout(resolve, 500)), + ]).catch((error) => { const errorTitle = getErrorTitle(names.length, names[0]); toasts.addDanger({ title: errorTitle, diff --git a/x-pack/plugins/remote_clusters/public/application/store/middleware/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/store/middleware/detail_panel.js index cbe86fb7065b6..3e9f5b7af7e68 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/middleware/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/store/middleware/detail_panel.js @@ -7,7 +7,7 @@ import { getRouter, getUserHasLeftApp } from '../../services'; import { CLOSE_DETAIL_PANEL } from '../action_types'; -export const detailPanel = () => next => action => { +export const detailPanel = () => (next) => (action) => { const { type } = action; switch (type) { diff --git a/x-pack/plugins/remote_clusters/public/application/store/reducers/clusters.js b/x-pack/plugins/remote_clusters/public/application/store/reducers/clusters.js index 1d88742e5a054..cae90b5a3eab6 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/reducers/clusters.js +++ b/x-pack/plugins/remote_clusters/public/application/store/reducers/clusters.js @@ -22,7 +22,7 @@ const initialState = { // Convert an Array of clusters to an object where // each key is the cluster name -const mapClustersToNames = clusters => +const mapClustersToNames = (clusters) => clusters.reduce( (byName, cluster) => ({ ...byName, @@ -31,7 +31,7 @@ const mapClustersToNames = clusters => {} ); -const getClustersNames = clusters => clusters.map(cluster => cluster.name); +const getClustersNames = (clusters) => clusters.map((cluster) => cluster.name); export function clusters(state = initialState, action) { const { type, payload } = action; @@ -69,8 +69,8 @@ export function clusters(state = initialState, action) { const clustersRemoved = payload; const updatedList = Object.keys(state.byName) - .filter(name => clustersRemoved.indexOf(name) < 0) - .map(name => state.byName[name]); + .filter((name) => clustersRemoved.indexOf(name) < 0) + .map((name) => state.byName[name]); return { ...state, diff --git a/x-pack/plugins/remote_clusters/public/application/store/selectors/index.js b/x-pack/plugins/remote_clusters/public/application/store/selectors/index.js index a6c68a7f06e1e..98feb269158df 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/selectors/index.js +++ b/x-pack/plugins/remote_clusters/public/application/store/selectors/index.js @@ -4,23 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -export const getClustersList = state => state.clusters.asList; -export const getClustersByName = state => state.clusters.byName; +export const getClustersList = (state) => state.clusters.asList; +export const getClustersByName = (state) => state.clusters.byName; export const getClusterByName = (state, name) => getClustersByName(state)[name]; -export const isDetailPanelOpen = state => state.detailPanel.isOpen; -export const getDetailPanelCluster = state => +export const isDetailPanelOpen = (state) => state.detailPanel.isOpen; +export const getDetailPanelCluster = (state) => getClusterByName(state, state.detailPanel.clusterName); -export const getDetailPanelClusterName = state => state.detailPanel.clusterName; +export const getDetailPanelClusterName = (state) => state.detailPanel.clusterName; -export const isLoading = state => state.clusters.isLoading; -export const clusterLoadError = state => state.clusters.clusterLoadError; +export const isLoading = (state) => state.clusters.isLoading; +export const clusterLoadError = (state) => state.clusters.clusterLoadError; -export const isAddingCluster = state => state.addCluster.isAdding; -export const getAddClusterError = state => state.addCluster.error; +export const isAddingCluster = (state) => state.addCluster.isAdding; +export const getAddClusterError = (state) => state.addCluster.error; -export const getEditedCluster = state => getClustersByName(state)[state.editCluster.clusterName]; -export const isEditingCluster = state => state.editCluster.isEditing; -export const getEditClusterError = state => state.editCluster.error; +export const getEditedCluster = (state) => getClustersByName(state)[state.editCluster.clusterName]; +export const isEditingCluster = (state) => state.editCluster.isEditing; +export const getEditClusterError = (state) => state.editCluster.error; -export const isRemovingCluster = state => state.removeCluster.isRemoving; +export const isRemovingCluster = (state) => state.removeCluster.isRemoving; diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index fde8ffa511319..8881db0f9196e 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -41,7 +41,7 @@ export class RemoteClustersUIPlugin defaultMessage: 'Remote Clusters', }), order: 7, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const [core] = await getStartServices(); const { i18n: { Context: i18nContext }, @@ -59,7 +59,7 @@ export class RemoteClustersUIPlugin const isCloudEnabled = Boolean(cloud?.isCloudEnabled); const { renderApp } = await import('./application'); - return renderApp(element, i18nContext, { isCloudEnabled }); + return renderApp(element, i18nContext, { isCloudEnabled }, history); }, }); } diff --git a/x-pack/plugins/remote_clusters/server/plugin.ts b/x-pack/plugins/remote_clusters/server/plugin.ts index a7ca30a6bf96d..9b2d6a0a05385 100644 --- a/x-pack/plugins/remote_clusters/server/plugin.ts +++ b/x-pack/plugins/remote_clusters/server/plugin.ts @@ -53,7 +53,7 @@ export class RemoteClustersServerPlugin registerUpdateRoute(routeDependencies); registerDeleteRoute(routeDependencies); - licensing.license$.subscribe(license => { + licensing.license$.subscribe((license) => { const { state, message } = license.check(PLUGIN.getI18nName(), PLUGIN.minimumLicenseType); const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense) { diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts index 2b8b3bf1a5e59..d28e95834ca0b 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './add_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -27,7 +28,7 @@ describe('ADD remote clusters', () => { { licenseCheckResult = { valid: true }, apiResponses = [], asserts, payload }: TestOptions ) => { test(description, async () => { - const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + const elasticsearchMock = elasticsearchServiceMock.createClusterClient(); const mockRouteDependencies = { router: httpServiceMock.createRouter(), @@ -59,13 +60,8 @@ describe('ADD remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts index 5e0fce82376e0..38f11db18a088 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts @@ -36,7 +36,7 @@ export const register = (deps: RouteDependencies): void => { response ) => { try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const { name } = request.body; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts index 69f84b7ef5e16..d1e3cf89e94d9 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './delete_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -29,7 +30,7 @@ describe('DELETE remote clusters', () => { { licenseCheckResult = { valid: true }, apiResponses = [], asserts, params }: TestOptions ) => { test(description, async () => { - const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + const elasticsearchMock = elasticsearchServiceMock.createClusterClient(); const mockRouteDependencies = { router: httpServiceMock.createRouter(), @@ -61,14 +62,8 @@ describe('DELETE remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; - + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts index 07b0fa3fd9cd7..69760b7477e1b 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts @@ -29,7 +29,7 @@ export const register = (deps: RouteDependencies): void => { response ) => { try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const { nameOrNames } = request.params; const names = nameOrNames.split(','); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts index deaf251c80c17..24e469c9ec9b2 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts @@ -5,11 +5,12 @@ */ import Boom from 'boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './get_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -28,7 +29,7 @@ describe('GET remote clusters', () => { { licenseCheckResult = { valid: true }, apiResponses = [], asserts }: TestOptions ) => { test(description, async () => { - const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + const elasticsearchMock = elasticsearchServiceMock.createClusterClient(); const mockRouteDependencies = { router: httpServiceMock.createRouter(), @@ -59,13 +60,8 @@ describe('GET remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts index 078e1073d1568..4e3fa34ac94c3 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts @@ -16,7 +16,7 @@ import { RouteDependencies } from '../../types'; export const register = (deps: RouteDependencies): void => { const allHandler: RequestHandler = async (ctx, request, response) => { try { - const callAsCurrentUser = await ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const clusterSettings = await callAsCurrentUser('cluster.getSettings'); const transientClusterNames = Object.keys( diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts index b2a443c41fbf9..9669c98e1349e 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './update_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -36,7 +37,7 @@ describe('UPDATE remote clusters', () => { }: TestOptions ) => { test(description, async () => { - const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + const elasticsearchMock = elasticsearchServiceMock.createClusterClient(); const mockRouteDependencies = { router: httpServiceMock.createRouter(), @@ -69,14 +70,8 @@ describe('UPDATE remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; - + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts index 02a63783154df..0666a295de456 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts @@ -42,7 +42,7 @@ export const register = (deps: RouteDependencies): void => { response ) => { try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const { name } = request.params; diff --git a/x-pack/legacy/plugins/reporting/README.md b/x-pack/plugins/reporting/README.md similarity index 100% rename from x-pack/legacy/plugins/reporting/README.md rename to x-pack/plugins/reporting/README.md diff --git a/x-pack/plugins/reporting/common/cancellation_token.ts b/x-pack/plugins/reporting/common/cancellation_token.ts index c03f9ee7328cb..779d7a1d5ed05 100644 --- a/x-pack/plugins/reporting/common/cancellation_token.ts +++ b/x-pack/plugins/reporting/common/cancellation_token.ts @@ -30,7 +30,7 @@ export class CancellationToken { public cancel = () => { this._isCancelled = true; - this._callbacks.forEach(callback => callback()); + this._callbacks.forEach((callback) => callback()); }; public isCancelled() { diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts new file mode 100644 index 0000000000000..48483c79d1af2 --- /dev/null +++ b/x-pack/plugins/reporting/common/constants.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const PLUGIN_ID = 'reporting'; + +export const BROWSER_TYPE = 'chromium'; + +export const JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY = + 'xpack.reporting.jobCompletionNotifications'; + +export const API_BASE_URL = '/api/reporting'; // "Generation URL" from share menu +export const API_BASE_URL_V1 = '/api/reporting/v1'; // +export const API_BASE_GENERATE_V1 = `${API_BASE_URL_V1}/generate`; +export const API_LIST_URL = '/api/reporting/jobs'; +export const API_GENERATE_IMMEDIATE = `${API_BASE_URL_V1}/generate/immediate/csv/saved-object`; + +export const CONTENT_TYPE_CSV = 'text/csv'; +export const CSV_REPORTING_ACTION = 'downloadCsvReport'; +export const CSV_BOM_CHARS = '\ufeff'; +export const CSV_FORMULA_CHARS = ['=', '+', '-', '@']; + +export const WHITELISTED_JOB_CONTENT_TYPES = [ + 'application/json', + 'application/pdf', + CONTENT_TYPE_CSV, + 'image/png', + 'text/plain', +]; + +// See: +// https://github.com/chromium/chromium/blob/3611052c055897e5ebbc5b73ea295092e0c20141/services/network/public/cpp/header_util_unittest.cc#L50 +// For a list of headers that chromium doesn't like +export const KBN_SCREENSHOT_HEADER_BLACKLIST = [ + 'accept-encoding', + 'connection', + 'content-length', + 'content-type', + 'host', + 'referer', + // `Transfer-Encoding` is hop-by-hop header that is meaningful + // only for a single transport-level connection, and shouldn't + // be stored by caches or forwarded by proxies. + 'transfer-encoding', + 'trailer', + 'te', + 'upgrade', + 'keep-alive', +]; + +export const KBN_SCREENSHOT_HEADER_BLACKLIST_STARTS_WITH_PATTERN = ['proxy-']; + +export const UI_SETTINGS_CUSTOM_PDF_LOGO = 'xpackReporting:customPdfLogo'; + +/** + * The type name used within the Monitoring index to publish reporting stats. + * @type {string} + */ +export const KIBANA_REPORTING_TYPE = 'reporting'; + +export const PDF_JOB_TYPE = 'printable_pdf'; +export const PNG_JOB_TYPE = 'PNG'; +export const CSV_JOB_TYPE = 'csv'; +export const CSV_FROM_SAVEDOBJECT_JOB_TYPE = 'csv_from_savedobject'; +export const USES_HEADLESS_JOB_TYPES = [PDF_JOB_TYPE, PNG_JOB_TYPE]; + +export const LICENSE_TYPE_TRIAL = 'trial'; +export const LICENSE_TYPE_BASIC = 'basic'; +export const LICENSE_TYPE_STANDARD = 'standard'; +export const LICENSE_TYPE_GOLD = 'gold'; +export const LICENSE_TYPE_PLATINUM = 'platinum'; +export const LICENSE_TYPE_ENTERPRISE = 'enterprise'; diff --git a/x-pack/legacy/plugins/reporting/common/get_absolute_url.test.ts b/x-pack/plugins/reporting/common/get_absolute_url.test.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/common/get_absolute_url.test.ts rename to x-pack/plugins/reporting/common/get_absolute_url.test.ts diff --git a/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts b/x-pack/plugins/reporting/common/get_absolute_url.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/common/get_absolute_url.ts rename to x-pack/plugins/reporting/common/get_absolute_url.ts diff --git a/x-pack/plugins/reporting/common/index.ts b/x-pack/plugins/reporting/common/index.ts index 36c896fb4f7b8..cda8934fc8bf6 100644 --- a/x-pack/plugins/reporting/common/index.ts +++ b/x-pack/plugins/reporting/common/index.ts @@ -5,3 +5,4 @@ */ export { CancellationToken } from './cancellation_token'; +export { Poller } from './poller'; diff --git a/x-pack/plugins/reporting/common/poller.ts b/x-pack/plugins/reporting/common/poller.ts index 1aeaca001cf1e..2127a876f4a27 100644 --- a/x-pack/plugins/reporting/common/poller.ts +++ b/x-pack/plugins/reporting/common/poller.ts @@ -46,7 +46,7 @@ export class Poller { this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis); }) - .catch(e => { + .catch((e) => { this.errorFunction(e); if (!this._isRunning) { return; diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types.ts index 5b9ddfb1bbdea..2b9e9299852f5 100644 --- a/x-pack/plugins/reporting/common/types.ts +++ b/x-pack/plugins/reporting/common/types.ts @@ -5,7 +5,7 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { ConfigType } from '../server/config'; +export { ReportingConfigType } from '../server/config'; export type JobId = string; export type JobStatus = diff --git a/x-pack/legacy/plugins/reporting/common/validate_urls.test.ts b/x-pack/plugins/reporting/common/validate_urls.test.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/common/validate_urls.test.ts rename to x-pack/plugins/reporting/common/validate_urls.test.ts diff --git a/x-pack/legacy/plugins/reporting/common/validate_urls.ts b/x-pack/plugins/reporting/common/validate_urls.ts similarity index 93% rename from x-pack/legacy/plugins/reporting/common/validate_urls.ts rename to x-pack/plugins/reporting/common/validate_urls.ts index b4ed2b19289a8..58e63a522e609 100644 --- a/x-pack/legacy/plugins/reporting/common/validate_urls.ts +++ b/x-pack/plugins/reporting/common/validate_urls.ts @@ -21,7 +21,7 @@ const isBogusUrl = (url: string) => { }; export const validateUrls = (urls: string[]): void => { - const badUrls = _.filter(urls, url => isBogusUrl(url)); + const badUrls = _.filter(urls, (url) => isBogusUrl(url)); if (badUrls.length) { throw new Error(`Found invalid URL(s), all URLs must be relative: ${badUrls.join(' ')}`); diff --git a/x-pack/plugins/reporting/constants.ts b/x-pack/plugins/reporting/constants.ts index 8079c5b1d9887..772c52dde4a15 100644 --- a/x-pack/plugins/reporting/constants.ts +++ b/x-pack/plugins/reporting/constants.ts @@ -12,7 +12,7 @@ export const API_BASE_URL = '/api/reporting'; export const API_LIST_URL = `${API_BASE_URL}/jobs`; export const API_BASE_GENERATE = `${API_BASE_URL}/generate`; export const API_GENERATE_IMMEDIATE = `${API_BASE_URL}/v1/generate/immediate/csv/saved-object`; -export const REPORTING_MANAGEMENT_HOME = '/app/kibana#/management/kibana/reporting'; +export const REPORTING_MANAGEMENT_HOME = '/app/management/insightsAndAlerting/reporting'; // Statuses export const JOB_STATUS_FAILED = 'failed'; diff --git a/x-pack/plugins/reporting/kibana.json b/x-pack/plugins/reporting/kibana.json index d068711b87c9d..bc1a808d500e0 100644 --- a/x-pack/plugins/reporting/kibana.json +++ b/x-pack/plugins/reporting/kibana.json @@ -3,17 +3,18 @@ "version": "8.0.0", "kibanaVersion": "kibana", "optionalPlugins": [ + "security", "usageCollection" ], "configPath": ["xpack", "reporting"], "requiredPlugins": [ + "data", "home", "management", "licensing", "uiActions", "embeddable", - "share", - "kibanaLegacy" + "share" ], "server": true, "ui": true diff --git a/x-pack/plugins/reporting/public/components/buttons/report_error_button.tsx b/x-pack/plugins/reporting/public/components/buttons/report_error_button.tsx index 1e33cc0188b8c..4eee86cd79ce7 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_error_button.tsx +++ b/x-pack/plugins/reporting/public/components/buttons/report_error_button.tsx @@ -83,7 +83,7 @@ class ReportErrorButtonUi extends Component { } private togglePopover = () => { - this.setState(prevState => { + this.setState((prevState) => { return { isPopoverOpen: !prevState.isPopoverOpen }; }); diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/components/report_listing.tsx index d8f9b7d37cfbf..afcae93a8db16 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/plugins/reporting/public/components/report_listing.tsx @@ -191,13 +191,13 @@ class ReportListingUi extends Component { }; private onSelectionChange = (jobs: Job[]) => { - this.setState(current => ({ ...current, selectedJobs: jobs })); + this.setState((current) => ({ ...current, selectedJobs: jobs })); }; private removeRecord = (record: Job) => { const { jobs } = this.state; - const filtered = jobs.filter(j => j.id !== record.id); - this.setState(current => ({ ...current, jobs: filtered })); + const filtered = jobs.filter((j) => j.id !== record.id); + this.setState((current) => ({ ...current, jobs: filtered })); }; private renderDeleteButton = () => { @@ -266,7 +266,7 @@ class ReportListingUi extends Component { } catch (fetchError) { if (!this.licenseAllowsToShowThisPage()) { this.props.toasts.addDanger(this.state.badLicenseMessage); - this.props.redirect('kibana#/management'); + this.props.redirect('management'); return; } diff --git a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx index cf107fd712876..eddf151167be8 100644 --- a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx +++ b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx @@ -130,7 +130,7 @@ class ReportingPanelContentUi extends Component { - {copy => ( + {(copy) => ( { }); describe('findChangedStatusJobs', () => { - it('finds no changed status jobs from empty', done => { + it('finds no changed status jobs from empty', (done) => { const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock); const findJobs = sh.findChangedStatusJobs([]); - findJobs.subscribe(data => { + findJobs.subscribe((data) => { expect(data).toEqual({ completed: [], failed: [] }); done(); }); }); - it('finds changed status jobs', done => { + it('finds changed status jobs', (done) => { const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock); const findJobs = sh.findChangedStatusJobs([ 'job-source-mock1', @@ -94,7 +94,7 @@ describe('stream handler', () => { 'job-source-mock3', ]); - findJobs.subscribe(data => { + findJobs.subscribe((data) => { expect(data).toMatchSnapshot(); done(); }); @@ -102,7 +102,7 @@ describe('stream handler', () => { }); describe('showNotifications', () => { - it('show success', done => { + it('show success', (done) => { const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock); sh.showNotifications({ completed: [ @@ -123,7 +123,7 @@ describe('stream handler', () => { }); }); - it('show max length warning', done => { + it('show max length warning', (done) => { const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock); sh.showNotifications({ completed: [ @@ -145,7 +145,7 @@ describe('stream handler', () => { }); }); - it('show csv formulas warning', done => { + it('show csv formulas warning', (done) => { const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock); sh.showNotifications({ completed: [ @@ -167,7 +167,7 @@ describe('stream handler', () => { }); }); - it('show failed job toast', done => { + it('show failed job toast', (done) => { const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock); sh.showNotifications({ completed: [], @@ -188,7 +188,7 @@ describe('stream handler', () => { }); }); - it('show multiple toast', done => { + it('show multiple toast', (done) => { const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock); sh.showNotifications({ completed: [ diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 41e5353badfe5..80ba02e17d56d 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -119,7 +119,7 @@ export class ReportingNotifierStreamHandler { return { completed: completedJobs, failed: failedJobs }; }), - catchError(err => { + catchError((err) => { // show connection refused toast this.notifications.toasts.addDanger( getGeneralErrorToast( diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index ecac454ee8c2f..b5613a579f4a6 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -40,7 +40,7 @@ export class GetCsvReportPanelAction implements Action { this.isDownloading = false; this.core = core; - license$.subscribe(license => { + license$.subscribe((license) => { const results = license.check('reporting', 'basic'); const { showLinks } = checkLicense(results); this.canDownloadCSV = showLinks; diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index ac8ea661a21ab..7dd709b956d12 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -26,7 +26,7 @@ import { import { ManagementSectionId, ManagementSetup } from '../../../../src/plugins/management/public'; import { SharePluginSetup } from '../../../../src/plugins/share/public'; import { LicensingPluginSetup } from '../../licensing/public'; -import { ConfigType, JobId, JobStatusBuckets } from '../common/types'; +import { ReportingConfigType, JobId, JobStatusBuckets } from '../common/types'; import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY } from '../constants'; import { getGeneralErrorToast } from './components'; import { ReportListing } from './components/report_listing'; @@ -37,7 +37,7 @@ import { csvReportingProvider } from './share_context_menu/register_csv_reportin import { reportingPDFPNGProvider } from './share_context_menu/register_pdf_png_reporting'; export interface ClientConfigType { - poll: ConfigType['poll']; + poll: ReportingConfigType['poll']; } function getStored(): JobId[] { @@ -111,7 +111,7 @@ export class ReportingPublicPlugin implements Plugin { defaultMessage: 'Manage your reports generated from Discover, Visualize, and Dashboard.', }), icon: 'reportingApp', - path: '/app/kibana#/management/kibana/reporting', + path: '/app/management/kibana/reporting', showOnHomePage: false, category: FeatureCatalogueCategory.ADMIN, }); @@ -120,7 +120,7 @@ export class ReportingPublicPlugin implements Plugin { id: 'reporting', title: this.title, order: 1, - mount: async params => { + mount: async (params) => { const [start] = await getStartServices(); params.setBreadcrumbs([{ text: this.breadcrumbText }]); ReactDOM.render( @@ -167,10 +167,10 @@ export class ReportingPublicPlugin implements Plugin { .pipe( takeUntil(this.stop$), // stop the interval when stop method is called map(() => getStored()), // read all pending job IDs from session storage - filter(storedJobs => storedJobs.length > 0), // stop the pipeline here if there are none pending - mergeMap(storedJobs => streamHandler.findChangedStatusJobs(storedJobs)), // look up the latest status of all pending jobs on the server + filter((storedJobs) => storedJobs.length > 0), // stop the pipeline here if there are none pending + mergeMap((storedJobs) => streamHandler.findChangedStatusJobs(storedJobs)), // look up the latest status of all pending jobs on the server mergeMap(({ completed, failed }) => streamHandler.showNotifications({ completed, failed })), - catchError(err => handleError(notifications, err)) + catchError((err) => handleError(notifications, err)) ) .subscribe(); } diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx index 9d4f475cde79a..ea4ecaa60ab2c 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx @@ -25,7 +25,7 @@ export const csvReportingProvider = ({ apiClient, toasts, license$ }: ReportingP let disabled = true; let hasCSVReporting = false; - license$.subscribe(license => { + license$.subscribe((license) => { const { enableLinks, showLinks, message } = checkLicense(license.check('reporting', 'basic')); toolTipContent = message; diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index 2a955ea398bd4..2343947a6d383 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -31,7 +31,7 @@ export const reportingPDFPNGProvider = ({ let disabled = true; let hasPDFPNGReporting = false; - license$.subscribe(license => { + license$.subscribe((license) => { const { enableLinks, showLinks, message } = checkLicense(license.check('reporting', 'gold')); toolTipContent = message; diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts similarity index 98% rename from x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts rename to x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 3dce8bf4e6819..898b123e976fd 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -9,7 +9,7 @@ import { map, trunc } from 'lodash'; import open from 'opn'; import { ElementHandle, EvaluateFn, Page, Response, SerializableOrJSHandle } from 'puppeteer'; import { parse as parseUrl } from 'url'; -import { ViewZoomWidthHeight } from '../../../../export_types/common/layouts/layout'; +import { ViewZoomWidthHeight } from '../../../export_types/common/layouts/layout'; import { LevelLogger } from '../../../lib'; import { ConditionalHeaders, ElementPosition } from '../../../types'; import { allowRequest, NetworkPolicy } from '../../network_policy'; @@ -178,7 +178,7 @@ export class HeadlessChromiumDriver { `Timed out waiting for the items selected to equal ${toEqual}. Found: ${result}. Context: ${context.context}` ); } - await new Promise(r => setTimeout(r, WAIT_FOR_DELAY_MS)); + await new Promise((r) => setTimeout(r, WAIT_FOR_DELAY_MS)); } } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/index.ts rename to x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/args.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/args.ts rename to x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts new file mode 100644 index 0000000000000..246c605f4bfe6 --- /dev/null +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import del from 'del'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { + Browser, + ConsoleMessage, + LaunchOptions, + Page, + Request as PuppeteerRequest, +} from 'puppeteer'; +import * as Rx from 'rxjs'; +import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber'; +import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators'; +import { BROWSER_TYPE } from '../../../../common/constants'; +import { CaptureConfig } from '../../../../server/types'; +import { LevelLogger } from '../../../lib'; +import { safeChildProcess } from '../../safe_child_process'; +import { HeadlessChromiumDriver } from '../driver'; +import { getChromeLogLocation } from '../paths'; +import { puppeteerLaunch } from '../puppeteer'; +import { args } from './args'; + +type binaryPath = string; +type BrowserConfig = CaptureConfig['browser']['chromium']; +type ViewportConfig = CaptureConfig['viewport']; + +export class HeadlessChromiumDriverFactory { + private binaryPath: binaryPath; + private captureConfig: CaptureConfig; + private browserConfig: BrowserConfig; + private userDataDir: string; + private getChromiumArgs: (viewport: ViewportConfig) => string[]; + + constructor(binaryPath: binaryPath, logger: LevelLogger, captureConfig: CaptureConfig) { + this.binaryPath = binaryPath; + this.captureConfig = captureConfig; + this.browserConfig = captureConfig.browser.chromium; + + this.userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); + this.getChromiumArgs = (viewport: ViewportConfig) => + args({ + userDataDir: this.userDataDir, + viewport, + disableSandbox: this.browserConfig.disableSandbox, + proxy: this.browserConfig.proxy, + }); + } + + type = BROWSER_TYPE; + + test(logger: LevelLogger) { + const chromiumArgs = args({ + userDataDir: this.userDataDir, + viewport: { width: 800, height: 600 }, + disableSandbox: this.browserConfig.disableSandbox, + proxy: this.browserConfig.proxy, + }); + + return puppeteerLaunch({ + userDataDir: this.userDataDir, + executablePath: this.binaryPath, + ignoreHTTPSErrors: true, + args: chromiumArgs, + } as LaunchOptions).catch((error: Error) => { + logger.error( + `The Reporting plugin encountered issues launching Chromium in a self-test. You may have trouble generating reports.` + ); + logger.error(error); + logger.warning(`See Chromium's log output at "${getChromeLogLocation(this.binaryPath)}"`); + return null; + }); + } + + /* + * Return an observable to objects which will drive screenshot capture for a page + */ + createPage( + { viewport, browserTimezone }: { viewport: ViewportConfig; browserTimezone: string }, + pLogger: LevelLogger + ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> { + return Rx.Observable.create(async (observer: InnerSubscriber) => { + const logger = pLogger.clone(['browser-driver']); + logger.info(`Creating browser page driver`); + + const chromiumArgs = this.getChromiumArgs(viewport); + + let browser: Browser; + let page: Page; + try { + browser = await puppeteerLaunch({ + pipe: !this.browserConfig.inspect, + userDataDir: this.userDataDir, + executablePath: this.binaryPath, + ignoreHTTPSErrors: true, + args: chromiumArgs, + env: { + TZ: browserTimezone, + }, + } as LaunchOptions); + + page = await browser.newPage(); + + // Set the default timeout for all navigation methods to the openUrl timeout (30 seconds) + // All waitFor methods have their own timeout config passed in to them + page.setDefaultTimeout(this.captureConfig.timeouts.openUrl); + + logger.debug(`Browser page driver created`); + } catch (err) { + observer.error(new Error(`Error spawning Chromium browser: [${err}]`)); + throw err; + } + + const childProcess = { + async kill() { + await browser.close(); + }, + }; + const { terminate$ } = safeChildProcess(logger, childProcess); + + // this is adding unsubscribe logic to our observer + // so that if our observer unsubscribes, we terminate our child-process + observer.add(() => { + logger.debug(`The browser process observer has unsubscribed. Closing the browser...`); + childProcess.kill(); // ignore async + }); + + // make the observer subscribe to terminate$ + observer.add( + terminate$ + .pipe( + tap((signal) => { + logger.debug(`Termination signal received: ${signal}`); + }), + ignoreElements() + ) + .subscribe(observer) + ); + + // taps the browser log streams and combine them to Kibana logs + this.getBrowserLogger(page, logger).subscribe(); + this.getProcessLogger(browser, logger).subscribe(); + + // HeadlessChromiumDriver: object to "drive" a browser page + const driver = new HeadlessChromiumDriver(page, { + inspect: !!this.browserConfig.inspect, + networkPolicy: this.captureConfig.networkPolicy, + }); + + // Rx.Observable: stream to interrupt page capture + const exit$ = this.getPageExit(browser, page); + + observer.next({ driver, exit$ }); + + // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium + observer.add(() => { + const userDataDir = this.userDataDir; + logger.debug(`deleting chromium user data directory at [${userDataDir}]`); + // the unsubscribe function isn't `async` so we're going to make our best effort at + // deleting the userDataDir and if it fails log an error. + del(userDataDir, { force: true }).catch((error) => { + logger.error(`error deleting user data directory at [${userDataDir}]: [${error}]`); + }); + }); + }); + } + + getBrowserLogger(page: Page, logger: LevelLogger): Rx.Observable { + const consoleMessages$ = Rx.fromEvent(page, 'console').pipe( + map((line) => { + if (line.type() === 'error') { + logger.error(line.text(), ['headless-browser-console']); + } else { + logger.debug(line.text(), [`headless-browser-console:${line.type()}`]); + } + }) + ); + + const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( + map((req) => { + const failure = req.failure && req.failure(); + if (failure) { + logger.warning( + `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.` + ); + } + }) + ); + + return Rx.merge(consoleMessages$, pageRequestFailed$); + } + + getProcessLogger(browser: Browser, logger: LevelLogger): Rx.Observable { + const childProcess = browser.process(); + // NOTE: The browser driver can not observe stdout and stderr of the child process + // Puppeteer doesn't give a handle to the original ChildProcess object + // See https://github.com/GoogleChrome/puppeteer/issues/1292#issuecomment-521470627 + + // just log closing of the process + const processClose$ = Rx.fromEvent(childProcess, 'close').pipe( + tap(() => { + logger.debug('child process closed', ['headless-browser-process']); + }) + ); + + return processClose$; // ideally, this would also merge with observers for stdout and stderr + } + + getPageExit(browser: Browser, page: Page) { + const pageError$ = Rx.fromEvent(page, 'error').pipe( + mergeMap((err) => { + return Rx.throwError( + i18n.translate('xpack.reporting.browsers.chromium.errorDetected', { + defaultMessage: 'Reporting detected an error: {err}', + values: { err: err.toString() }, + }) + ); + }) + ); + + const uncaughtExceptionPageError$ = Rx.fromEvent(page, 'pageerror').pipe( + mergeMap((err) => { + return Rx.throwError( + i18n.translate('xpack.reporting.browsers.chromium.pageErrorDetected', { + defaultMessage: `Reporting detected an error on the page: {err}`, + values: { err: err.toString() }, + }) + ); + }) + ); + + const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe( + mergeMap(() => + Rx.throwError( + new Error( + i18n.translate('xpack.reporting.browsers.chromium.chromiumClosed', { + defaultMessage: `Reporting detected that Chromium has closed.`, + }) + ) + ) + ) + ); + + return Rx.merge(pageError$, uncaughtExceptionPageError$, browserDisconnect$); + } +} diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/chromium/index.ts rename to x-pack/plugins/reporting/server/browsers/chromium/index.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts b/x-pack/plugins/reporting/server/browsers/chromium/paths.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts rename to x-pack/plugins/reporting/server/browsers/chromium/paths.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/puppeteer.ts b/x-pack/plugins/reporting/server/browsers/chromium/puppeteer.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/chromium/puppeteer.ts rename to x-pack/plugins/reporting/server/browsers/chromium/puppeteer.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts b/x-pack/plugins/reporting/server/browsers/create_browser_driver_factory.ts similarity index 89% rename from x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts rename to x-pack/plugins/reporting/server/browsers/create_browser_driver_factory.ts index 7b4407890652c..f3486a48ba7b1 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts +++ b/x-pack/plugins/reporting/server/browsers/create_browser_driver_factory.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { first } from 'rxjs/operators'; import { ReportingConfig } from '../'; import { LevelLogger } from '../lib'; import { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; @@ -19,13 +20,13 @@ export async function createBrowserDriverFactory( const browserConfig = captureConfig.browser.chromium; const browserAutoDownload = captureConfig.browser.autoDownload; const browserType = captureConfig.browser.type; - const dataDir = config.kbnConfig.get('path', 'data'); + const dataDir = await config.kbnConfig.get('path', 'data').pipe(first()).toPromise(); if (browserConfig.disableSandbox) { logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`); } if (browserAutoDownload) { - await ensureBrowserDownloaded(browserType); + await ensureBrowserDownloaded(browserType, logger); } try { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/checksum.ts b/x-pack/plugins/reporting/server/browsers/download/checksum.ts similarity index 84% rename from x-pack/legacy/plugins/reporting/server/browsers/download/checksum.ts rename to x-pack/plugins/reporting/server/browsers/download/checksum.ts index fd68a8e356e10..3191a8968383c 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/checksum.ts +++ b/x-pack/plugins/reporting/server/browsers/download/checksum.ts @@ -11,6 +11,6 @@ import { readableEnd } from './util'; export async function md5(path: string) { const hash = createHash('md5'); - await readableEnd(createReadStream(path).on('data', chunk => hash.update(chunk))); + await readableEnd(createReadStream(path).on('data', (chunk) => hash.update(chunk))); return hash.digest('hex'); } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts b/x-pack/plugins/reporting/server/browsers/download/clean.ts similarity index 80% rename from x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts rename to x-pack/plugins/reporting/server/browsers/download/clean.ts index a2d1fc7f91a29..8558b001e8174 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts +++ b/x-pack/plugins/reporting/server/browsers/download/clean.ts @@ -4,17 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import del from 'del'; import { readdirSync } from 'fs'; import { resolve as resolvePath } from 'path'; - -import del from 'del'; - -import { log, asyncMap } from './util'; +import { LevelLogger } from '../../lib'; +import { asyncMap } from './util'; /** * Delete any file in the `dir` that is not in the expectedPaths */ -export async function clean(dir: string, expectedPaths: string[]) { +export async function clean(dir: string, expectedPaths: string[], logger: LevelLogger) { let filenames: string[]; try { filenames = await readdirSync(dir); @@ -27,10 +26,10 @@ export async function clean(dir: string, expectedPaths: string[]) { throw error; } - await asyncMap(filenames, async filename => { + await asyncMap(filenames, async (filename) => { const path = resolvePath(dir, filename); if (!expectedPaths.includes(path)) { - log(`Deleting unexpected file ${path}`); + logger.warn(`Deleting unexpected file ${path}`); await del(path, { force: true }); } }); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/download.test.ts b/x-pack/plugins/reporting/server/browsers/download/download.test.ts similarity index 78% rename from x-pack/legacy/plugins/reporting/server/browsers/download/download.test.ts rename to x-pack/plugins/reporting/server/browsers/download/download.test.ts index 901fc6ccf9b22..b33dfa721d038 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/download.test.ts +++ b/x-pack/plugins/reporting/server/browsers/download/download.test.ts @@ -5,11 +5,11 @@ */ import { createHash } from 'crypto'; -import { resolve as resolvePath } from 'path'; +import del from 'del'; import { readFileSync } from 'fs'; +import { resolve as resolvePath } from 'path'; import { Readable } from 'stream'; - -import del from 'del'; +import { LevelLogger } from '../../lib'; import { download } from './download'; const TEMP_DIR = resolvePath(__dirname, '__tmp__'); @@ -29,6 +29,12 @@ class ReadableOf extends Readable { jest.mock('axios'); const request: jest.Mock = jest.requireMock('axios').request; +const mockLogger = ({ + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), +} as unknown) as LevelLogger; + test('downloads the url to the path', async () => { const BODY = 'abdcefg'; request.mockImplementationOnce(async () => { @@ -37,22 +43,20 @@ test('downloads the url to the path', async () => { }; }); - await download('url', TEMP_FILE); + await download('url', TEMP_FILE, mockLogger); expect(readFileSync(TEMP_FILE, 'utf8')).toEqual(BODY); }); test('returns the md5 hex hash of the http body', async () => { const BODY = 'foobar'; - const HASH = createHash('md5') - .update(BODY) - .digest('hex'); + const HASH = createHash('md5').update(BODY).digest('hex'); request.mockImplementationOnce(async () => { return { data: new ReadableOf(BODY), }; }); - const returned = await download('url', TEMP_FILE); + const returned = await download('url', TEMP_FILE, mockLogger); expect(returned).toEqual(HASH); }); @@ -61,7 +65,7 @@ test('throws if request emits an error', async () => { throw new Error('foo'); }); - return expect(download('url', TEMP_FILE)).rejects.toThrow('foo'); + return expect(download('url', TEMP_FILE, mockLogger)).rejects.toThrow('foo'); }); afterEach(async () => await del(TEMP_DIR)); diff --git a/x-pack/plugins/reporting/server/browsers/download/download.ts b/x-pack/plugins/reporting/server/browsers/download/download.ts new file mode 100644 index 0000000000000..30b50c32a7402 --- /dev/null +++ b/x-pack/plugins/reporting/server/browsers/download/download.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Axios from 'axios'; +import { createHash } from 'crypto'; +import { closeSync, mkdirSync, openSync, writeSync } from 'fs'; +import { dirname } from 'path'; +import { LevelLogger } from '../../lib'; + +/** + * Download a url and calculate it's checksum + * @param {String} url + * @param {String} path + * @return {Promise} checksum of the downloaded file + */ +export async function download(url: string, path: string, logger: LevelLogger) { + logger.info(`Downloading ${url} to ${path}`); + + const hash = createHash('md5'); + + mkdirSync(dirname(path), { recursive: true }); + const handle = openSync(path, 'w'); + + try { + const resp = await Axios.request({ + url, + method: 'GET', + responseType: 'stream', + }); + + resp.data.on('data', (chunk: Buffer) => { + writeSync(handle, chunk); + hash.update(chunk); + }); + + await new Promise((resolve, reject) => { + resp.data + .on('error', (err: Error) => { + logger.error(err); + reject(err); + }) + .on('end', () => { + logger.info(`Downloaded ${url}`); + resolve(); + }); + }); + } finally { + closeSync(handle); + } + + return hash.digest('hex'); +} diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts new file mode 100644 index 0000000000000..b334510d71947 --- /dev/null +++ b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { existsSync } from 'fs'; +import { resolve as resolvePath } from 'path'; +import { BrowserDownload, chromium } from '../'; +import { BROWSER_TYPE } from '../../../common/constants'; +import { LevelLogger } from '../../lib'; +import { md5 } from './checksum'; +import { clean } from './clean'; +import { download } from './download'; +import { asyncMap } from './util'; + +/** + * Check for the downloaded archive of each requested browser type and + * download them if they are missing or their checksum is invalid + * @param {String} browserType + * @return {Promise} + */ +export async function ensureBrowserDownloaded(browserType = BROWSER_TYPE, logger: LevelLogger) { + await ensureDownloaded([chromium], logger); +} + +/** + * Check for the downloaded archive of each requested browser type and + * download them if they are missing or their checksum is invalid* + * @return {Promise} + */ +export async function ensureAllBrowsersDownloaded(logger: LevelLogger) { + await ensureDownloaded([chromium], logger); +} + +/** + * Clears the unexpected files in the browsers archivesPath + * and ensures that all packages/archives are downloaded and + * that their checksums match the declared value + * @param {BrowserSpec} browsers + * @return {Promise} + */ +async function ensureDownloaded(browsers: BrowserDownload[], logger: LevelLogger) { + await asyncMap(browsers, async (browser) => { + const { archivesPath } = browser.paths; + + await clean( + archivesPath, + browser.paths.packages.map((p) => resolvePath(archivesPath, p.archiveFilename)), + logger + ); + + const invalidChecksums: string[] = []; + await asyncMap(browser.paths.packages, async ({ archiveFilename, archiveChecksum }) => { + const url = `${browser.paths.baseUrl}${archiveFilename}`; + const path = resolvePath(archivesPath, archiveFilename); + + if (existsSync(path) && (await md5(path)) === archiveChecksum) { + logger.info(`Browser archive exists in ${path}`); + return; + } + + const downloadedChecksum = await download(url, path, logger); + if (downloadedChecksum !== archiveChecksum) { + invalidChecksums.push(`${url} => ${path}`); + } + }); + + if (invalidChecksums.length) { + const err = new Error( + `Error downloading browsers, checksums incorrect for:\n - ${invalidChecksums.join( + '\n - ' + )}` + ); + logger.error(err); + throw err; + } + }); +} diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/index.ts b/x-pack/plugins/reporting/server/browsers/download/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/download/index.ts rename to x-pack/plugins/reporting/server/browsers/download/index.ts diff --git a/x-pack/plugins/reporting/server/browsers/download/util.ts b/x-pack/plugins/reporting/server/browsers/download/util.ts new file mode 100644 index 0000000000000..99267664be766 --- /dev/null +++ b/x-pack/plugins/reporting/server/browsers/download/util.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Readable } from 'stream'; + +/** + * Iterate an array asynchronously and in parallel + */ +export function asyncMap(array: T[], asyncFn: (x: T) => T2): Promise { + return Promise.all(array.map(asyncFn)); +} + +/** + * Wait for a readable stream to end + */ +export function readableEnd(stream: Readable) { + return new Promise((resolve, reject) => { + stream.on('error', reject).on('end', resolve); + }); +} diff --git a/x-pack/legacy/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md b/x-pack/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md rename to x-pack/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md diff --git a/x-pack/legacy/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md.zip b/x-pack/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md.zip similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md.zip rename to x-pack/plugins/reporting/server/browsers/extract/__tests__/__fixtures__/file.md.zip diff --git a/x-pack/legacy/plugins/reporting/server/browsers/extract/__tests__/extract.js b/x-pack/plugins/reporting/server/browsers/extract/__tests__/extract.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/extract/__tests__/extract.js rename to x-pack/plugins/reporting/server/browsers/extract/__tests__/extract.js diff --git a/x-pack/legacy/plugins/reporting/server/browsers/extract/extract.js b/x-pack/plugins/reporting/server/browsers/extract/extract.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/extract/extract.js rename to x-pack/plugins/reporting/server/browsers/extract/extract.js diff --git a/x-pack/legacy/plugins/reporting/server/browsers/extract/extract_error.js b/x-pack/plugins/reporting/server/browsers/extract/extract_error.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/extract/extract_error.js rename to x-pack/plugins/reporting/server/browsers/extract/extract_error.js diff --git a/x-pack/legacy/plugins/reporting/server/browsers/extract/index.js b/x-pack/plugins/reporting/server/browsers/extract/index.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/extract/index.js rename to x-pack/plugins/reporting/server/browsers/extract/index.js diff --git a/x-pack/legacy/plugins/reporting/server/browsers/extract/unzip.js b/x-pack/plugins/reporting/server/browsers/extract/unzip.js similarity index 82% rename from x-pack/legacy/plugins/reporting/server/browsers/extract/unzip.js rename to x-pack/plugins/reporting/server/browsers/extract/unzip.js index 80499f8f76f37..d57d04a52f46e 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/extract/unzip.js +++ b/x-pack/plugins/reporting/server/browsers/extract/unzip.js @@ -8,8 +8,8 @@ import extractZip from 'extract-zip'; import { ExtractError } from './extract_error'; export function unzip(filepath, target) { - return new Promise(function(resolve, reject) { - extractZip(filepath, { dir: target }, err => { + return new Promise(function (resolve, reject) { + extractZip(filepath, { dir: target }, (err) => { if (err) { return reject(new ExtractError(err)); } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/index.ts b/x-pack/plugins/reporting/server/browsers/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/index.ts rename to x-pack/plugins/reporting/server/browsers/index.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/install.ts b/x-pack/plugins/reporting/server/browsers/install.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/install.ts rename to x-pack/plugins/reporting/server/browsers/install.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/network_policy.test.ts b/x-pack/plugins/reporting/server/browsers/network_policy.test.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/network_policy.test.ts rename to x-pack/plugins/reporting/server/browsers/network_policy.test.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/network_policy.ts b/x-pack/plugins/reporting/server/browsers/network_policy.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/network_policy.ts rename to x-pack/plugins/reporting/server/browsers/network_policy.ts diff --git a/x-pack/legacy/plugins/reporting/server/browsers/safe_child_process.ts b/x-pack/plugins/reporting/server/browsers/safe_child_process.ts similarity index 96% rename from x-pack/legacy/plugins/reporting/server/browsers/safe_child_process.ts rename to x-pack/plugins/reporting/server/browsers/safe_child_process.ts index 6f86a62c21575..92ef1bc6f8704 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/safe_child_process.ts +++ b/x-pack/plugins/reporting/server/browsers/safe_child_process.ts @@ -25,7 +25,7 @@ export function safeChildProcess( ).pipe(take(1), share()); const ownTerminateMapToKill$ = ownTerminateSignal$.pipe( - tap(signal => { + tap((signal) => { logger.debug(`Kibana process received terminate signal: ${signal}`); }), mapTo('SIGKILL') @@ -33,7 +33,7 @@ export function safeChildProcess( const kibanaForceExit$ = Rx.fromEvent(process as NodeJS.EventEmitter, 'exit').pipe( take(1), - tap(signal => { + tap((signal) => { logger.debug(`Kibana process forcefully exited with signal: ${signal}`); }), mapTo('SIGKILL') @@ -52,7 +52,7 @@ export function safeChildProcess( ownTerminateSignal$.pipe( delay(1), - tap(signal => { + tap((signal) => { logger.debug(`Kibana process terminate signal was: ${signal}. Closing the browser...`); return process.kill(process.pid, signal); }) diff --git a/x-pack/plugins/reporting/server/config/config.ts b/x-pack/plugins/reporting/server/config/config.ts new file mode 100644 index 0000000000000..4142ab6f0ae43 --- /dev/null +++ b/x-pack/plugins/reporting/server/config/config.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { get } from 'lodash'; +import { map } from 'rxjs/operators'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { ReportingConfigType } from './schema'; + +// make config.get() aware of the value type it returns +interface Config { + get(key1: Key1): BaseType[Key1]; + get( + key1: Key1, + key2: Key2 + ): BaseType[Key1][Key2]; + get< + Key1 extends keyof BaseType, + Key2 extends keyof BaseType[Key1], + Key3 extends keyof BaseType[Key1][Key2] + >( + key1: Key1, + key2: Key2, + key3: Key3 + ): BaseType[Key1][Key2][Key3]; + get< + Key1 extends keyof BaseType, + Key2 extends keyof BaseType[Key1], + Key3 extends keyof BaseType[Key1][Key2], + Key4 extends keyof BaseType[Key1][Key2][Key3] + >( + key1: Key1, + key2: Key2, + key3: Key3, + key4: Key4 + ): BaseType[Key1][Key2][Key3][Key4]; +} + +interface KbnServerConfigType { + path: { data: Observable }; + server: { + basePath: string; + host: string; + name: string; + port: number; + protocol: string; + uuid: string; + }; +} + +export interface ReportingConfig extends Config { + kbnConfig: Config; +} + +export const buildConfig = ( + initContext: PluginInitializerContext, + core: CoreSetup, + reportingConfig: ReportingConfigType +): ReportingConfig => { + const { http } = core; + const serverInfo = http.getServerInfo(); + + const kbnConfig = { + path: { + data: initContext.config.legacy.globalConfig$.pipe(map((c) => c.path.data)), + }, + server: { + basePath: core.http.basePath.serverBasePath, + host: serverInfo.host, + name: serverInfo.name, + port: serverInfo.port, + uuid: core.uuid.getInstanceUuid(), + protocol: serverInfo.protocol, + }, + }; + + return { + get: (...keys: string[]) => get(reportingConfig, keys.join('.'), null), // spreading arguments as an array allows the return type to be known by the compiler + kbnConfig: { + get: (...keys: string[]) => get(kbnConfig, keys.join('.'), null), + }, + }; +}; diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts index 3107866be6496..1c4c840cfa312 100644 --- a/x-pack/plugins/reporting/server/config/create_config.test.ts +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -5,9 +5,10 @@ */ import * as Rx from 'rxjs'; -import { CoreSetup, Logger, PluginInitializerContext } from 'src/core/server'; -import { ConfigType as ReportingConfigType } from './schema'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { LevelLogger } from '../lib'; import { createConfig$ } from './create_config'; +import { ReportingConfigType } from './schema'; interface KibanaServer { host?: string; @@ -37,14 +38,14 @@ const makeMockCoreSetup = (serverInfo: KibanaServer): CoreSetup => describe('Reporting server createConfig$', () => { let mockCoreSetup: CoreSetup; let mockInitContext: PluginInitializerContext; - let mockLogger: Logger; + let mockLogger: LevelLogger; beforeEach(() => { mockCoreSetup = makeMockCoreSetup({ host: 'kibanaHost', port: 5601, protocol: 'http' }); mockInitContext = makeMockInitContext({ kibanaServer: {}, }); - mockLogger = ({ warn: jest.fn(), debug: jest.fn() } as unknown) as Logger; + mockLogger = ({ warn: jest.fn(), debug: jest.fn() } as unknown) as LevelLogger; }); afterEach(() => { @@ -52,7 +53,8 @@ describe('Reporting server createConfig$', () => { }); it('creates random encryption key and default config using host, protocol, and port from server info', async () => { - const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + const mockConfig$: any = mockInitContext.config.create(); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch(/\S{32,}/); // random 32 characters expect(result.kibanaServer).toMatchInlineSnapshot(` @@ -73,8 +75,8 @@ describe('Reporting server createConfig$', () => { encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', kibanaServer: {}, }); - const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); - + const mockConfig$: any = mockInitContext.config.create(); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'); expect((mockLogger.warn as any).mock.calls.length).toBe(0); }); @@ -88,7 +90,8 @@ describe('Reporting server createConfig$', () => { protocol: 'httpsa', }, }); - const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + const mockConfig$: any = mockInitContext.config.create(); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result).toMatchInlineSnapshot(` Object { @@ -115,7 +118,8 @@ describe('Reporting server createConfig$', () => { encryptionKey: 'aaaaaaaaaaaaabbbbbbbbbbbbaaaaaaaaa', kibanaServer: { hostname: '0' }, }); - const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + const mockConfig$: any = mockInitContext.config.create(); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.kibanaServer).toMatchInlineSnapshot(` Object { @@ -136,7 +140,8 @@ describe('Reporting server createConfig$', () => { encryptionKey: '888888888888888888888888888888888', capture: { browser: { chromium: { disableSandbox: false } } }, } as ReportingConfigType); - const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + const mockConfig$: any = mockInitContext.config.create(); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false }); expect((mockLogger.warn as any).mock.calls.length).toBe(0); @@ -147,7 +152,8 @@ describe('Reporting server createConfig$', () => { encryptionKey: '888888888888888888888888888888888', capture: { browser: { chromium: { disableSandbox: true } } }, } as ReportingConfigType); - const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + const mockConfig$: any = mockInitContext.config.create(); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true }); expect((mockLogger.warn as any).mock.calls.length).toBe(0); @@ -157,7 +163,8 @@ describe('Reporting server createConfig$', () => { mockInitContext = makeMockInitContext({ encryptionKey: '888888888888888888888888888888888', } as ReportingConfigType); - const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + const mockConfig$: any = mockInitContext.config.create(); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) }); expect((mockLogger.warn as any).mock.calls.length).toBe(0); diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts index 1e6e8bbde5d27..67df7dacc77ab 100644 --- a/x-pack/plugins/reporting/server/config/create_config.ts +++ b/x-pack/plugins/reporting/server/config/create_config.ts @@ -5,13 +5,14 @@ */ import { i18n } from '@kbn/i18n/'; -import { TypeOf } from '@kbn/config-schema'; import crypto from 'crypto'; import { capitalize } from 'lodash'; +import { Observable } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; -import { CoreSetup, Logger, PluginInitializerContext } from 'src/core/server'; +import { CoreSetup } from 'src/core/server'; +import { LevelLogger } from '../lib'; import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; -import { ConfigSchema } from './schema'; +import { ReportingConfigType } from './schema'; /* * Set up dynamic config defaults @@ -19,9 +20,13 @@ import { ConfigSchema } from './schema'; * - xpack.kibanaServer * - xpack.reporting.encryptionKey */ -export function createConfig$(core: CoreSetup, context: PluginInitializerContext, logger: Logger) { - return context.config.create>().pipe( - map(config => { +export function createConfig$( + core: CoreSetup, + config$: Observable, + logger: LevelLogger +) { + return config$.pipe( + map((config) => { // encryption key let encryptionKey = config.encryptionKey; if (encryptionKey === undefined) { @@ -70,7 +75,7 @@ export function createConfig$(core: CoreSetup, context: PluginInitializerContext }, }; }), - mergeMap(async config => { + mergeMap(async (config) => { if (config.capture.browser.chromium.disableSandbox != null) { // disableSandbox was set by user return config; @@ -78,10 +83,7 @@ export function createConfig$(core: CoreSetup, context: PluginInitializerContext // disableSandbox was not set by user, apply default for OS const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled(); - const osName = [os.os, os.dist, os.release] - .filter(Boolean) - .map(capitalize) - .join(' '); + const osName = [os.os, os.dist, os.release].filter(Boolean).map(capitalize).join(' '); logger.debug( i18n.translate('xpack.reporting.serverConfig.osDetected', { diff --git a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts index 307c96bb34909..2404d082649e9 100644 --- a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts +++ b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts @@ -19,7 +19,7 @@ interface TestObject { function defaultTest(os: TestObject, expectedDefault: boolean) { test(`${expectedDefault ? 'disabled' : 'enabled'} on ${JSON.stringify(os)}`, async () => { - (getos as jest.Mock).mockImplementation(cb => cb(null, os)); + (getos as jest.Mock).mockImplementation((cb) => cb(null, os)); const actualDefault = await getDefaultChromiumSandboxDisabled(); expect(actualDefault.disableSandbox).toBe(expectedDefault); }); diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index a0d7618322c65..caa64a7414005 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -5,11 +5,12 @@ */ import { PluginConfigDescriptor } from 'kibana/server'; -import { ConfigSchema, ConfigType } from './schema'; - +import { ConfigSchema, ReportingConfigType } from './schema'; +export { buildConfig } from './config'; export { createConfig$ } from './create_config'; +export { ConfigSchema, ReportingConfigType }; -export const config: PluginConfigDescriptor = { +export const config: PluginConfigDescriptor = { exposeToBrowser: { poll: true }, schema: ConfigSchema, deprecations: ({ unused }) => [ @@ -20,5 +21,3 @@ export const config: PluginConfigDescriptor = { unused('kibanaApp'), ], }; - -export { ConfigSchema, ConfigType }; diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts index 402fddcb5e014..b1234a6ddf0b6 100644 --- a/x-pack/plugins/reporting/server/config/schema.ts +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -162,6 +162,7 @@ const PollSchema = schema.object({ }); export const ConfigSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), kibanaServer: KibanaServerSchema, queue: QueueSchema, capture: CaptureSchema, @@ -172,4 +173,4 @@ export const ConfigSchema = schema.object({ poll: PollSchema, }); -export type ConfigType = TypeOf; +export type ReportingConfigType = TypeOf; diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts new file mode 100644 index 0000000000000..e7786b3b753fb --- /dev/null +++ b/x-pack/plugins/reporting/server/core.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as Rx from 'rxjs'; +import { first, map, mapTo } from 'rxjs/operators'; +import { + BasePath, + ElasticsearchServiceSetup, + IRouter, + KibanaRequest, + SavedObjectsClientContract, + SavedObjectsServiceStart, + UiSettingsServiceStart, +} from 'src/core/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { SecurityPluginSetup } from '../../security/server'; +import { ScreenshotsObservableFn } from '../server/types'; +import { ReportingConfig } from './'; +import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory'; +import { screenshotsObservableFactory } from './export_types/common/lib/screenshots'; +import { checkLicense, getExportTypesRegistry } from './lib'; +import { ESQueueInstance } from './lib/create_queue'; +import { EnqueueJobFn } from './lib/enqueue_job'; + +export interface ReportingInternalSetup { + browserDriverFactory: HeadlessChromiumDriverFactory; + elasticsearch: ElasticsearchServiceSetup; + licensing: LicensingPluginSetup; + basePath: BasePath['get']; + router: IRouter; + security?: SecurityPluginSetup; +} + +interface ReportingInternalStart { + enqueueJob: EnqueueJobFn; + esqueue: ESQueueInstance; + savedObjects: SavedObjectsServiceStart; + uiSettings: UiSettingsServiceStart; +} + +export class ReportingCore { + private pluginSetupDeps?: ReportingInternalSetup; + private pluginStartDeps?: ReportingInternalStart; + private readonly pluginSetup$ = new Rx.ReplaySubject(); + private readonly pluginStart$ = new Rx.ReplaySubject(); + private exportTypesRegistry = getExportTypesRegistry(); + + constructor(private config: ReportingConfig) {} + + public pluginSetup(reportingSetupDeps: ReportingInternalSetup) { + this.pluginSetupDeps = reportingSetupDeps; + this.pluginSetup$.next(reportingSetupDeps); + } + + public pluginStart(reportingStartDeps: ReportingInternalStart) { + this.pluginStart$.next(reportingStartDeps); + } + + public pluginHasStarted(): Promise { + return this.pluginStart$.pipe(first(), mapTo(true)).toPromise(); + } + + /* + * Internal module dependencies + */ + public getExportTypesRegistry() { + return this.exportTypesRegistry; + } + + public async getEsqueue() { + return (await this.getPluginStartDeps()).esqueue; + } + + public async getEnqueueJob() { + return (await this.getPluginStartDeps()).enqueueJob; + } + + public async getLicenseInfo() { + const { licensing } = this.getPluginSetupDeps(); + return await licensing.license$ + .pipe( + map((license) => checkLicense(this.getExportTypesRegistry(), license)), + first() + ) + .toPromise(); + } + + public getConfig(): ReportingConfig { + return this.config; + } + + public getScreenshotsObservable(): ScreenshotsObservableFn { + const { browserDriverFactory } = this.getPluginSetupDeps(); + return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory); + } + + public getPluginSetupDeps() { + if (!this.pluginSetupDeps) { + throw new Error(`"pluginSetupDeps" dependencies haven't initialized yet`); + } + return this.pluginSetupDeps; + } + + /* + * Outside dependencies + */ + + private async getPluginStartDeps() { + if (this.pluginStartDeps) { + return this.pluginStartDeps; + } + return await this.pluginStart$.pipe(first()).toPromise(); + } + + public async getElasticsearchService() { + return this.getPluginSetupDeps().elasticsearch; + } + + public async getSavedObjectsClient(fakeRequest: KibanaRequest) { + const { savedObjects } = await this.getPluginStartDeps(); + return savedObjects.getScopedClient(fakeRequest) as SavedObjectsClientContract; + } + + public async getUiSettingsServiceFactory(savedObjectsClient: SavedObjectsClientContract) { + const { uiSettings: uiSettingsService } = await this.getPluginStartDeps(); + const scopedUiSettingsService = uiSettingsService.asScopedToClient(savedObjectsClient); + return scopedUiSettingsService; + } +} diff --git a/x-pack/legacy/plugins/reporting/export_types/common/constants.ts b/x-pack/plugins/reporting/server/export_types/common/constants.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/constants.ts rename to x-pack/plugins/reporting/server/export_types/common/constants.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.test.ts similarity index 96% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.test.ts index fe3ac16b79fe0..4998d936c9b16 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { cryptoFactory, LevelLogger } from '../../../server/lib'; +import { cryptoFactory, LevelLogger } from '../../../lib'; import { decryptJobHeaders } from './decrypt_job_headers'; const encryptHeaders = async (encryptionKey: string, headers: Record) => { diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts index a13c1fa2a9efb..e5124c80601d7 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { cryptoFactory, LevelLogger } from '../../../server/lib'; +import { cryptoFactory, LevelLogger } from '../../../lib'; interface HasEncryptedHeaders { headers?: string; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts index c09cc8314374e..5d651ad5f8aea 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts @@ -5,9 +5,10 @@ */ import sinon from 'sinon'; -import { ReportingConfig, ReportingCore } from '../../../server'; -import { JobDocPayload } from '../../../server/types'; +import { ReportingConfig } from '../../../'; +import { ReportingCore } from '../../../core'; import { createMockReportingCore } from '../../../test_helpers'; +import { JobDocPayload } from '../../../types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; import { getConditionalHeaders, getCustomLogo } from './index'; @@ -134,10 +135,7 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav describe('config formatting', () => { test(`lowercases server.host`, async () => { - const mockConfigGet = sinon - .stub() - .withArgs('server', 'host') - .returns('COOL-HOSTNAME'); + const mockConfigGet = sinon.stub().withArgs('server', 'host').returns('COOL-HOSTNAME'); mockConfig = getMockConfig(mockConfigGet); const conditionalHeaders = await getConditionalHeaders({ diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts similarity index 89% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts index 808d5db5c57d5..6854f678aa975 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReportingConfig } from '../../../server'; -import { ConditionalHeaders } from '../../../server/types'; +import { ReportingConfig } from '../../../'; +import { ConditionalHeaders } from '../../../types'; export const getConditionalHeaders = ({ config, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts index 2cbde69c81316..bd6eb4644d87f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReportingCore } from '../../../server'; +import { ReportingCore } from '../../../core'; import { createMockReportingCore } from '../../../test_helpers'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; import { getConditionalHeaders, getCustomLogo } from './index'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts similarity index 87% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts index 777de317af41e..85d1272fc22ce 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../common/constants'; -import { ReportingConfig, ReportingCore } from '../../../server'; -import { ConditionalHeaders } from '../../../server/types'; +import { ReportingConfig, ReportingCore } from '../../../'; +import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../../common/constants'; +import { ConditionalHeaders } from '../../../types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; // Logo is PDF only export const getCustomLogo = async ({ diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts similarity index 99% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts index 5f55617724ff6..cacea41477ea4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReportingConfig } from '../../../server'; +import { ReportingConfig } from '../../../'; import { JobDocPayloadPNG } from '../../png/types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; import { getFullUrls } from './get_full_urls'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts similarity index 92% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts index 59cb7ce14ca2d..bcd7f122748cb 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts @@ -7,12 +7,12 @@ import { format as urlFormat, parse as urlParse, - UrlWithStringQuery, UrlWithParsedQuery, + UrlWithStringQuery, } from 'url'; -import { getAbsoluteUrlFactory } from '../../../common/get_absolute_url'; -import { validateUrls } from '../../../common/validate_urls'; -import { ReportingConfig } from '../../../server'; +import { ReportingConfig } from '../../..'; +import { getAbsoluteUrlFactory } from '../../../../common/get_absolute_url'; +import { validateUrls } from '../../../../common/validate_urls'; import { JobDocPayloadPNG } from '../../png/types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; @@ -58,7 +58,7 @@ export function getFullUrls({ validateUrls(relativeUrls); - const urls = relativeUrls.map(relativeUrl => { + const urls = relativeUrls.map((relativeUrl) => { const parsedRelative: UrlWithStringQuery = urlParse(relativeUrl); const jobUrl = getAbsoluteUrl({ basePath: job.basePath, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/index.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/index.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.test.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.test.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts rename to x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts index 2fbfd868674f6..5147881a980ea 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts @@ -7,7 +7,7 @@ import { omit } from 'lodash'; import { KBN_SCREENSHOT_HEADER_BLACKLIST, KBN_SCREENSHOT_HEADER_BLACKLIST_STARTS_WITH_PATTERN, -} from '../../../common/constants'; +} from '../../../../common/constants'; export const omitBlacklistedHeaders = ({ job, @@ -21,7 +21,7 @@ export const omitBlacklistedHeaders = ({ (_value, header: string) => header && (KBN_SCREENSHOT_HEADER_BLACKLIST.includes(header) || - KBN_SCREENSHOT_HEADER_BLACKLIST_STARTS_WITH_PATTERN.some(pattern => + KBN_SCREENSHOT_HEADER_BLACKLIST_STARTS_WITH_PATTERN.some((pattern) => header?.startsWith(pattern) )) ); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/create_layout.ts b/x-pack/plugins/reporting/server/export_types/common/layouts/create_layout.ts similarity index 93% rename from x-pack/legacy/plugins/reporting/export_types/common/layouts/create_layout.ts rename to x-pack/plugins/reporting/server/export_types/common/layouts/create_layout.ts index d33760fcb4f89..216a59d41cec0 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/create_layout.ts +++ b/x-pack/plugins/reporting/server/export_types/common/layouts/create_layout.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CaptureConfig } from '../../../server/types'; +import { CaptureConfig } from '../../../types'; import { LayoutParams, LayoutTypes } from './'; import { Layout } from './layout'; import { PreserveLayout } from './preserve_layout'; diff --git a/x-pack/plugins/reporting/server/export_types/common/layouts/index.ts b/x-pack/plugins/reporting/server/export_types/common/layouts/index.ts new file mode 100644 index 0000000000000..23e4c095afe61 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/layouts/index.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HeadlessChromiumDriver } from '../../../browsers'; +import { LevelLogger } from '../../../lib'; +import { Layout } from './layout'; + +export { createLayout } from './create_layout'; +export { Layout } from './layout'; +export { PreserveLayout } from './preserve_layout'; +export { PrintLayout } from './print_layout'; + +export const LayoutTypes = { + PRESERVE_LAYOUT: 'preserve_layout', + PRINT: 'print', +}; + +export const getDefaultLayoutSelectors = (): LayoutSelectorDictionary => ({ + screenshot: '[data-shared-items-container]', + renderComplete: '[data-shared-item]', + itemsCountAttribute: 'data-shared-items-count', + timefilterDurationAttribute: 'data-shared-timefilter-duration', +}); + +export interface PageSizeParams { + pageMarginTop: number; + pageMarginBottom: number; + pageMarginWidth: number; + tableBorderWidth: number; + headingHeight: number; + subheadingHeight: number; +} + +export interface LayoutSelectorDictionary { + screenshot: string; + renderComplete: string; + itemsCountAttribute: string; + timefilterDurationAttribute: string; +} + +export interface PdfImageSize { + width: number; + height?: number; +} + +export interface Size { + width: number; + height: number; +} + +export interface LayoutParams { + id: string; + dimensions: Size; +} + +interface LayoutSelectors { + // Fields that are not part of Layout: the instances + // independently implement these fields on their own + selectors: LayoutSelectorDictionary; + positionElements?: (browser: HeadlessChromiumDriver, logger: LevelLogger) => Promise; +} + +export type LayoutInstance = Layout & LayoutSelectors & Size; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts b/x-pack/plugins/reporting/server/export_types/common/layouts/layout.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts rename to x-pack/plugins/reporting/server/export_types/common/layouts/layout.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/plugins/reporting/server/export_types/common/layouts/preserve_layout.css similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css rename to x-pack/plugins/reporting/server/export_types/common/layouts/preserve_layout.css diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.ts b/x-pack/plugins/reporting/server/export_types/common/layouts/preserve_layout.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.ts rename to x-pack/plugins/reporting/server/export_types/common/layouts/preserve_layout.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css b/x-pack/plugins/reporting/server/export_types/common/layouts/print.css similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css rename to x-pack/plugins/reporting/server/export_types/common/layouts/print.css diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts b/x-pack/plugins/reporting/server/export_types/common/layouts/print_layout.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts rename to x-pack/plugins/reporting/server/export_types/common/layouts/print_layout.ts index 759f07a33e2b6..30c83771aa3c9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts +++ b/x-pack/plugins/reporting/server/export_types/common/layouts/print_layout.ts @@ -6,9 +6,9 @@ import path from 'path'; import { EvaluateFn, SerializableOrJSHandle } from 'puppeteer'; -import { LevelLogger } from '../../../server/lib'; -import { HeadlessChromiumDriver } from '../../../server/browsers'; -import { CaptureConfig } from '../../../server/types'; +import { CaptureConfig } from '../../../types'; +import { HeadlessChromiumDriver } from '../../../browsers'; +import { LevelLogger } from '../../../lib'; import { getDefaultLayoutSelectors, LayoutSelectorDictionary, Size, LayoutTypes } from './'; import { Layout } from './layout'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/constants.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/constants.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/constants.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/constants.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_element_position_data.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_element_position_data.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_element_position_data.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_element_position_data.ts index d02e852a3c1b6..140d76f8d1cd6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_element_position_data.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_element_position_data.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { AttributesMap, ElementsPositionAndAttribute } from '../../../../server/types'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; +import { AttributesMap, ElementsPositionAndAttribute } from '../../../../types'; import { LayoutInstance } from '../../layouts'; import { CONTEXT_ELEMENTATTRIBUTES } from './constants'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_number_of_items.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_number_of_items.ts similarity index 93% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_number_of_items.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_number_of_items.ts index 9e446f499ab3a..42eb91ecba830 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_number_of_items.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_number_of_items.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; -import { CaptureConfig } from '../../../../server/types'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; +import { CaptureConfig } from '../../../../types'; import { LayoutInstance } from '../../layouts'; import { CONTEXT_GETNUMBEROFITEMS, CONTEXT_READMETADATA } from './constants'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_screenshots.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_screenshots.ts similarity index 89% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_screenshots.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_screenshots.ts index 578a4dd0b975c..05c315b8341a3 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_screenshots.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_screenshots.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { ElementsPositionAndAttribute, Screenshot } from '../../../../server/types'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; +import { ElementsPositionAndAttribute, Screenshot } from '../../../../types'; export const getScreenshots = async ( browser: HeadlessChromiumDriver, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_time_range.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_time_range.ts similarity index 88% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_time_range.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_time_range.ts index 7bdb38298c383..ba68a5fec4e4c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_time_range.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/get_time_range.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; import { LayoutInstance } from '../../layouts'; import { CONTEXT_GETTIMERANGE } from './constants'; @@ -19,7 +19,7 @@ export const getTimeRange = async ( const timeRange = await browser.evaluate( { - fn: durationAttribute => { + fn: (durationAttribute) => { const durationElement = document.querySelector(`[${durationAttribute}]`); if (!durationElement) { diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/index.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/inject_css.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/inject_css.ts similarity index 90% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/inject_css.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/inject_css.ts index dae6f3cc6350f..d72afacc1bef3 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/inject_css.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/inject_css.ts @@ -7,8 +7,8 @@ import { i18n } from '@kbn/i18n'; import fs from 'fs'; import { promisify } from 'util'; -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; import { Layout } from '../../layouts/layout'; import { CONTEXT_INJECTCSS } from './constants'; @@ -31,7 +31,7 @@ export const injectCustomCss = async ( try { await browser.evaluate( { - fn: css => { + fn: (css) => { const node = document.createElement('style'); node.type = 'text/css'; node.innerHTML = css; // eslint-disable-line no-unsanitized/property diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.test.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.test.ts index cc8b438310430..2ddb4a5d5b994 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../../server/browsers/chromium/puppeteer', () => ({ +jest.mock('../../../../browsers/chromium/puppeteer', () => ({ puppeteerLaunch: () => ({ // Fixme needs event emitters newPage: () => ({ @@ -18,14 +18,10 @@ jest.mock('../../../../server/browsers/chromium/puppeteer', () => ({ import * as Rx from 'rxjs'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { loggingServiceMock } from '../../../../../../../../src/core/server/mocks'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; -import { LevelLogger } from '../../../../server/lib'; -import { - CaptureConfig, - ConditionalHeaders, - ElementsPositionAndAttribute, -} from '../../../../server/types'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger } from '../../../../lib'; import { createMockBrowserDriverFactory, createMockLayoutInstance } from '../../../../test_helpers'; +import { CaptureConfig, ConditionalHeaders, ElementsPositionAndAttribute } from '../../../../types'; import * as contexts from './constants'; import { screenshotsObservableFactory } from './observable'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.ts index de27d5ad30014..028bff4aaa5ee 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.ts @@ -16,14 +16,14 @@ import { tap, toArray, } from 'rxjs/operators'; -import { HeadlessChromiumDriverFactory } from '../../../../server/browsers'; +import { HeadlessChromiumDriverFactory } from '../../../../browsers'; import { CaptureConfig, ElementsPositionAndAttribute, ScreenshotObservableOpts, ScreenshotResults, ScreenshotsObservableFn, -} from '../../../../server/types'; +} from '../../../../types'; import { DEFAULT_PAGELOAD_SELECTOR } from '../../constants'; import { getElementPositionAndAttributes } from './get_element_position_data'; import { getNumberOfItems } from './get_number_of_items'; @@ -86,7 +86,7 @@ export function screenshotsObservableFactory( ); }), mergeMap(() => getNumberOfItems(captureConfig, driver, layout, logger)), - mergeMap(async itemsCount => { + mergeMap(async (itemsCount) => { const viewport = layout.getViewport(itemsCount) || getDefaultViewPort(); await Promise.all([ driver.setViewport(viewport, logger), @@ -116,7 +116,7 @@ export function screenshotsObservableFactory( timeRange, })); }), - catchError(err => { + catchError((err) => { logger.error(err); return Rx.of({ elementsPositionAndAttributes: null, timeRange: null, error: err }); }) diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/open_url.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/open_url.ts similarity index 83% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/open_url.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/open_url.ts index 3cf962b8178fd..bd7e8c508c118 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/open_url.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/open_url.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { CaptureConfig, ConditionalHeaders } from '../../../../server/types'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; +import { CaptureConfig, ConditionalHeaders } from '../../../../types'; export const openUrl = async ( captureConfig: CaptureConfig, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/wait_for_render.ts similarity index 87% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/wait_for_render.ts index 0e02fa2dacfad..b6519e914430a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/wait_for_render.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; -import { CaptureConfig } from '../../../../server/types'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; +import { CaptureConfig } from '../../../../types'; import { LayoutInstance } from '../../layouts'; import { CONTEXT_WAITFORRENDER } from './constants'; @@ -35,13 +35,13 @@ export const waitForRenderComplete = async ( const renderedTasks = []; function waitForRender(visualization: Element) { - return new Promise(resolve => { + return new Promise((resolve) => { visualization.addEventListener('renderComplete', () => resolve()); }); } function waitForRenderDelay() { - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(resolve, visLoadDelay); }); } @@ -63,7 +63,7 @@ export const waitForRenderComplete = async ( // capture the first visualization before it was actually in the DOM. // Note: 100 proved too short, see https://github.com/elastic/kibana/issues/22581, // bumping to 250. - const hackyWaitForVisualizations = () => new Promise(r => setTimeout(r, 250)); + const hackyWaitForVisualizations = () => new Promise((r) => setTimeout(r, 250)); return Promise.all(renderedTasks).then(hackyWaitForVisualizations); }, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_visualizations.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/wait_for_visualizations.ts similarity index 91% rename from x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_visualizations.ts rename to x-pack/plugins/reporting/server/export_types/common/lib/screenshots/wait_for_visualizations.ts index ff84d06956dbc..75a7b6516473c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_visualizations.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/wait_for_visualizations.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../../../../server/lib'; -import { HeadlessChromiumDriver } from '../../../../server/browsers'; -import { CaptureConfig } from '../../../../server/types'; +import { HeadlessChromiumDriver } from '../../../../browsers'; +import { LevelLogger, startTrace } from '../../../../lib'; +import { CaptureConfig } from '../../../../types'; import { LayoutInstance } from '../../layouts'; import { CONTEXT_WAITFORELEMENTSTOBEINDOM } from './constants'; diff --git a/x-pack/plugins/reporting/server/export_types/csv/index.ts b/x-pack/plugins/reporting/server/export_types/csv/index.ts new file mode 100644 index 0000000000000..8642a6d5758a8 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/index.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + LICENSE_TYPE_BASIC, + LICENSE_TYPE_ENTERPRISE, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_TRIAL, +} from '../../../common/constants'; +import { CSV_JOB_TYPE as jobType } from '../../../constants'; +import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types'; +import { metadata } from './metadata'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { JobDocPayloadDiscoverCsv, JobParamsDiscoverCsv } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsDiscoverCsv, + ESQueueCreateJobFn, + JobDocPayloadDiscoverCsv, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentExtension: 'csv', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_ENTERPRISE, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/metadata.ts b/x-pack/plugins/reporting/server/export_types/csv/metadata.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/csv/metadata.ts rename to x-pack/plugins/reporting/server/export_types/csv/metadata.ts diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts new file mode 100644 index 0000000000000..acf7f0505a735 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { ReportingCore } from '../../../'; +import { cryptoFactory } from '../../../lib'; +import { CreateJobFactory, ESQueueCreateJobFn } from '../../../types'; +import { JobParamsDiscoverCsv } from '../types'; + +export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore) { + const config = reporting.getConfig(); + const crypto = cryptoFactory(config.get('encryptionKey')); + const setupDeps = reporting.getPluginSetupDeps(); + + return async function createJob( + jobParams: JobParamsDiscoverCsv, + context: RequestHandlerContext, + request: KibanaRequest + ) { + const serializedEncryptedHeaders = await crypto.encrypt(request.headers); + + const savedObjectsClient = context.core.savedObjects.client; + const indexPatternSavedObject = await savedObjectsClient.get( + 'index-pattern', + jobParams.indexPatternId! + ); + + return { + headers: serializedEncryptedHeaders, + indexPatternSavedObject, + basePath: setupDeps.basePath(request), + ...jobParams, + }; + }; +}; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts similarity index 82% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts index d427600817d69..ddcf94079ade4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts @@ -8,16 +8,20 @@ import nodeCrypto from '@elastic/node-crypto'; // @ts-ignore import Puid from 'puid'; import sinon from 'sinon'; -import { fieldFormats } from '../../../../../../../src/plugins/data/server'; -import { CancellationToken } from '../../../../../../plugins/reporting/common'; -import { CSV_BOM_CHARS } from '../../../common/constants'; -import { LevelLogger } from '../../../server/lib'; -import { setFieldFormats } from '../../../server/services'; +import { fieldFormats, UI_SETTINGS } from '../../../../../../../src/plugins/data/server'; +import { CancellationToken } from '../../../../common'; +import { CSV_BOM_CHARS } from '../../../../common/constants'; +import { LevelLogger } from '../../../lib'; +import { setFieldFormats } from '../../../services'; import { createMockReportingCore } from '../../../test_helpers'; import { JobDocPayloadDiscoverCsv } from '../types'; import { executeJobFactory } from './execute_job'; +import { + CSV_SEPARATOR_SETTING, + CSV_QUOTE_VALUES_SETTING, +} from '../../../../../../../src/plugins/share/server'; -const delay = (ms: number) => new Promise(resolve => setTimeout(() => resolve(), ms)); +const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms)); const puid = new Puid(); const getRandomScrollId = () => { @@ -26,7 +30,7 @@ const getRandomScrollId = () => { const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadDiscoverCsv; -describe('CSV Execute Job', function() { +describe('CSV Execute Job', function () { const encryptionKey = 'testEncryptionKey'; const headers = { sid: 'test', @@ -45,25 +49,27 @@ describe('CSV Execute Job', function() { let clusterStub: any; let configGetStub: any; let mockReportingConfig: any; - let mockReportingPlugin: any; + let mockReportingCore: any; let callAsCurrentUserStub: any; let cancellationToken: any; const mockElasticsearch = { - dataClient: { - asScoped: () => clusterStub, + legacy: { + client: { + asScoped: () => clusterStub, + }, }, }; const mockUiSettingsClient = { get: sinon.stub(), }; - beforeAll(async function() { + beforeAll(async function () { const crypto = nodeCrypto({ encryptionKey }); encryptedHeaders = await crypto.encrypt(headers); }); - beforeEach(async function() { + beforeEach(async function () { configGetStub = sinon.stub(); configGetStub.withArgs('index').returns('.reporting-foo-test'); configGetStub.withArgs('encryptionKey').returns(encryptionKey); @@ -71,9 +77,10 @@ describe('CSV Execute Job', function() { configGetStub.withArgs('csv', 'scroll').returns({}); mockReportingConfig = { get: configGetStub, kbnConfig: { get: configGetStub } }; - mockReportingPlugin = await createMockReportingCore(mockReportingConfig); - mockReportingPlugin.getUiSettingsServiceFactory = () => Promise.resolve(mockUiSettingsClient); - mockReportingPlugin.getElasticsearchService = () => Promise.resolve(mockElasticsearch); + mockReportingCore = await createMockReportingCore(mockReportingConfig); + mockReportingCore.getUiSettingsServiceFactory = () => Promise.resolve(mockUiSettingsClient); + mockReportingCore.getElasticsearchService = () => Promise.resolve(mockElasticsearch); + mockReportingCore.config = mockReportingConfig; cancellationToken = new CancellationToken(); @@ -91,19 +98,19 @@ describe('CSV Execute Job', function() { .stub(clusterStub, 'callAsCurrentUser') .resolves(defaultElasticsearchResponse); - mockUiSettingsClient.get.withArgs('csv:separator').returns(','); - mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true); + mockUiSettingsClient.get.withArgs(CSV_SEPARATOR_SETTING).returns(','); + mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(true); setFieldFormats({ fieldFormatServiceFactory() { const uiConfigMock = {}; - (uiConfigMock as any)['format:defaultTypeMap'] = { + (uiConfigMock as any)[UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP] = { _default_: { id: 'string', params: {} }, }; const fieldFormatsRegistry = new fieldFormats.FieldFormatsRegistry(); - fieldFormatsRegistry.init(key => (uiConfigMock as any)[key], {}, [ + fieldFormatsRegistry.init((key) => (uiConfigMock as any)[key], {}, [ fieldFormats.StringFormat, ]); @@ -112,9 +119,9 @@ describe('CSV Execute Job', function() { }); }); - describe('basic Elasticsearch call behavior', function() { - it('should decrypt encrypted headers and pass to callAsCurrentUser', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + describe('basic Elasticsearch call behavior', function () { + it('should decrypt encrypted headers and pass to callAsCurrentUser', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -128,13 +135,13 @@ describe('CSV Execute Job', function() { expect(callAsCurrentUserStub.firstCall.args[0]).toEqual('search'); }); - it('should pass the index and body to execute the initial search', async function() { + it('should pass the index and body to execute the initial search', async function () { const index = 'index'; const body = { testBody: true, }; - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const job = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -152,7 +159,7 @@ describe('CSV Execute Job', function() { expect(searchCall.args[1].body).toBe(body); }); - it('should pass the scrollId from the initial search to the subsequent scroll', async function() { + it('should pass the scrollId from the initial search to the subsequent scroll', async function () { const scrollId = getRandomScrollId(); callAsCurrentUserStub.onFirstCall().resolves({ hits: { @@ -161,7 +168,7 @@ describe('CSV Execute Job', function() { _scroll_id: scrollId, }); callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -178,8 +185,8 @@ describe('CSV Execute Job', function() { expect(scrollCall.args[1].scrollId).toBe(scrollId); }); - it('should not execute scroll if there are no hits from the search', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should not execute scroll if there are no hits from the search', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -199,7 +206,7 @@ describe('CSV Execute Job', function() { expect(clearScrollCall.args[0]).toBe('clearScroll'); }); - it('should stop executing scroll if there are no hits', async function() { + it('should stop executing scroll if there are no hits', async function () { callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], @@ -213,7 +220,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -236,7 +243,7 @@ describe('CSV Execute Job', function() { expect(clearScroll.args[0]).toBe('clearScroll'); }); - it('should call clearScroll with scrollId when there are no more hits', async function() { + it('should call clearScroll with scrollId when there are no more hits', async function () { const lastScrollId = getRandomScrollId(); callAsCurrentUserStub.onFirstCall().resolves({ hits: { @@ -252,7 +259,7 @@ describe('CSV Execute Job', function() { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -268,7 +275,7 @@ describe('CSV Execute Job', function() { expect(lastCall.args[1].scrollId).toEqual([lastScrollId]); }); - it('calls clearScroll when there is an error iterating the hits', async function() { + it('calls clearScroll when there is an error iterating the hits', async function () { const lastScrollId = getRandomScrollId(); callAsCurrentUserStub.onFirstCall().resolves({ hits: { @@ -284,7 +291,7 @@ describe('CSV Execute Job', function() { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -302,7 +309,7 @@ describe('CSV Execute Job', function() { }); describe('Warning when cells have formulas', () => { - it('returns `csv_contains_formulas` when cells contain formulas', async function() { + it('returns `csv_contains_formulas` when cells contain formulas', async function () { configGetStub.withArgs('csv', 'checkForFormulas').returns(true); callAsCurrentUserStub.onFirstCall().returns({ hits: { @@ -311,7 +318,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -327,7 +334,7 @@ describe('CSV Execute Job', function() { expect(csvContainsFormulas).toEqual(true); }); - it('returns warnings when headings contain formulas', async function() { + it('returns warnings when headings contain formulas', async function () { configGetStub.withArgs('csv', 'checkForFormulas').returns(true); callAsCurrentUserStub.onFirstCall().returns({ hits: { @@ -336,7 +343,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -352,7 +359,7 @@ describe('CSV Execute Job', function() { expect(csvContainsFormulas).toEqual(true); }); - it('returns no warnings when cells have no formulas', async function() { + it('returns no warnings when cells have no formulas', async function () { configGetStub.withArgs('csv', 'checkForFormulas').returns(true); configGetStub.withArgs('csv', 'escapeFormulaValues').returns(false); callAsCurrentUserStub.onFirstCall().returns({ @@ -362,7 +369,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -378,7 +385,7 @@ describe('CSV Execute Job', function() { expect(csvContainsFormulas).toEqual(false); }); - it('returns no warnings when cells have formulas but are escaped', async function() { + it('returns no warnings when cells have formulas but are escaped', async function () { configGetStub.withArgs('csv', 'checkForFormulas').returns(true); configGetStub.withArgs('csv', 'escapeFormulaValues').returns(true); callAsCurrentUserStub.onFirstCall().returns({ @@ -388,7 +395,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -414,7 +421,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -441,7 +448,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -462,7 +469,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -485,7 +492,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -506,7 +513,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -519,10 +526,10 @@ describe('CSV Execute Job', function() { }); }); - describe('Elasticsearch call errors', function() { - it('should reject Promise if search call errors out', async function() { + describe('Elasticsearch call errors', function () { + it('should reject Promise if search call errors out', async function () { callAsCurrentUserStub.rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -533,7 +540,7 @@ describe('CSV Execute Job', function() { ).rejects.toMatchInlineSnapshot(`[Error]`); }); - it('should reject Promise if scroll call errors out', async function() { + it('should reject Promise if scroll call errors out', async function () { callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], @@ -541,7 +548,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); callAsCurrentUserStub.onSecondCall().rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -553,8 +560,8 @@ describe('CSV Execute Job', function() { }); }); - describe('invalid responses', function() { - it('should reject Promise if search returns hits but no _scroll_id', async function() { + describe('invalid responses', function () { + it('should reject Promise if search returns hits but no _scroll_id', async function () { callAsCurrentUserStub.resolves({ hits: { hits: [{}], @@ -562,7 +569,7 @@ describe('CSV Execute Job', function() { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -575,7 +582,7 @@ describe('CSV Execute Job', function() { ); }); - it('should reject Promise if search returns no hits and no _scroll_id', async function() { + it('should reject Promise if search returns no hits and no _scroll_id', async function () { callAsCurrentUserStub.resolves({ hits: { hits: [], @@ -583,7 +590,7 @@ describe('CSV Execute Job', function() { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -596,7 +603,7 @@ describe('CSV Execute Job', function() { ); }); - it('should reject Promise if scroll returns hits but no _scroll_id', async function() { + it('should reject Promise if scroll returns hits but no _scroll_id', async function () { callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], @@ -611,7 +618,7 @@ describe('CSV Execute Job', function() { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -624,7 +631,7 @@ describe('CSV Execute Job', function() { ); }); - it('should reject Promise if scroll returns no hits and no _scroll_id', async function() { + it('should reject Promise if scroll returns no hits and no _scroll_id', async function () { callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], @@ -639,7 +646,7 @@ describe('CSV Execute Job', function() { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -653,17 +660,17 @@ describe('CSV Execute Job', function() { }); }); - describe('cancellation', function() { + describe('cancellation', function () { const scrollId = getRandomScrollId(); - beforeEach(function() { + beforeEach(function () { // We have to "re-stub" the callAsCurrentUser stub here so that we can use the fakeFunction // that delays the Promise resolution so we have a chance to call cancellationToken.cancel(). // Otherwise, we get into an endless loop, and don't have a chance to call cancel callAsCurrentUserStub.restore(); callAsCurrentUserStub = sinon .stub(clusterStub, 'callAsCurrentUser') - .callsFake(async function() { + .callsFake(async function () { await delay(1); return { hits: { @@ -674,8 +681,8 @@ describe('CSV Execute Job', function() { }); }); - it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); executeJob( 'job345', getJobDocPayload({ @@ -693,8 +700,8 @@ describe('CSV Execute Job', function() { expect(callAsCurrentUserStub.callCount).toBe(callCount + 1); // last call is to clear the scroll }); - it(`shouldn't call clearScroll if it never got a scrollId`, async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it(`shouldn't call clearScroll if it never got a scrollId`, async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); executeJob( 'job345', getJobDocPayload({ @@ -711,8 +718,8 @@ describe('CSV Execute Job', function() { } }); - it('should call clearScroll if it got a scrollId', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should call clearScroll if it got a scrollId', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); executeJob( 'job345', getJobDocPayload({ @@ -732,9 +739,9 @@ describe('CSV Execute Job', function() { }); }); - describe('csv content', function() { - it('should write column headers to output, even if there are no results', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + describe('csv content', function () { + it('should write column headers to output, even if there are no results', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -744,9 +751,9 @@ describe('CSV Execute Job', function() { expect(content).toBe(`one,two\n`); }); - it('should use custom uiSettings csv:separator for header', async function() { - mockUiSettingsClient.get.withArgs('csv:separator').returns(';'); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should use custom uiSettings csv:separator for header', async function () { + mockUiSettingsClient.get.withArgs(CSV_SEPARATOR_SETTING).returns(';'); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -756,9 +763,9 @@ describe('CSV Execute Job', function() { expect(content).toBe(`one;two\n`); }); - it('should escape column headers if uiSettings csv:quoteValues is true', async function() { - mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should escape column headers if uiSettings csv:quoteValues is true', async function () { + mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(true); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -768,9 +775,9 @@ describe('CSV Execute Job', function() { expect(content).toBe(`"one and a half",two,"three-and-four","five & six"\n`); }); - it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function() { - mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(false); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () { + mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(false); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -780,8 +787,8 @@ describe('CSV Execute Job', function() { expect(content).toBe(`one and a half,two,three-and-four,five & six\n`); }); - it('should write column headers to output, when there are results', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should write column headers to output, when there are results', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ one: '1', two: '2' }], @@ -800,8 +807,8 @@ describe('CSV Execute Job', function() { expect(headerLine).toBe('one,two'); }); - it('should use comma separated values of non-nested fields from _source', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should use comma separated values of non-nested fields from _source', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -821,8 +828,8 @@ describe('CSV Execute Job', function() { expect(valuesLine).toBe('foo,bar'); }); - it('should concatenate the hits from multiple responses', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should concatenate the hits from multiple responses', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -849,8 +856,8 @@ describe('CSV Execute Job', function() { expect(lines[2]).toBe('baz,qux'); }); - it('should use field formatters to format fields', async function() { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + it('should use field formatters to format fields', async function () { + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -880,19 +887,19 @@ describe('CSV Execute Job', function() { }); }); - describe('maxSizeBytes', function() { + describe('maxSizeBytes', function () { // The following tests use explicitly specified lengths. UTF-8 uses between one and four 8-bit bytes for each // code-point. However, any character that can be represented by ASCII requires one-byte, so a majority of the // tests use these 'simple' characters to make the math easier - describe('when only the headers exceed the maxSizeBytes', function() { + describe('when only the headers exceed the maxSizeBytes', function () { let content: string; let maxSizeReached: boolean; - beforeEach(async function() { + beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(1); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -906,23 +913,23 @@ describe('CSV Execute Job', function() { )); }); - it('should return max_size_reached', function() { + it('should return max_size_reached', function () { expect(maxSizeReached).toBe(true); }); - it('should return empty content', function() { + it('should return empty content', function () { expect(content).toBe(''); }); }); - describe('when headers are equal to maxSizeBytes', function() { + describe('when headers are equal to maxSizeBytes', function () { let content: string; let maxSizeReached: boolean; - beforeEach(async function() { + beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -936,20 +943,20 @@ describe('CSV Execute Job', function() { )); }); - it(`shouldn't return max_size_reached`, function() { + it(`shouldn't return max_size_reached`, function () { expect(maxSizeReached).toBe(false); }); - it(`should return content`, function() { + it(`should return content`, function () { expect(content).toBe('one,two\n'); }); }); - describe('when the data exceeds the maxSizeBytes', function() { + describe('when the data exceeds the maxSizeBytes', function () { let content: string; let maxSizeReached: boolean; - beforeEach(async function() { + beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); callAsCurrentUserStub.onFirstCall().returns({ @@ -959,7 +966,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -974,21 +981,21 @@ describe('CSV Execute Job', function() { )); }); - it(`should return max_size_reached`, function() { + it(`should return max_size_reached`, function () { expect(maxSizeReached).toBe(true); }); - it(`should return the headers in the content`, function() { + it(`should return the headers in the content`, function () { expect(content).toBe('one,two\n'); }); }); - describe('when headers and data equal the maxSizeBytes', function() { + describe('when headers and data equal the maxSizeBytes', function () { let content: string; let maxSizeReached: boolean; - beforeEach(async function() { - mockReportingPlugin.getUiSettingsServiceFactory = () => mockUiSettingsClient; + beforeEach(async function () { + mockReportingCore.getUiSettingsServiceFactory = () => mockUiSettingsClient; configGetStub.withArgs('csv', 'maxSizeBytes').returns(18); callAsCurrentUserStub.onFirstCall().returns({ @@ -998,7 +1005,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1013,18 +1020,18 @@ describe('CSV Execute Job', function() { )); }); - it(`shouldn't return max_size_reached`, async function() { + it(`shouldn't return max_size_reached`, async function () { expect(maxSizeReached).toBe(false); }); - it('should return headers and data in content', function() { + it('should return headers and data in content', function () { expect(content).toBe('one,two\nfoo,bar\n'); }); }); }); - describe('scroll settings', function() { - it('passes scroll duration to initial search call', async function() { + describe('scroll settings', function () { + it('passes scroll duration to initial search call', async function () { const scrollDuration = 'test'; configGetStub.withArgs('csv', 'scroll').returns({ duration: scrollDuration }); @@ -1035,7 +1042,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1050,7 +1057,7 @@ describe('CSV Execute Job', function() { expect(searchCall.args[1].scroll).toBe(scrollDuration); }); - it('passes scroll size to initial search call', async function() { + it('passes scroll size to initial search call', async function () { const scrollSize = 100; configGetStub.withArgs('csv', 'scroll').returns({ size: scrollSize }); @@ -1061,7 +1068,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1076,7 +1083,7 @@ describe('CSV Execute Job', function() { expect(searchCall.args[1].size).toBe(scrollSize); }); - it('passes scroll duration to subsequent scroll call', async function() { + it('passes scroll duration to subsequent scroll call', async function () { const scrollDuration = 'test'; configGetStub.withArgs('csv', 'scroll').returns({ duration: scrollDuration }); @@ -1087,7 +1094,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingCore, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts new file mode 100644 index 0000000000000..4b17cc669efe1 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import Hapi from 'hapi'; +import { IUiSettingsClient, KibanaRequest } from '../../../../../../../src/core/server'; +import { + CSV_SEPARATOR_SETTING, + CSV_QUOTE_VALUES_SETTING, +} from '../../../../../../../src/plugins/share/server'; +import { ReportingCore } from '../../..'; +import { CSV_BOM_CHARS, CSV_JOB_TYPE } from '../../../../common/constants'; +import { getFieldFormats } from '../../../../server/services'; +import { cryptoFactory, LevelLogger } from '../../../lib'; +import { ESQueueWorkerExecuteFn, ExecuteJobFactory } from '../../../types'; +import { JobDocPayloadDiscoverCsv } from '../types'; +import { fieldFormatMapFactory } from './lib/field_format_map'; +import { createGenerateCsv } from './lib/generate_csv'; + +export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { + const config = reporting.getConfig(); + const crypto = cryptoFactory(config.get('encryptionKey')); + const logger = parentLogger.clone([CSV_JOB_TYPE, 'execute-job']); + const serverBasePath = config.kbnConfig.get('server', 'basePath'); + + return async function executeJob( + jobId: string, + job: JobDocPayloadDiscoverCsv, + cancellationToken: any + ) { + const elasticsearch = await reporting.getElasticsearchService(); + const jobLogger = logger.clone([jobId]); + + const { + searchRequest, + fields, + indexPatternSavedObject, + metaFields, + conflictedTypesFields, + headers, + basePath, + } = job; + + const decryptHeaders = async () => { + try { + if (typeof headers !== 'string') { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv.executeJob.missingJobHeadersErrorMessage', + { + defaultMessage: 'Job headers are missing', + } + ) + ); + } + return await crypto.decrypt(headers); + } catch (err) { + logger.error(err); + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv.executeJob.failedToDecryptReportJobDataErrorMessage', + { + defaultMessage: 'Failed to decrypt report job data. Please ensure that {encryptionKey} is set and re-generate this report. {err}', + values: { encryptionKey: 'xpack.reporting.encryptionKey', err: err.toString() }, + } + ) + ); // prettier-ignore + } + }; + + const fakeRequest = KibanaRequest.from({ + headers: await decryptHeaders(), + // This is used by the spaces SavedObjectClientWrapper to determine the existing space. + // We use the basePath from the saved job, which we'll have post spaces being implemented; + // or we use the server base path, which uses the default space + getBasePath: () => basePath || serverBasePath, + path: '/', + route: { settings: {} }, + url: { href: '/' }, + raw: { req: { url: '/' } }, + } as Hapi.Request); + + const { callAsCurrentUser } = elasticsearch.legacy.client.asScoped(fakeRequest); + const callEndpoint = (endpoint: string, clientParams = {}, options = {}) => + callAsCurrentUser(endpoint, clientParams, options); + + const savedObjectsClient = await reporting.getSavedObjectsClient(fakeRequest); + const uiSettingsClient = await reporting.getUiSettingsServiceFactory(savedObjectsClient); + + const getFormatsMap = async (client: IUiSettingsClient) => { + const fieldFormats = await getFieldFormats().fieldFormatServiceFactory(client); + return fieldFormatMapFactory(indexPatternSavedObject, fieldFormats); + }; + const getUiSettings = async (client: IUiSettingsClient) => { + const [separator, quoteValues, timezone] = await Promise.all([ + client.get(CSV_SEPARATOR_SETTING), + client.get(CSV_QUOTE_VALUES_SETTING), + client.get('dateFormat:tz'), + ]); + + if (timezone === 'Browser') { + logger.warn( + i18n.translate('xpack.reporting.exportTypes.csv.executeJob.dateFormateSetting', { + defaultMessage: 'Kibana Advanced Setting "{dateFormatTimezone}" is set to "Browser". Dates will be formatted as UTC to avoid ambiguity.', + values: { dateFormatTimezone: 'dateFormat:tz' } + }) + ); // prettier-ignore + } + + return { + separator, + quoteValues, + timezone, + }; + }; + + const [formatsMap, uiSettings] = await Promise.all([ + getFormatsMap(uiSettingsClient), + getUiSettings(uiSettingsClient), + ]); + + const generateCsv = createGenerateCsv(jobLogger); + const bom = config.get('csv', 'useByteOrderMarkEncoding') ? CSV_BOM_CHARS : ''; + + const { content, maxSizeReached, size, csvContainsFormulas, warnings } = await generateCsv({ + searchRequest, + fields, + metaFields, + conflictedTypesFields, + callEndpoint, + cancellationToken, + formatsMap, + settings: { + ...uiSettings, + checkForFormulas: config.get('csv', 'checkForFormulas'), + maxSizeBytes: config.get('csv', 'maxSizeBytes'), + scroll: config.get('csv', 'scroll'), + escapeFormulaValues: config.get('csv', 'escapeFormulaValues'), + }, + }); + + // @TODO: Consolidate these one-off warnings into the warnings array (max-size reached and csv contains formulas) + return { + content_type: 'text/csv', + content: bom + content, + max_size_reached: maxSizeReached, + size, + csv_contains_formulas: csvContainsFormulas, + warnings, + }; + }; +}; diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/lib/cell_has_formula.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/cell_has_formula.ts new file mode 100644 index 0000000000000..659aef85ed593 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/cell_has_formula.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { startsWith } from 'lodash'; +import { CSV_FORMULA_CHARS } from '../../../../../common/constants'; + +export const cellHasFormulas = (val: string) => + CSV_FORMULA_CHARS.some((formulaChar) => startsWith(val, formulaChar)); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/check_cells_for_formulas.test.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/check_cells_for_formulas.test.ts index 972ca1777bd73..114512435a4c3 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/check_cells_for_formulas.test.ts @@ -23,7 +23,7 @@ describe(`Check CSV Injected values`, () => { ).toBe(false); }); - formulaValues.forEach(formula => { + formulaValues.forEach((formula) => { it(`returns 'true' when cells start with "${formula}"`, () => { expect( checkIfRowsHaveFormulas( @@ -51,7 +51,7 @@ describe(`Check CSV Injected values`, () => { }); }); - formulaValues.forEach(formula => { + formulaValues.forEach((formula) => { it(`returns 'true' when headers start with "${formula}"`, () => { expect( checkIfRowsHaveFormulas( @@ -79,7 +79,7 @@ describe(`Check CSV Injected values`, () => { }); }); - nonRows.forEach(nonRow => { + nonRows.forEach((nonRow) => { it(`returns false when there's "${nonRow}" for rows`, () => { expect( checkIfRowsHaveFormulas( diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/check_cells_for_formulas.ts similarity index 91% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/check_cells_for_formulas.ts index 0ec39c527d656..d89eb45ead75e 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/check_cells_for_formulas.ts @@ -15,5 +15,5 @@ export const checkIfRowsHaveFormulas = (flattened: IFlattened, fields: string[]) const pruned = _.pick(flattened, fields); const cells = [..._.keys(pruned), ...(_.values(pruned) as string[])]; - return _.some(cells, cell => cellHasFormulas(cell)); + return _.some(cells, (cell) => cellHasFormulas(cell)); }; diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/lib/escape_value.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/escape_value.test.ts new file mode 100644 index 0000000000000..be1f34288bd25 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/escape_value.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { createEscapeValue } from './escape_value'; + +describe('escapeValue', function () { + describe('quoteValues is true', function () { + let escapeValue: (val: string) => string; + beforeEach(function () { + escapeValue = createEscapeValue(true, false); + }); + + it('should escape value with spaces', function () { + expect(escapeValue('baz qux')).to.be('"baz qux"'); + }); + + it('should escape values with hyphens', function () { + expect(escapeValue('baz-qux')).to.be('"baz-qux"'); + }); + + it('should not escape small integers', function () { + expect(escapeValue((1).toString())).to.be('1'); + }); + + it('should not escape small whole numbers', function () { + expect(escapeValue((1.0).toString())).to.be('1'); + }); + + it('should escape decimal numbers', function () { + expect(escapeValue((1.1).toString())).to.be('"1.1"'); + }); + + it('should not comma-separate large integers', function () { + expect(escapeValue((1000000).toString())).to.be('1000000'); + }); + + it('should treat booleans like strings', function () { + expect(escapeValue(true.toString())).to.be('true'); + }); + }); + + describe('quoteValues is false', function () { + let escapeValue: (val: string) => string; + beforeEach(function () { + escapeValue = createEscapeValue(false, false); + }); + + it('should return the value unescaped', function () { + const value = '"foo, bar & baz-qux"'; + expect(escapeValue(value)).to.be(value); + }); + }); + + describe('escapeValues', () => { + describe('when true', () => { + let escapeValue: (val: string) => string; + beforeEach(function () { + escapeValue = createEscapeValue(true, true); + }); + + ['@', '+', '-', '='].forEach((badChar) => { + it(`should escape ${badChar} injection values`, function () { + expect(escapeValue(`${badChar}cmd|' /C calc'!A0`)).to.be( + `"'${badChar}cmd|' /C calc'!A0"` + ); + }); + }); + }); + + describe('when false', () => { + let escapeValue: (val: string) => string; + beforeEach(function () { + escapeValue = createEscapeValue(true, false); + }); + + ['@', '+', '-', '='].forEach((badChar) => { + it(`should not escape ${badChar} injection values`, function () { + expect(escapeValue(`${badChar}cmd|' /C calc'!A0`)).to.be(`"${badChar}cmd|' /C calc'!A0"`); + }); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/escape_value.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/escape_value.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.test.ts similarity index 87% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.test.ts index 9ab434e6a058b..83aa23de67663 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.test.ts @@ -9,12 +9,13 @@ import expect from '@kbn/expect'; import { fieldFormats, FieldFormatsGetConfigFn, + UI_SETTINGS, } from '../../../../../../../../src/plugins/data/server'; import { fieldFormatMapFactory } from './field_format_map'; type ConfigValue = { number: { id: string; params: {} } } | string; -describe('field format map', function() { +describe('field format map', function () { const indexPatternSavedObject = { id: 'logstash-*', type: 'index-pattern', @@ -28,10 +29,10 @@ describe('field format map', function() { }, }; const configMock: Record = {}; - configMock['format:defaultTypeMap'] = { + configMock[UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP] = { number: { id: 'number', params: {} }, }; - configMock['format:number:defaultPattern'] = '0,0.[000]'; + configMock[UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN] = '0,0.[000]'; const getConfig = ((key: string) => configMock[key]) as FieldFormatsGetConfigFn; const testValue = '4000'; @@ -40,17 +41,17 @@ describe('field format map', function() { const formatMap = fieldFormatMapFactory(indexPatternSavedObject, fieldFormatsRegistry); - it('should build field format map with entry per index pattern field', function() { + it('should build field format map with entry per index pattern field', function () { expect(formatMap.has('field1')).to.be(true); expect(formatMap.has('field2')).to.be(true); expect(formatMap.has('field_not_in_index')).to.be(false); }); - it('should create custom FieldFormat for fields with configured field formatter', function() { + it('should create custom FieldFormat for fields with configured field formatter', function () { expect(formatMap.get('field1').convert(testValue)).to.be('3.9KB'); }); - it('should create default FieldFormat for fields with no field formatter', function() { + it('should create default FieldFormat for fields with no field formatter', function () { expect(formatMap.get('field2').convert(testValue)).to.be('4,000'); }); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.ts similarity index 96% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.ts index e1459e195d9f6..6cb4d0bbb1c65 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.ts @@ -35,7 +35,7 @@ export function fieldFormatMapFactory( // Add FieldFormat instances for fields with custom formatters if (_.has(indexPatternSavedObject, 'attributes.fieldFormatMap')) { const fieldFormatMap = JSON.parse(indexPatternSavedObject.attributes.fieldFormatMap); - Object.keys(fieldFormatMap).forEach(fieldName => { + Object.keys(fieldFormatMap).forEach((fieldName) => { const formatConfig: FieldFormatConfig = fieldFormatMap[fieldName]; if (!_.isEmpty(formatConfig)) { diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/flatten_hit.test.ts similarity index 86% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/flatten_hit.test.ts index 1e06e78357399..4dbdcbdd4f744 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/flatten_hit.test.ts @@ -9,12 +9,12 @@ import { createFlattenHit } from './flatten_hit'; type Hit = Record; -describe('flattenHit', function() { +describe('flattenHit', function () { let flattenHit: (hit: Hit) => Record; let hit: Hit; let metaFields: string[]; - beforeEach(function() { + beforeEach(function () { const fields = [ 'tags.text', 'tags.label', @@ -69,7 +69,7 @@ describe('flattenHit', function() { }; }); - it('flattens keys as far down as the mapping goes', function() { + it('flattens keys as far down as the mapping goes', function () { const flat = flattenHit(hit); expect(flat).to.have.property('geo.coordinates', hit._source.geo.coordinates); @@ -82,7 +82,7 @@ describe('flattenHit', function() { expect(flat).to.have.property('bytes', 10039103); }); - it('flattens keys not in the mapping', function() { + it('flattens keys not in the mapping', function () { const flat = flattenHit(hit); expect(flat).to.have.property('noMapping', true); @@ -90,7 +90,7 @@ describe('flattenHit', function() { expect(flat.groups).to.eql(['loners']); }); - it('flattens conflicting types in the mapping', function() { + it('flattens conflicting types in the mapping', function () { const flat = flattenHit(hit); expect(flat).to.not.have.property('user'); @@ -98,13 +98,13 @@ describe('flattenHit', function() { expect(flat).to.have.property('user.id', hit._source.user.id); }); - it('should preserve objects in arrays', function() { + it('should preserve objects in arrays', function () { const flat = flattenHit(hit); expect(flat).to.have.property('tags', hit._source.tags); }); - it('does not enter into nested fields', function() { + it('does not enter into nested fields', function () { const flat = flattenHit(hit); expect(flat).to.have.property('team', hit._source.team); @@ -114,36 +114,36 @@ describe('flattenHit', function() { expect(flat).to.not.have.property('team.0'); }); - it('unwraps script fields', function() { + it('unwraps script fields', function () { const flat = flattenHit(hit); expect(flat).to.have.property('delta', 42); }); - it('assumes that all fields are "computed fields"', function() { + it('assumes that all fields are "computed fields"', function () { const flat = flattenHit(hit); expect(flat).to.have.property('random', 0.12345); }); - describe('metaFields', function() { - beforeEach(function() { + describe('metaFields', function () { + beforeEach(function () { metaFields.push('_metaKey'); }); - it('ignores fields that start with an _ and are not in the metaFields', function() { + it('ignores fields that start with an _ and are not in the metaFields', function () { hit.fields._notMetaKey = [100]; const flat = flattenHit(hit); expect(flat).to.not.have.property('_notMetaKey'); }); - it('includes underscore-prefixed keys that are in the metaFields', function() { + it('includes underscore-prefixed keys that are in the metaFields', function () { hit.fields._metaKey = [100]; const flat = flattenHit(hit); expect(flat).to.have.property('_metaKey', 100); }); - it('handles fields that are not arrays, like _timestamp', function() { + it('handles fields that are not arrays, like _timestamp', function () { hit.fields._metaKey = 20000; const flat = flattenHit(hit); expect(flat).to.have.property('_metaKey', 20000); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/flatten_hit.ts similarity index 98% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/flatten_hit.ts index 328d49a27911c..a4c634439ec45 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/flatten_hit.ts @@ -40,7 +40,7 @@ export function createFlattenHit( }; const flattenMetaFields = (flat: Hit, hit: Hit) => { - _.each(metaFields, meta => { + _.each(metaFields, (meta) => { if (meta === '_source') return; flat[meta] = hit[meta]; }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/format_csv_values.test.ts similarity index 82% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/format_csv_values.test.ts index b38bad1179471..15bd52afc9118 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/format_csv_values.test.ts @@ -7,11 +7,11 @@ import expect from '@kbn/expect'; import { createFormatCsvValues } from './format_csv_values'; -describe('formatCsvValues', function() { +describe('formatCsvValues', function () { const separator = ','; const fields = ['foo', 'bar']; const mockEscapeValue = (value: any, index: number, array: any[]) => value || ''; - describe('with _source as one of the fields', function() { + describe('with _source as one of the fields', function () { const formatsMap = new Map(); const formatCsvValues = createFormatCsvValues( mockEscapeValue, @@ -19,22 +19,22 @@ describe('formatCsvValues', function() { ['foo', '_source'], formatsMap ); - it('should return full _source for _source field', function() { + it('should return full _source for _source field', function () { const values = { foo: 'baz', }; expect(formatCsvValues(values)).to.be('baz,{"foo":"baz"}'); }); }); - describe('without field formats', function() { + describe('without field formats', function () { const formatsMap = new Map(); const formatCsvValues = createFormatCsvValues(mockEscapeValue, separator, fields, formatsMap); - it('should use the specified separator', function() { + it('should use the specified separator', function () { expect(formatCsvValues({})).to.be(separator); }); - it('should replace null and undefined with empty strings', function() { + it('should replace null and undefined with empty strings', function () { const values = { foo: undefined, bar: null, @@ -42,7 +42,7 @@ describe('formatCsvValues', function() { expect(formatCsvValues(values)).to.be(','); }); - it('should JSON.stringify objects', function() { + it('should JSON.stringify objects', function () { const values = { foo: { baz: 'qux', @@ -51,7 +51,7 @@ describe('formatCsvValues', function() { expect(formatCsvValues(values)).to.be('{"baz":"qux"},'); }); - it('should concatenate strings', function() { + it('should concatenate strings', function () { const values = { foo: 'baz', bar: 'qux', @@ -60,7 +60,7 @@ describe('formatCsvValues', function() { }); }); - describe('with field formats', function() { + describe('with field formats', function () { const mockFieldFormat = { convert: (val: string) => String(val).toUpperCase(), }; @@ -68,7 +68,7 @@ describe('formatCsvValues', function() { formatsMap.set('bar', mockFieldFormat); const formatCsvValues = createFormatCsvValues(mockEscapeValue, separator, fields, formatsMap); - it('should replace null and undefined with empty strings', function() { + it('should replace null and undefined with empty strings', function () { const values = { foo: undefined, bar: null, @@ -76,7 +76,7 @@ describe('formatCsvValues', function() { expect(formatCsvValues(values)).to.be(','); }); - it('should format value with appropriate FieldFormat', function() { + it('should format value with appropriate FieldFormat', function () { const values = { foo: 'baz', bar: 'qux', diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/format_csv_values.ts similarity index 87% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/format_csv_values.ts index 35093f45fdd5b..bb4e2be86f5df 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/format_csv_values.ts @@ -15,7 +15,7 @@ export function createFormatCsvValues( ) { return function formatCsvValues(values: Record) { return fields - .map(field => { + .map((field) => { let value; if (field === '_source') { value = values; @@ -34,8 +34,8 @@ export function createFormatCsvValues( return formattedValue; }) - .map(value => (isObject(value) ? JSON.stringify(value) : value)) - .map(value => (value ? value.toString() : value)) + .map((value) => (isObject(value) ? JSON.stringify(value) : value)) + .map((value) => (value ? value.toString() : value)) .map(escapeValue) .join(separator); }; diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/lib/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/generate_csv.ts new file mode 100644 index 0000000000000..019fa3c9c8e9d --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/generate_csv.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { LevelLogger } from '../../../../lib'; +import { GenerateCsvParams, SavedSearchGeneratorResult } from '../../types'; +import { createFlattenHit } from './flatten_hit'; +import { createFormatCsvValues } from './format_csv_values'; +import { createEscapeValue } from './escape_value'; +import { createHitIterator } from './hit_iterator'; +import { MaxSizeStringBuilder } from './max_size_string_builder'; +import { checkIfRowsHaveFormulas } from './check_cells_for_formulas'; + +export function createGenerateCsv(logger: LevelLogger) { + const hitIterator = createHitIterator(logger); + + return async function generateCsv({ + searchRequest, + fields, + formatsMap, + metaFields, + conflictedTypesFields, + callEndpoint, + cancellationToken, + settings, + }: GenerateCsvParams): Promise { + const escapeValue = createEscapeValue(settings.quoteValues, settings.escapeFormulaValues); + const builder = new MaxSizeStringBuilder(settings.maxSizeBytes); + const header = `${fields.map(escapeValue).join(settings.separator)}\n`; + const warnings: string[] = []; + + if (!builder.tryAppend(header)) { + return { + size: 0, + content: '', + maxSizeReached: true, + warnings: [], + }; + } + + const iterator = hitIterator(settings.scroll, callEndpoint, searchRequest, cancellationToken); + let maxSizeReached = false; + let csvContainsFormulas = false; + + const flattenHit = createFlattenHit(fields, metaFields, conflictedTypesFields); + const formatCsvValues = createFormatCsvValues( + escapeValue, + settings.separator, + fields, + formatsMap + ); + try { + while (true) { + const { done, value: hit } = await iterator.next(); + + if (!hit) { + break; + } + + if (done) { + break; + } + + const flattened = flattenHit(hit); + const rows = formatCsvValues(flattened); + const rowsHaveFormulas = + settings.checkForFormulas && checkIfRowsHaveFormulas(flattened, fields); + + if (rowsHaveFormulas) { + csvContainsFormulas = true; + } + + if (!builder.tryAppend(rows + '\n')) { + logger.warn('max Size Reached'); + maxSizeReached = true; + cancellationToken.cancel(); + break; + } + } + } finally { + await iterator.return(); + } + const size = builder.getSizeInBytes(); + logger.debug(`finished generating, total size in bytes: ${size}`); + + if (csvContainsFormulas && settings.escapeFormulaValues) { + warnings.push( + i18n.translate('xpack.reporting.exportTypes.csv.generateCsv.escapedFormulaValues', { + defaultMessage: 'CSV may contain formulas whose values have been escaped', + }) + ); + } + + return { + content: builder.getString(), + csvContainsFormulas: csvContainsFormulas && !settings.escapeFormulaValues, + maxSizeReached, + size, + warnings, + }; + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/hit_iterator.test.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/hit_iterator.test.ts index 905d9cd68b128..479879e3c8b01 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/hit_iterator.test.ts @@ -6,9 +6,9 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; -import { CancellationToken } from '../../../../../../../plugins/reporting/common'; -import { LevelLogger } from '../../../../server/lib'; -import { ScrollConfig } from '../../../../server/types'; +import { CancellationToken } from '../../../../../common'; +import { LevelLogger } from '../../../../lib'; +import { ScrollConfig } from '../../../../types'; import { createHitIterator } from './hit_iterator'; const mockLogger = { @@ -25,7 +25,7 @@ const mockConfig: ScrollConfig = { duration: '2s', size: 123 }; let realCancellationToken = new CancellationToken(); let isCancelledStub: sinon.SinonStub<[], boolean>; -describe('hitIterator', function() { +describe('hitIterator', function () { beforeEach(() => { debugLogStub.resetHistory(); warnLogStub.resetHistory(); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/hit_iterator.ts similarity index 93% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/hit_iterator.ts index 803161910443e..38b28573d602d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/hit_iterator.ts @@ -6,9 +6,9 @@ import { i18n } from '@kbn/i18n'; import { SearchParams, SearchResponse } from 'elasticsearch'; -import { CancellationToken } from '../../../../../../../plugins/reporting/common'; -import { LevelLogger } from '../../../../server/lib'; -import { ScrollConfig } from '../../../../server/types'; +import { CancellationToken } from '../../../../../common'; +import { LevelLogger } from '../../../../lib'; +import { ScrollConfig } from '../../../../types'; async function parseResponse(request: SearchResponse) { const response = await request; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/max_size_string_builder.test.ts similarity index 81% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/max_size_string_builder.test.ts index 843ff82e7c4bc..7a35de1cea19b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/max_size_string_builder.test.ts @@ -7,55 +7,55 @@ import expect from '@kbn/expect'; import { MaxSizeStringBuilder } from './max_size_string_builder'; -describe('MaxSizeStringBuilder', function() { - describe('tryAppend', function() { - it(`should return true if appended string is under maxSize`, function() { +describe('MaxSizeStringBuilder', function () { + describe('tryAppend', function () { + it(`should return true if appended string is under maxSize`, function () { const builder = new MaxSizeStringBuilder(100); const result = builder.tryAppend('aa'); expect(result).to.be(true); }); - it(`should return false if appended string is over the maxSize`, function() { + it(`should return false if appended string is over the maxSize`, function () { const builder = new MaxSizeStringBuilder(1); const result = builder.tryAppend('aa'); expect(result).to.be(false); }); - it(`should return true then false if second appended string puts total size over the maxSize`, function() { + it(`should return true then false if second appended string puts total size over the maxSize`, function () { const builder = new MaxSizeStringBuilder(1); expect(builder.tryAppend('a')).to.be(true); expect(builder.tryAppend('a')).to.be(false); }); }); - describe('getBuffer', function() { - it(`should return an empty string when we don't call tryAppend`, function() { + describe('getBuffer', function () { + it(`should return an empty string when we don't call tryAppend`, function () { const builder = new MaxSizeStringBuilder(100); expect(builder.getString()).to.be(''); }); - it('should return equivalent string if tryAppend called once and less than maxSize', function() { + it('should return equivalent string if tryAppend called once and less than maxSize', function () { const str = 'foo'; const builder = new MaxSizeStringBuilder(100); builder.tryAppend(str); expect(builder.getString()).to.be(str); }); - it('should return equivalent string if tryAppend called multiple times and total size less than maxSize', function() { + it('should return equivalent string if tryAppend called multiple times and total size less than maxSize', function () { const strs = ['foo', 'bar', 'baz']; const builder = new MaxSizeStringBuilder(100); - strs.forEach(str => builder.tryAppend(str)); + strs.forEach((str) => builder.tryAppend(str)); expect(builder.getString()).to.be(strs.join('')); }); - it('should return empty string if tryAppend called one time with size greater than maxSize', function() { + it('should return empty string if tryAppend called one time with size greater than maxSize', function () { const str = 'aa'; // each a is one byte const builder = new MaxSizeStringBuilder(1); builder.tryAppend(str); expect(builder.getString()).to.be(''); }); - it('should return partial string if tryAppend called multiple times with total size greater than maxSize', function() { + it('should return partial string if tryAppend called multiple times with total size greater than maxSize', function () { const str = 'a'; // each a is one byte const builder = new MaxSizeStringBuilder(1); builder.tryAppend(str); @@ -64,13 +64,13 @@ describe('MaxSizeStringBuilder', function() { }); }); - describe('getSizeInBytes', function() { - it(`should return 0 when no strings have been appended`, function() { + describe('getSizeInBytes', function () { + it(`should return 0 when no strings have been appended`, function () { const builder = new MaxSizeStringBuilder(100); expect(builder.getSizeInBytes()).to.be(0); }); - it(`should the size in bytes`, function() { + it(`should the size in bytes`, function () { const builder = new MaxSizeStringBuilder(100); const stringValue = 'foobar'; builder.tryAppend(stringValue); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/max_size_string_builder.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts rename to x-pack/plugins/reporting/server/export_types/csv/server/lib/max_size_string_builder.ts diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts new file mode 100644 index 0000000000000..c80cd5fd24fe5 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CancellationToken } from '../../../common'; +import { JobParamPostPayload, JobDocPayload, ScrollConfig } from '../../types'; + +export type RawValue = string | object | null | undefined; + +interface DocValueField { + field: string; + format: string; +} + +interface SortOptions { + order: string; + unmapped_type: string; +} + +export interface JobParamPostPayloadDiscoverCsv extends JobParamPostPayload { + state?: { + query: any; + sort: Array>; + docvalue_fields: DocValueField[]; + }; +} + +export interface JobParamsDiscoverCsv { + indexPatternId?: string; + post?: JobParamPostPayloadDiscoverCsv; +} + +export interface JobDocPayloadDiscoverCsv extends JobDocPayload { + basePath: string; + searchRequest: any; + fields: any; + indexPatternSavedObject: any; + metaFields: any; + conflictedTypesFields: any; +} + +export interface SearchRequest { + index: string; + body: + | { + _source: { + excludes: string[]; + includes: string[]; + }; + docvalue_fields: string[]; + query: + | { + bool: { + filter: any[]; + must_not: any[]; + should: any[]; + must: any[]; + }; + } + | any; + script_fields: any; + sort: Array<{ + [key: string]: { + order: string; + }; + }>; + stored_fields: string[]; + } + | any; +} + +type EndpointCaller = (method: string, params: any) => Promise; + +type FormatsMap = Map< + string, + { + id: string; + params: { + pattern: string; + }; + } +>; + +export interface SavedSearchGeneratorResult { + content: string; + size: number; + maxSizeReached: boolean; + csvContainsFormulas?: boolean; + warnings: string[]; +} + +export interface CsvResultFromSearch { + type: string; + result: SavedSearchGeneratorResult; +} + +export interface GenerateCsvParams { + searchRequest: SearchRequest; + callEndpoint: EndpointCaller; + fields: string[]; + formatsMap: FormatsMap; + metaFields: string[]; + conflictedTypesFields: string[]; + cancellationToken: CancellationToken; + settings: { + separator: string; + quoteValues: boolean; + timezone: string | null; + maxSizeBytes: number; + scroll: ScrollConfig; + checkForFormulas?: boolean; + escapeFormulaValues: boolean; + }; +} diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts new file mode 100644 index 0000000000000..65802ee5bb7fb --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + LICENSE_TYPE_BASIC, + LICENSE_TYPE_ENTERPRISE, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_TRIAL, +} from '../../../common/constants'; +import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../constants'; +import { ExportTypeDefinition } from '../../types'; +import { metadata } from './metadata'; +import { createJobFactory, ImmediateCreateJobFn } from './server/create_job'; +import { executeJobFactory, ImmediateExecuteFn } from './server/execute_job'; +import { JobParamsPanelCsv } from './types'; + +/* + * These functions are exported to share with the API route handler that + * generates csv from saved object immediately on request. + */ +export { createJobFactory } from './server/create_job'; +export { executeJobFactory } from './server/execute_job'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPanelCsv, + ImmediateCreateJobFn, + JobParamsPanelCsv, + ImmediateExecuteFn +> => ({ + ...metadata, + jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE, + jobContentExtension: 'csv', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_ENTERPRISE, + ], +}); diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/metadata.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/metadata.ts new file mode 100644 index 0000000000000..a0fd8a29fdcc4 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/metadata.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../constants'; + +export const metadata = { + id: CSV_FROM_SAVEDOBJECT_JOB_TYPE, + name: CSV_FROM_SAVEDOBJECT_JOB_TYPE, +}; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts new file mode 100644 index 0000000000000..c187da5104d3f --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { notFound, notImplemented } from 'boom'; +import { get } from 'lodash'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { ReportingCore } from '../../../..'; +import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../../common/constants'; +import { cryptoFactory, LevelLogger } from '../../../../lib'; +import { CreateJobFactory, TimeRangeParams } from '../../../../types'; +import { + JobDocPayloadPanelCsv, + JobParamsPanelCsv, + SavedObject, + SavedObjectServiceError, + SavedSearchObjectAttributesJSON, + SearchPanel, + VisObjectAttributesJSON, +} from '../../types'; +import { createJobSearch } from './create_job_search'; + +export type ImmediateCreateJobFn = ( + jobParams: JobParamsType, + headers: KibanaRequest['headers'], + context: RequestHandlerContext, + req: KibanaRequest +) => Promise<{ + type: string | null; + title: string; + jobParams: JobParamsType; +}>; + +interface VisData { + title: string; + visType: string; + panel: SearchPanel; +} + +export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { + const config = reporting.getConfig(); + const crypto = cryptoFactory(config.get('encryptionKey')); + const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); + + return async function createJob( + jobParams: JobParamsPanelCsv, + headers: KibanaRequest['headers'], + context: RequestHandlerContext, + req: KibanaRequest + ): Promise { + const { savedObjectType, savedObjectId } = jobParams; + const serializedEncryptedHeaders = await crypto.encrypt(headers); + + const { panel, title, visType }: VisData = await Promise.resolve() + .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId)) + .then(async (savedObject: SavedObject) => { + const { attributes, references } = savedObject; + const { + kibanaSavedObjectMeta: kibanaSavedObjectMetaJSON, + } = attributes as SavedSearchObjectAttributesJSON; + const { timerange } = req.body as { timerange: TimeRangeParams }; + + if (!kibanaSavedObjectMetaJSON) { + throw new Error('Could not parse saved object data!'); + } + + const kibanaSavedObjectMeta = { + ...kibanaSavedObjectMetaJSON, + searchSource: JSON.parse(kibanaSavedObjectMetaJSON.searchSourceJSON), + }; + + const { visState: visStateJSON } = attributes as VisObjectAttributesJSON; + if (visStateJSON) { + throw notImplemented('Visualization types are not yet implemented'); + } + + // saved search type + return await createJobSearch(timerange, attributes, references, kibanaSavedObjectMeta); + }) + .catch((err: Error) => { + const boomErr = (err as unknown) as { isBoom: boolean }; + if (boomErr.isBoom) { + throw err; + } + const errPayload: SavedObjectServiceError = get(err, 'output.payload', { statusCode: 0 }); + if (errPayload.statusCode === 404) { + throw notFound(errPayload.message); + } + if (err.stack) { + logger.error(err.stack); + } + throw new Error(`Unable to create a job from saved object data! Error: ${err}`); + }); + + return { + headers: serializedEncryptedHeaders, + jobParams: { ...jobParams, panel, visType }, + type: null, + title, + }; + }; +}; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job_search.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job_search.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job_search.ts rename to x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job_search.ts index 19204ef81c5eb..02abfb90091a1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job_search.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job_search.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TimeRangeParams } from '../../../../server/types'; +import { TimeRangeParams } from '../../../../types'; import { SavedObjectMeta, SavedObjectReference, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/index.ts rename to x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts new file mode 100644 index 0000000000000..d555100b6320d --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { ReportingCore } from '../../..'; +import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants'; +import { cryptoFactory, LevelLogger } from '../../../lib'; +import { ExecuteJobFactory, JobDocOutput, JobDocPayload } from '../../../types'; +import { CsvResultFromSearch } from '../../csv/types'; +import { FakeRequest, JobDocPayloadPanelCsv, JobParamsPanelCsv, SearchPanel } from '../types'; +import { createGenerateCsv } from './lib'; + +/* + * ImmediateExecuteFn receives the job doc payload because the payload was + * generated in the CreateFn + */ +export type ImmediateExecuteFn = ( + jobId: null, + job: JobDocPayload, + context: RequestHandlerContext, + req: KibanaRequest +) => Promise; + +export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { + const config = reporting.getConfig(); + const crypto = cryptoFactory(config.get('encryptionKey')); + const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); + const generateCsv = createGenerateCsv(reporting, parentLogger); + + return async function executeJob( + jobId: string | null, + job: JobDocPayloadPanelCsv, + context, + req + ): Promise { + // There will not be a jobID for "immediate" generation. + // jobID is only for "queued" jobs + // Use the jobID as a logging tag or "immediate" + const jobLogger = logger.clone([jobId === null ? 'immediate' : jobId]); + + const { jobParams } = job; + const { isImmediate, panel, visType } = jobParams as JobParamsPanelCsv & { panel: SearchPanel }; + + if (!panel) { + i18n.translate( + 'xpack.reporting.exportTypes.csv_from_savedobject.executeJob.failedToAccessPanel', + { defaultMessage: 'Failed to access panel metadata for job execution' } + ); + } + + jobLogger.debug(`Execute job generating [${visType}] csv`); + + let requestObject: KibanaRequest | FakeRequest; + + if (isImmediate && req) { + jobLogger.info(`Executing job from Immediate API using request context`); + requestObject = req; + } else { + jobLogger.info(`Executing job async using encrypted headers`); + let decryptedHeaders: Record; + const serializedEncryptedHeaders = job.headers; + try { + if (typeof serializedEncryptedHeaders !== 'string') { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv_from_savedobject.executeJob.missingJobHeadersErrorMessage', + { + defaultMessage: 'Job headers are missing', + } + ) + ); + } + decryptedHeaders = (await crypto.decrypt(serializedEncryptedHeaders)) as Record< + string, + unknown + >; + } catch (err) { + jobLogger.error(err); + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv_from_savedobject.executeJob.failedToDecryptReportJobDataErrorMessage', + { + defaultMessage: + 'Failed to decrypt report job data. Please ensure that {encryptionKey} is set and re-generate this report. {err}', + values: { encryptionKey: 'xpack.reporting.encryptionKey', err }, + } + ) + ); + } + + requestObject = { headers: decryptedHeaders }; + } + + let content: string; + let maxSizeReached = false; + let size = 0; + try { + const generateResults: CsvResultFromSearch = await generateCsv( + context, + requestObject, + visType as string, + panel, + jobParams + ); + + ({ + result: { content, maxSizeReached, size }, + } = generateResults); + } catch (err) { + jobLogger.error(`Generate CSV Error! ${err}`); + throw err; + } + + if (maxSizeReached) { + jobLogger.warn(`Max size reached: CSV output truncated to ${size} bytes`); + } + + return { + content_type: CONTENT_TYPE_CSV, + content, + max_size_reached: maxSizeReached, + size, + }; + }; +}; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv.ts new file mode 100644 index 0000000000000..dd0fb34668e9e --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { badRequest } from 'boom'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { ReportingCore } from '../../../..'; +import { LevelLogger } from '../../../../lib'; +import { FakeRequest, JobParamsPanelCsv, SearchPanel, VisPanel } from '../../types'; +import { generateCsvSearch } from './generate_csv_search'; + +export function createGenerateCsv(reporting: ReportingCore, logger: LevelLogger) { + return async function generateCsv( + context: RequestHandlerContext, + request: KibanaRequest | FakeRequest, + visType: string, + panel: VisPanel | SearchPanel, + jobParams: JobParamsPanelCsv + ) { + // This should support any vis type that is able to fetch + // and model data on the server-side + + // This structure will not be needed when the vis data just consists of an + // expression that we could run through the interpreter to get csv + switch (visType) { + case 'search': + return await generateCsvSearch( + reporting, + context, + request as KibanaRequest, + panel as SearchPanel, + jobParams, + logger + ); + default: + throw badRequest(`Unsupported or unrecognized saved object type: ${visType}`); + } + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts similarity index 80% rename from x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts rename to x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index 506208e4e1aad..3f997a703bef1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -4,18 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IUiSettingsClient, KibanaRequest } from '../../../../../../../../src/core/server'; +import { ReportingCore } from '../../../../'; +import { + IUiSettingsClient, + KibanaRequest, + RequestHandlerContext, +} from '../../../../../../../../src/core/server'; import { esQuery, EsQueryConfig, Filter, IIndexPattern, Query, + UI_SETTINGS, } from '../../../../../../../../src/plugins/data/server'; -import { CancellationToken } from '../../../../../../../plugins/reporting/common'; -import { ReportingCore } from '../../../../server'; -import { LevelLogger } from '../../../../server/lib'; -import { RequestFacade } from '../../../../server/types'; +import { + CSV_SEPARATOR_SETTING, + CSV_QUOTE_VALUES_SETTING, +} from '../../../../../../../../src/plugins/share/server'; +import { CancellationToken } from '../../../../../common'; +import { LevelLogger } from '../../../../lib'; import { createGenerateCsv } from '../../../csv/server/lib/generate_csv'; import { CsvResultFromSearch, @@ -23,21 +31,15 @@ import { JobParamsDiscoverCsv, SearchRequest, } from '../../../csv/types'; -import { - IndexPatternField, - QueryFilter, - SavedSearchObjectAttributes, - SearchPanel, - SearchSource, -} from '../../types'; +import { IndexPatternField, QueryFilter, SearchPanel, SearchSource } from '../../types'; import { getDataSource } from './get_data_source'; import { getFilters } from './get_filters'; const getEsQueryConfig = async (config: IUiSettingsClient) => { const configs = await Promise.all([ - config.get('query:allowLeadingWildcards'), - config.get('query:queryString:options'), - config.get('courier:ignoreFilterIfFieldNotInIndex'), + config.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS), + config.get(UI_SETTINGS.QUERY_STRING_OPTIONS), + config.get(UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX), ]); const [allowLeadingWildcards, queryStringOptions, ignoreFilterIfFieldNotInIndex] = configs; return { @@ -48,23 +50,25 @@ const getEsQueryConfig = async (config: IUiSettingsClient) => { }; const getUiSettings = async (config: IUiSettingsClient) => { - const configs = await Promise.all([config.get('csv:separator'), config.get('csv:quoteValues')]); + const configs = await Promise.all([ + config.get(CSV_SEPARATOR_SETTING), + config.get(CSV_QUOTE_VALUES_SETTING), + ]); const [separator, quoteValues] = configs; return { separator, quoteValues }; }; export async function generateCsvSearch( - req: RequestFacade, reporting: ReportingCore, - logger: LevelLogger, + context: RequestHandlerContext, + req: KibanaRequest, searchPanel: SearchPanel, - jobParams: JobParamsDiscoverCsv + jobParams: JobParamsDiscoverCsv, + logger: LevelLogger ): Promise { - const savedObjectsClient = await reporting.getSavedObjectsClient( - KibanaRequest.from(req.getRawRequest()) - ); + const savedObjectsClient = context.core.savedObjects.client; const { indexPatternSavedObjectId, timerange } = searchPanel; - const savedSearchObjectAttr = searchPanel.attributes as SavedSearchObjectAttributes; + const savedSearchObjectAttr = searchPanel.attributes; const { indexPatternSavedObject } = await getDataSource( savedObjectsClient, indexPatternSavedObjectId @@ -153,9 +157,7 @@ export async function generateCsvSearch( const config = reporting.getConfig(); const elasticsearch = await reporting.getElasticsearchService(); - const { callAsCurrentUser } = elasticsearch.dataClient.asScoped( - KibanaRequest.from(req.getRawRequest()) - ); + const { callAsCurrentUser } = elasticsearch.legacy.client.asScoped(req); const callCluster = (...params: [string, object]) => callAsCurrentUser(...params); const uiSettings = await getUiSettings(uiConfig); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_data_source.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_data_source.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_data_source.ts rename to x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_data_source.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.test.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_filters.test.ts similarity index 98% rename from x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.test.ts rename to x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_filters.test.ts index 110ce91ddfd79..b5d564d93d0d6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_filters.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TimeRangeParams } from '../../../../server/types'; +import { TimeRangeParams } from '../../../../types'; import { QueryFilter, SavedSearchObjectAttributes, SearchSourceFilter } from '../../types'; import { getFilters } from './get_filters'; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_filters.ts similarity index 93% rename from x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.ts rename to x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_filters.ts index 071427f4dab64..26631548cc797 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_filters.ts @@ -6,7 +6,7 @@ import { badRequest } from 'boom'; import moment from 'moment-timezone'; -import { TimeRangeParams } from '../../../../server/types'; +import { TimeRangeParams } from '../../../../types'; import { Filter, QueryFilter, SavedSearchObjectAttributes, SearchSourceFilter } from '../../types'; export function getFilters( @@ -22,7 +22,7 @@ export function getFilters( let timezone: string | null; if (indexPatternTimeField) { - if (!timerange) { + if (!timerange || !timerange.min || !timerange.max) { throw badRequest( `Time range params are required for index pattern [${indexPatternId}], using time field [${indexPatternTimeField}]` ); diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts new file mode 100644 index 0000000000000..5aed02c10b961 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaRequest } from 'src/core/server'; +import { JobParamsPanelCsv, JobParamsPostPayloadPanelCsv } from '../../types'; + +export function getJobParamsFromRequest( + request: KibanaRequest, + opts: { isImmediate: boolean } +): JobParamsPanelCsv { + const { savedObjectType, savedObjectId } = request.params as { + savedObjectType: string; + savedObjectId: string; + }; + const { timerange, state } = request.body as JobParamsPostPayloadPanelCsv; + + const post = timerange || state ? { timerange, state } : undefined; + + return { + isImmediate: opts.isImmediate, + savedObjectType, + savedObjectId, + post, + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/index.ts rename to x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/index.ts diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts new file mode 100644 index 0000000000000..36ae5b1dac05e --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { JobParamPostPayload, JobDocPayload, TimeRangeParams } from '../../types'; + +export interface FakeRequest { + headers: Record; +} + +export interface JobParamsPostPayloadPanelCsv extends JobParamPostPayload { + state?: any; +} + +export interface JobParamsPanelCsv { + savedObjectType: string; + savedObjectId: string; + isImmediate: boolean; + panel?: SearchPanel; + post?: JobParamsPostPayloadPanelCsv; + visType?: string; +} + +export interface JobDocPayloadPanelCsv extends JobDocPayload { + jobParams: JobParamsPanelCsv; +} + +export interface SavedObjectServiceError { + statusCode: number; + error?: string; + message?: string; +} + +export interface SavedObjectMetaJSON { + searchSourceJSON: string; +} + +export interface SavedObjectMeta { + searchSource: SearchSource; +} + +export interface SavedSearchObjectAttributesJSON { + title: string; + sort: any[]; + columns: string[]; + kibanaSavedObjectMeta: SavedObjectMetaJSON; + uiState: any; +} + +export interface SavedSearchObjectAttributes { + title: string; + sort: any[]; + columns?: string[]; + kibanaSavedObjectMeta: SavedObjectMeta; + uiState: any; +} + +export interface VisObjectAttributesJSON { + title: string; + visState: string; // JSON string + type: string; + params: any; + uiStateJSON: string; // also JSON string + aggs: any[]; + sort: any[]; + kibanaSavedObjectMeta: SavedObjectMeta; +} + +export interface VisObjectAttributes { + title: string; + visState: string; // JSON string + type: string; + params: any; + uiState: { + vis: { + params: { + sort: { + columnIndex: string; + direction: string; + }; + }; + }; + }; + aggs: any[]; + sort: any[]; + kibanaSavedObjectMeta: SavedObjectMeta; +} + +export interface SavedObjectReference { + name: string; // should be kibanaSavedObjectMeta.searchSourceJSON.index + type: string; // should be index-pattern + id: string; +} + +export interface SavedObject { + attributes: any; + references: SavedObjectReference[]; +} + +/* This object is passed to different helpers in different parts of the code + - packages/kbn-es-query/src/es_query/build_es_query + The structure has redundant parts and json-parsed / json-unparsed versions of the same data + */ +export interface IndexPatternSavedObject { + title: string; + timeFieldName: string; + fields: any[]; + attributes: { + fieldFormatMap: string; + fields: string; + }; +} + +export interface VisPanel { + indexPatternSavedObjectId?: string; + savedSearchObjectId?: string; + attributes: VisObjectAttributes; + timerange: TimeRangeParams; +} + +export interface SearchPanel { + indexPatternSavedObjectId: string; + attributes: SavedSearchObjectAttributes; + timerange: TimeRangeParams; +} + +export interface SearchSourceQuery { + isSearchSourceQuery: boolean; +} + +export interface SearchSource { + query: SearchSourceQuery; + filter: any[]; +} + +/* + * These filter types are stub types to help ensure things get passed to + * non-Typescript functions in the right order. An actual structure is not + * needed because the code doesn't look into the properties; just combines them + * and passes them through to other non-TS modules. + */ +export interface Filter { + isFilter: boolean; +} +export interface TimeFilter extends Filter { + isTimeFilter: boolean; +} +export interface QueryFilter extends Filter { + isQueryFilter: boolean; +} +export interface SearchSourceFilter extends Filter { + isSearchSourceFilter: boolean; +} + +export interface IndexPatternField { + scripted: boolean; + lang?: string; + script?: string; + name: string; +} diff --git a/x-pack/plugins/reporting/server/export_types/png/index.ts b/x-pack/plugins/reporting/server/export_types/png/index.ts new file mode 100644 index 0000000000000..a3b51e365e772 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/png/index.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + LICENSE_TYPE_ENTERPRISE, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_TRIAL, + PNG_JOB_TYPE as jobType, +} from '../../../common/constants'; +import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../..//types'; +import { metadata } from './metadata'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { JobDocPayloadPNG, JobParamsPNG } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPNG, + ESQueueCreateJobFn, + JobDocPayloadPNG, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentEncoding: 'base64', + jobContentExtension: 'PNG', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_ENTERPRISE, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/metadata.ts b/x-pack/plugins/reporting/server/export_types/png/metadata.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/png/metadata.ts rename to x-pack/plugins/reporting/server/export_types/png/metadata.ts diff --git a/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts new file mode 100644 index 0000000000000..3f1556fb29782 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { validateUrls } from '../../../../../common/validate_urls'; +import { cryptoFactory } from '../../../../lib'; +import { CreateJobFactory, ESQueueCreateJobFn } from '../../../../types'; +import { JobParamsPNG } from '../../types'; + +export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting) { + const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); + const crypto = cryptoFactory(config.get('encryptionKey')); + + return async function createJob( + { objectType, title, relativeUrl, browserTimezone, layout }, + context, + req + ) { + const serializedEncryptedHeaders = await crypto.encrypt(req.headers); + + validateUrls([relativeUrl]); + + return { + objectType, + title, + relativeUrl, + headers: serializedEncryptedHeaders, + browserTimezone, + layout, + basePath: setupDeps.basePath(req), + forceNow: new Date().toISOString(), + }; + }; +}; diff --git a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts new file mode 100644 index 0000000000000..b92f53ff6563f --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as Rx from 'rxjs'; +import { ReportingCore } from '../../../../'; +import { CancellationToken } from '../../../../../common'; +import { cryptoFactory, LevelLogger } from '../../../../lib'; +import { createMockReportingCore } from '../../../../test_helpers'; +import { JobDocPayloadPNG } from '../../types'; +import { generatePngObservableFactory } from '../lib/generate_png'; +import { executeJobFactory } from './'; + +jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() })); + +let mockReporting: ReportingCore; + +const cancellationToken = ({ + on: jest.fn(), +} as unknown) as CancellationToken; + +const mockLoggerFactory = { + get: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + })), +}; +const getMockLogger = () => new LevelLogger(mockLoggerFactory); + +const mockEncryptionKey = 'abcabcsecuresecret'; +const encryptHeaders = async (headers: Record) => { + const crypto = cryptoFactory(mockEncryptionKey); + return await crypto.encrypt(headers); +}; + +const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPNG; + +beforeEach(async () => { + const kbnConfig = { + 'server.basePath': '/sbp', + }; + const reportingConfig = { + index: '.reporting-2018.10.10', + encryptionKey: mockEncryptionKey, + 'kibanaServer.hostname': 'localhost', + 'kibanaServer.port': 5601, + 'kibanaServer.protocol': 'http', + 'queue.indexInterval': 'daily', + 'queue.timeout': Infinity, + }; + const mockReportingConfig = { + get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')], + kbnConfig: { get: (...keys: string[]) => (kbnConfig as any)[keys.join('.')] }, + }; + + mockReporting = await createMockReportingCore(mockReportingConfig); + + const mockElasticsearch = { + legacy: { + client: { + asScoped: () => ({ callAsCurrentUser: jest.fn() }), + }, + }, + }; + const mockGetElasticsearch = jest.fn(); + mockGetElasticsearch.mockImplementation(() => Promise.resolve(mockElasticsearch)); + mockReporting.getElasticsearchService = mockGetElasticsearch; + // @ts-ignore over-riding config method + mockReporting.config = mockReportingConfig; + + (generatePngObservableFactory as jest.Mock).mockReturnValue(jest.fn()); +}); + +afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset()); + +test(`passes browserTimezone to generatePng`, async () => { + const encryptedHeaders = await encryptHeaders({}); + const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; + generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); + + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const browserTimezone = 'UTC'; + await executeJob( + 'pngJobId', + getJobDocPayload({ + relativeUrl: '/app/kibana#/something', + browserTimezone, + headers: encryptedHeaders, + }), + cancellationToken + ); + + expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + LevelLogger { + "_logger": Object { + "get": [MockFunction], + }, + "_tags": Array [ + "PNG", + "execute", + "pngJobId", + ], + "warning": [Function], + }, + "http://localhost:5601/sbp/app/kibana#/something", + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", + }, + "headers": Object {}, + }, + undefined, + ], + ] + `); +}); + +test(`returns content_type of application/png`, async () => { + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const encryptedHeaders = await encryptHeaders({}); + + const generatePngObservable = await generatePngObservableFactory(mockReporting); + (generatePngObservable as jest.Mock).mockReturnValue(Rx.of('foo')); + + const { content_type: contentType } = await executeJob( + 'pngJobId', + getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), + cancellationToken + ); + expect(contentType).toBe('image/png'); +}); + +test(`returns content of generatePng getBuffer base64 encoded`, async () => { + const testContent = 'raw string from get_screenhots'; + const generatePngObservable = await generatePngObservableFactory(mockReporting); + (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ base64: testContent })); + + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const encryptedHeaders = await encryptHeaders({}); + const { content } = await executeJob( + 'pngJobId', + getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), + cancellationToken + ); + + expect(content).toEqual(testContent); +}); diff --git a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts new file mode 100644 index 0000000000000..ea4c4b1d106ae --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import apm from 'elastic-apm-node'; +import * as Rx from 'rxjs'; +import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; +import { ReportingCore } from '../../../..'; +import { PNG_JOB_TYPE } from '../../../../../common/constants'; +import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../..//types'; +import { LevelLogger } from '../../../../lib'; +import { + decryptJobHeaders, + getConditionalHeaders, + getFullUrls, + omitBlacklistedHeaders, +} from '../../../common/execute_job/'; +import { JobDocPayloadPNG } from '../../types'; +import { generatePngObservableFactory } from '../lib/generate_png'; + +type QueuedPngExecutorFactory = ExecuteJobFactory>; + +export const executeJobFactory: QueuedPngExecutorFactory = async function executeJobFactoryFn( + reporting: ReportingCore, + parentLogger: LevelLogger +) { + const config = reporting.getConfig(); + const encryptionKey = config.get('encryptionKey'); + const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']); + + return async function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) { + const apmTrans = apm.startTransaction('reporting execute_job png', 'reporting'); + const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); + let apmGeneratePng: { end: () => void } | null | undefined; + + const generatePngObservable = await generatePngObservableFactory(reporting); + const jobLogger = logger.clone([jobId]); + const process$: Rx.Observable = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), + map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })), + map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), + mergeMap((conditionalHeaders) => { + const urls = getFullUrls({ config, job }); + const hashUrl = urls[0]; + if (apmGetAssets) apmGetAssets.end(); + + apmGeneratePng = apmTrans?.startSpan('generate_png_pipeline', 'execute'); + return generatePngObservable( + jobLogger, + hashUrl, + job.browserTimezone, + conditionalHeaders, + job.layout + ); + }), + map(({ base64, warnings }) => { + if (apmGeneratePng) apmGeneratePng.end(); + + return { + content_type: 'image/png', + content: base64, + size: (base64 && base64.length) || 0, + + warnings, + }; + }), + catchError((err) => { + jobLogger.error(err); + return Rx.throwError(err); + }) + ); + + const stop$ = Rx.fromEventPattern(cancellationToken.on); + return process$.pipe(takeUntil(stop$)).toPromise(); + }; +}; diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/plugins/reporting/server/export_types/png/server/lib/generate_png.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts rename to x-pack/plugins/reporting/server/export_types/png/server/lib/generate_png.ts index 0ad381885f4c4..d7e9d0f812b37 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/plugins/reporting/server/export_types/png/server/lib/generate_png.ts @@ -7,9 +7,9 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { map } from 'rxjs/operators'; -import { ReportingCore } from '../../../../server'; -import { LevelLogger } from '../../../../server/lib'; -import { ConditionalHeaders, ScreenshotResults } from '../../../../server/types'; +import { ReportingCore } from '../../../../'; +import { LevelLogger } from '../../../../lib'; +import { ConditionalHeaders, ScreenshotResults } from '../../../../types'; import { LayoutParams } from '../../../common/layouts'; import { PreserveLayout } from '../../../common/layouts/preserve_layout'; diff --git a/x-pack/plugins/reporting/server/export_types/png/types.d.ts b/x-pack/plugins/reporting/server/export_types/png/types.d.ts new file mode 100644 index 0000000000000..486a8e91a722f --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/png/types.d.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { JobDocPayload } from '../../../server/types'; +import { LayoutInstance, LayoutParams } from '../common/layouts'; + +// Job params: structure of incoming user request data +export interface JobParamsPNG { + objectType: string; + title: string; + relativeUrl: string; + browserTimezone: string; + layout: LayoutInstance; +} + +// Job payload: structure of stored job data provided by create_job +export interface JobDocPayloadPNG extends JobDocPayload { + basePath?: string; + browserTimezone: string; + forceNow?: string; + layout: LayoutParams; + relativeUrl: string; +} diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts new file mode 100644 index 0000000000000..39a0cbd5270a1 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + LICENSE_TYPE_ENTERPRISE, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_TRIAL, + PDF_JOB_TYPE as jobType, +} from '../../../common/constants'; +import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types'; +import { metadata } from './metadata'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { JobDocPayloadPDF, JobParamsPDF } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPDF, + ESQueueCreateJobFn, + JobDocPayloadPDF, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + LICENSE_TYPE_ENTERPRISE, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/metadata.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/metadata.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/metadata.ts rename to x-pack/plugins/reporting/server/export_types/printable_pdf/metadata.ts diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts new file mode 100644 index 0000000000000..06a0902a56954 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { validateUrls } from '../../../../../common/validate_urls'; +import { cryptoFactory } from '../../../../lib'; +import { CreateJobFactory, ESQueueCreateJobFn } from '../../../../types'; +import { JobParamsPDF } from '../../types'; + +export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting) { + const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); + const crypto = cryptoFactory(config.get('encryptionKey')); + + return async function createJobFn( + { title, relativeUrls, browserTimezone, layout, objectType }: JobParamsPDF, + context, + req + ) { + const serializedEncryptedHeaders = await crypto.encrypt(req.headers); + + validateUrls(relativeUrls); + + return { + basePath: setupDeps.basePath(req), + browserTimezone, + forceNow: new Date().toISOString(), + headers: serializedEncryptedHeaders, + layout, + relativeUrls, + title, + objectType, + }; + }; +}; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts new file mode 100644 index 0000000000000..b8e1e5eebd9e7 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() })); + +import * as Rx from 'rxjs'; +import { ReportingCore } from '../../../../'; +import { CancellationToken } from '../../../../../common'; +import { cryptoFactory, LevelLogger } from '../../../../lib'; +import { createMockReportingCore } from '../../../../test_helpers'; +import { JobDocPayloadPDF } from '../../types'; +import { generatePdfObservableFactory } from '../lib/generate_pdf'; +import { executeJobFactory } from './'; + +let mockReporting: ReportingCore; + +const cancellationToken = ({ + on: jest.fn(), +} as unknown) as CancellationToken; + +const mockLoggerFactory = { + get: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + })), +}; +const getMockLogger = () => new LevelLogger(mockLoggerFactory); + +const mockEncryptionKey = 'testencryptionkey'; +const encryptHeaders = async (headers: Record) => { + const crypto = cryptoFactory(mockEncryptionKey); + return await crypto.encrypt(headers); +}; + +const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPDF; + +beforeEach(async () => { + const kbnConfig = { + 'server.basePath': '/sbp', + }; + const reportingConfig = { + index: '.reports-test', + encryptionKey: mockEncryptionKey, + 'kibanaServer.hostname': 'localhost', + 'kibanaServer.port': 5601, + 'kibanaServer.protocol': 'http', + }; + const mockReportingConfig = { + get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')], + kbnConfig: { get: (...keys: string[]) => (kbnConfig as any)[keys.join('.')] }, + }; + + mockReporting = await createMockReportingCore(mockReportingConfig); + + const mockElasticsearch = { + legacy: { + client: { + asScoped: () => ({ callAsCurrentUser: jest.fn() }), + }, + }, + }; + const mockGetElasticsearch = jest.fn(); + mockGetElasticsearch.mockImplementation(() => Promise.resolve(mockElasticsearch)); + mockReporting.getElasticsearchService = mockGetElasticsearch; + // @ts-ignore over-riding config + mockReporting.config = mockReportingConfig; + + (generatePdfObservableFactory as jest.Mock).mockReturnValue(jest.fn()); +}); + +afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset()); + +test(`passes browserTimezone to generatePdf`, async () => { + const encryptedHeaders = await encryptHeaders({}); + const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; + generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); + + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const browserTimezone = 'UTC'; + await executeJob( + 'pdfJobId', + getJobDocPayload({ + relativeUrl: '/app/kibana#/something', + browserTimezone, + headers: encryptedHeaders, + }), + cancellationToken + ); + + expect(generatePdfObservable.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + LevelLogger { + "_logger": Object { + "get": [MockFunction], + }, + "_tags": Array [ + "printable_pdf", + "execute", + "pdfJobId", + ], + "warning": [Function], + }, + undefined, + Array [ + "http://localhost:5601/sbp/app/kibana#/something", + ], + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", + }, + "headers": Object {}, + }, + undefined, + false, + ], + ] + `); +}); + +test(`returns content_type of application/pdf`, async () => { + const logger = getMockLogger(); + const executeJob = await executeJobFactory(mockReporting, logger); + const encryptedHeaders = await encryptHeaders({}); + + const generatePdfObservable = await generatePdfObservableFactory(mockReporting); + (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); + + const { content_type: contentType } = await executeJob( + 'pdfJobId', + getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }), + cancellationToken + ); + expect(contentType).toBe('application/pdf'); +}); + +test(`returns content of generatePdf getBuffer base64 encoded`, async () => { + const testContent = 'test content'; + const generatePdfObservable = await generatePdfObservableFactory(mockReporting); + (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); + + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const encryptedHeaders = await encryptHeaders({}); + const { content } = await executeJob( + 'pdfJobId', + getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }), + cancellationToken + ); + + expect(content).toEqual(Buffer.from(testContent).toString('base64')); +}); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts new file mode 100644 index 0000000000000..a4d84b2f9f1e0 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import apm from 'elastic-apm-node'; +import * as Rx from 'rxjs'; +import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; +import { ReportingCore } from '../../../..'; +import { PDF_JOB_TYPE } from '../../../../../common/constants'; +import { LevelLogger } from '../../../../lib'; +import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../types'; +import { + decryptJobHeaders, + getConditionalHeaders, + getCustomLogo, + getFullUrls, + omitBlacklistedHeaders, +} from '../../../common/execute_job'; +import { JobDocPayloadPDF } from '../../types'; +import { generatePdfObservableFactory } from '../lib/generate_pdf'; + +type QueuedPdfExecutorFactory = ExecuteJobFactory>; + +export const executeJobFactory: QueuedPdfExecutorFactory = async function executeJobFactoryFn( + reporting: ReportingCore, + parentLogger: LevelLogger +) { + const config = reporting.getConfig(); + const encryptionKey = config.get('encryptionKey'); + + const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']); + + return async function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) { + const apmTrans = apm.startTransaction('reporting execute_job pdf', 'reporting'); + const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); + let apmGeneratePdf: { end: () => void } | null | undefined; + + const generatePdfObservable = await generatePdfObservableFactory(reporting); + + const jobLogger = logger.clone([jobId]); + const process$: Rx.Observable = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), + map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })), + map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), + mergeMap((conditionalHeaders) => + getCustomLogo({ reporting, config, job, conditionalHeaders }) + ), + mergeMap(({ logo, conditionalHeaders }) => { + const urls = getFullUrls({ config, job }); + + const { browserTimezone, layout, title } = job; + if (apmGetAssets) apmGetAssets.end(); + + apmGeneratePdf = apmTrans?.startSpan('generate_pdf_pipeline', 'execute'); + return generatePdfObservable( + jobLogger, + title, + urls, + browserTimezone, + conditionalHeaders, + layout, + logo + ); + }), + map(({ buffer, warnings }) => { + if (apmGeneratePdf) apmGeneratePdf.end(); + + const apmEncode = apmTrans?.startSpan('encode_pdf', 'output'); + const content = buffer?.toString('base64') || null; + if (apmEncode) apmEncode.end(); + + return { + content_type: 'application/pdf', + content, + size: buffer?.byteLength || 0, + warnings, + }; + }), + catchError((err) => { + jobLogger.error(err); + return Rx.throwError(err); + }) + ); + + const stop$ = Rx.fromEventPattern(cancellationToken.on); + + if (apmTrans) apmTrans.end(); + return process$.pipe(takeUntil(stop$)).toPromise(); + }; +}; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/generate_pdf.ts similarity index 92% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/generate_pdf.ts index 0d23eb62fe37c..366949a033757 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -7,16 +7,16 @@ import { groupBy } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; -import { ReportingCore } from '../../../../server'; -import { LevelLogger } from '../../../../server/lib'; -import { ConditionalHeaders, ScreenshotResults } from '../../../../server/types'; +import { ReportingCore } from '../../../../'; +import { LevelLogger } from '../../../../lib'; +import { ConditionalHeaders, ScreenshotResults } from '../../../../types'; import { createLayout, LayoutInstance, LayoutParams } from '../../../common/layouts'; // @ts-ignore untyped module import { pdf } from './pdf'; import { getTracker } from './tracker'; const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { - const grouped = groupBy(urlScreenshots.map(u => u.timeRange)); + const grouped = groupBy(urlScreenshots.map((u) => u.timeRange)); const values = Object.values(grouped); if (values.length === 1) { return values[0][0]; @@ -65,8 +65,8 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) { } tracker.endSetup(); - results.forEach(r => { - r.screenshots.forEach(screenshot => { + results.forEach((r) => { + r.screenshots.forEach((screenshot) => { logger.debug(`Adding image to PDF. Image base64 size: ${screenshot.base64EncodedData?.length || 0}`); // prettier-ignore tracker.startAddImage(); tracker.endAddImage(); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/LICENSE_OFL.txt b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/LICENSE_OFL.txt similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/LICENSE_OFL.txt rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/LICENSE_OFL.txt diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Medium.ttf b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Medium.ttf similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Medium.ttf rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Medium.ttf diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Regular.ttf b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Regular.ttf similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Regular.ttf rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Regular.ttf diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/index.js b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/index.js similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/index.js rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/index.js diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/LICENSE.txt b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/LICENSE.txt similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/LICENSE.txt rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/LICENSE.txt diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Italic.ttf b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Italic.ttf similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Italic.ttf rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Italic.ttf diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Medium.ttf b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Medium.ttf similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Medium.ttf rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Medium.ttf diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Regular.ttf b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Regular.ttf similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Regular.ttf rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Regular.ttf diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/img/logo-grey.png b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/img/logo-grey.png similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/img/logo-grey.png rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/img/logo-grey.png diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/index.js b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/index.js new file mode 100644 index 0000000000000..f9a9d9d85bfd3 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/index.js @@ -0,0 +1,319 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import path from 'path'; +import _ from 'lodash'; +import concat from 'concat-stream'; +import Printer from 'pdfmake'; +import xRegExp from 'xregexp'; +import { i18n } from '@kbn/i18n'; + +const assetPath = path.resolve(__dirname, 'assets'); + +const tableBorderWidth = 1; + +function getFont(text) { + // Once unicode regex scripts are fully supported we should be able to get rid of the dependency + // on xRegExp library. See https://github.com/tc39/proposal-regexp-unicode-property-escapes + // for more information. We are matching Han characters which is one of the supported unicode scripts + // (you can see the full list of supported scripts here: http://www.unicode.org/standard/supported.html). + // This will match Chinese, Japanese, Korean and some other Asian languages. + const isCKJ = xRegExp('\\p{Han}').test(text, 'g'); + if (isCKJ) { + return 'noto-cjk'; + } else { + return 'Roboto'; + } +} + +class PdfMaker { + constructor(layout, logo) { + const fontPath = (filename) => path.resolve(assetPath, 'fonts', filename); + const fonts = { + Roboto: { + normal: fontPath('roboto/Roboto-Regular.ttf'), + bold: fontPath('roboto/Roboto-Medium.ttf'), + italics: fontPath('roboto/Roboto-Italic.ttf'), + bolditalics: fontPath('roboto/Roboto-Italic.ttf'), + }, + 'noto-cjk': { + // Roboto does not support CJK characters, so we'll fall back on this font if we detect them. + normal: fontPath('noto/NotoSansCJKtc-Regular.ttf'), + bold: fontPath('noto/NotoSansCJKtc-Medium.ttf'), + italics: fontPath('noto/NotoSansCJKtc-Regular.ttf'), + bolditalics: fontPath('noto/NotoSansCJKtc-Medium.ttf'), + }, + }; + + this._layout = layout; + this._logo = logo; + this._title = ''; + this._content = []; + this._printer = new Printer(fonts); + } + + _addContents(contents) { + const groupCount = this._content.length; + + // inject a page break for every 2 groups on the page + if (groupCount > 0 && groupCount % this._layout.groupCount === 0) { + contents = [ + { + text: '', + pageBreak: 'after', + }, + ].concat(contents); + } + this._content.push(contents); + } + + addImage(base64EncodedData, { title = '', description = '' }) { + const contents = []; + + if (title && title.length > 0) { + contents.push({ + text: title, + style: 'heading', + font: getFont(title), + noWrap: true, + }); + } + + if (description && description.length > 0) { + contents.push({ + text: description, + style: 'subheading', + font: getFont(description), + noWrap: true, + }); + } + + const img = { + image: `data:image/png;base64,${base64EncodedData}`, + alignment: 'center', + }; + + const size = this._layout.getPdfImageSize(); + img.height = size.height; + img.width = size.width; + + const wrappedImg = { + table: { + body: [[img]], + }, + layout: 'simpleBorder', + }; + + contents.push(wrappedImg); + + this._addContents(contents); + } + + addHeading(headingText, opts = {}) { + const contents = []; + contents.push({ + text: headingText, + style: ['heading'].concat(opts.styles || []), + font: getFont(headingText), + }); + this._addContents(contents); + } + + setTitle(title) { + this._title = title; + } + + generate() { + const docTemplate = _.assign(getTemplate(this._layout, this._logo, this._title), { + content: this._content, + }); + this._pdfDoc = this._printer.createPdfKitDocument(docTemplate, getDocOptions()); + return this; + } + + getBuffer() { + if (!this._pdfDoc) { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.printablePdf.documentStreamIsNotgeneratedErrorMessage', + { + defaultMessage: 'Document stream has not been generated', + } + ) + ); + } + return new Promise((resolve, reject) => { + const concatStream = concat(function (pdfBuffer) { + resolve(pdfBuffer); + }); + + this._pdfDoc.on('error', reject); + this._pdfDoc.pipe(concatStream); + this._pdfDoc.end(); + }); + } + + getStream() { + if (!this._pdfDoc) { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.printablePdf.documentStreamIsNotgeneratedErrorMessage', + { + defaultMessage: 'Document stream has not been generated', + } + ) + ); + } + this._pdfDoc.end(); + return this._pdfDoc; + } +} + +function getTemplate(layout, logo, title) { + const pageMarginTop = 40; + const pageMarginBottom = 80; + const pageMarginWidth = 40; + const headingFontSize = 14; + const headingMarginTop = 10; + const headingMarginBottom = 5; + const headingHeight = headingFontSize * 1.5 + headingMarginTop + headingMarginBottom; + const subheadingFontSize = 12; + const subheadingMarginTop = 0; + const subheadingMarginBottom = 5; + const subheadingHeight = subheadingFontSize * 1.5 + subheadingMarginTop + subheadingMarginBottom; + + return { + // define page size + pageOrientation: layout.getPdfPageOrientation(), + pageSize: layout.getPdfPageSize({ + pageMarginTop, + pageMarginBottom, + pageMarginWidth, + tableBorderWidth, + headingHeight, + subheadingHeight, + }), + pageMargins: [pageMarginWidth, pageMarginTop, pageMarginWidth, pageMarginBottom], + + header: function () { + return { + margin: [pageMarginWidth, pageMarginTop / 4, pageMarginWidth, 0], + text: title, + font: getFont(title), + style: { + color: '#aaa', + }, + fontSize: 10, + alignment: 'center', + }; + }, + + footer: function (currentPage, pageCount) { + const logoPath = path.resolve(assetPath, 'img', 'logo-grey.png'); + return { + margin: [pageMarginWidth, pageMarginBottom / 4, pageMarginWidth, 0], + layout: 'noBorder', + table: { + widths: [100, '*', 100], + body: [ + [ + { + fit: [100, 35], + image: logo || logoPath, + }, + { + alignment: 'center', + text: i18n.translate('xpack.reporting.exportTypes.printablePdf.pagingDescription', { + defaultMessage: 'Page {currentPage} of {pageCount}', + values: { currentPage: currentPage.toString(), pageCount }, + }), + style: { + color: '#aaa', + }, + }, + '', + ], + [ + logo + ? { + text: i18n.translate( + 'xpack.reporting.exportTypes.printablePdf.logoDescription', + { + defaultMessage: 'Powered by Elastic', + } + ), + fontSize: 10, + style: { + color: '#aaa', + }, + margin: [0, 2, 0, 0], + } + : '', + '', + '', + ], + ], + }, + }; + }, + + styles: { + heading: { + alignment: 'left', + fontSize: headingFontSize, + bold: true, + marginTop: headingMarginTop, + marginBottom: headingMarginBottom, + }, + subheading: { + alignment: 'left', + fontSize: subheadingFontSize, + italics: true, + marginLeft: 20, + marginBottom: subheadingMarginBottom, + }, + warning: { + color: '#f39c12', // same as @brand-warning in Kibana colors.less + }, + }, + + defaultStyle: { + fontSize: 12, + font: 'Roboto', + }, + }; +} + +function getDocOptions() { + return { + tableLayouts: { + noBorder: { + // format is function (i, node) { ... }; + hLineWidth: () => 0, + vLineWidth: () => 0, + paddingLeft: () => 0, + paddingRight: () => 0, + paddingTop: () => 0, + paddingBottom: () => 0, + }, + simpleBorder: { + // format is function (i, node) { ... }; + hLineWidth: () => tableBorderWidth, + vLineWidth: () => tableBorderWidth, + hLineColor: () => 'silver', + vLineColor: () => 'silver', + paddingLeft: () => 0, + paddingRight: () => 0, + paddingTop: () => 0, + paddingBottom: () => 0, + }, + }, + }; +} + +export const pdf = { + create: (layout, logo) => new PdfMaker(layout, logo), +}; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/tracker.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/tracker.ts rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/tracker.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/uri_encode.js b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/uri_encode.js similarity index 91% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/uri_encode.js rename to x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/uri_encode.js index f764271c22a2d..d057cfba4ef30 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/uri_encode.js +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/uri_encode.js @@ -9,9 +9,9 @@ import { url } from '../../../../../../../../src/plugins/kibana_utils/server'; function toKeyValue(obj) { const parts = []; - forEach(obj, function(value, key) { + forEach(obj, function (value, key) { if (isArray(value)) { - forEach(value, function(arrayValue) { + forEach(value, function (arrayValue) { const keyStr = url.encodeUriQuery(key, true); const valStr = arrayValue === true ? '' : '=' + url.encodeUriQuery(arrayValue, true); parts.push(keyStr + valStr); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts new file mode 100644 index 0000000000000..087ef5a6ca82c --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { JobDocPayload } from '../../../server/types'; +import { LayoutInstance, LayoutParams } from '../common/layouts'; + +// Job params: structure of incoming user request data, after being parsed from RISON +export interface JobParamsPDF { + objectType: string; // visualization, dashboard, etc. Used for job info & telemetry + title: string; + relativeUrls: string[]; + browserTimezone: string; + layout: LayoutInstance; +} + +// Job payload: structure of stored job data provided by create_job +export interface JobDocPayloadPDF extends JobDocPayload { + basePath?: string; + browserTimezone: string; + forceNow?: string; + layout: LayoutParams; + relativeUrls: string[]; +} diff --git a/x-pack/plugins/reporting/server/index.ts b/x-pack/plugins/reporting/server/index.ts index 9d34eba70d0f4..c78e042d019e8 100644 --- a/x-pack/plugins/reporting/server/index.ts +++ b/x-pack/plugins/reporting/server/index.ts @@ -4,11 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; +import { PluginInitializerContext } from 'kibana/server'; import { ReportingPlugin } from './plugin'; +import { ReportingConfigType } from './config'; -export { config, ConfigSchema, ConfigType } from './config'; -export { PluginsSetup } from './plugin'; +export const plugin = (initContext: PluginInitializerContext) => + new ReportingPlugin(initContext); -export const plugin = (initializerContext: PluginInitializerContext) => - new ReportingPlugin(initializerContext); +export { ReportingPlugin as Plugin }; +export { config } from './config'; +export { ReportingSetupDeps as PluginSetup } from './types'; +export { ReportingStartDeps as PluginStart } from './types'; + +// internal imports +export { ReportingCore } from './core'; +export { ReportingConfig } from './config/config'; diff --git a/x-pack/plugins/reporting/server/lib/__tests__/export_types_registry.js b/x-pack/plugins/reporting/server/lib/__tests__/export_types_registry.js new file mode 100644 index 0000000000000..3f8b7e7e75af1 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/__tests__/export_types_registry.js @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { ExportTypesRegistry } from '../export_types_registry'; + +describe('ExportTypesRegistry', function () { + let exportTypesRegistry; + beforeEach(function () { + exportTypesRegistry = new ExportTypesRegistry(); + }); + + describe('register', function () { + it(`doesn't throw an Error when using a new type with a string id`, function () { + expect(() => { + exportTypesRegistry.register({ id: 'foo' }); + }).to.not.throwError(); + }); + + it('throws an Error when registering a type without an id', function () { + expect(() => { + exportTypesRegistry.register({}); + }).to.throwError(); + }); + + it('throws an Error when registering a type with an integer id', function () { + expect(() => { + exportTypesRegistry.register({ id: 1 }); + }).to.throwError(); + }); + + it('throws an Error when registering the same id twice', function () { + const id = 'foo'; + expect(() => { + exportTypesRegistry.register({ id }); + }).to.not.throwError(); + + expect(() => { + exportTypesRegistry.register({ id }); + }).to.throwError(); + }); + }); + + describe('getById', function () { + it('returns the same object that was registered', function () { + const id = 'foo'; + const obj = { id }; + exportTypesRegistry.register(obj); + exportTypesRegistry.register({ id: 'bar' }); + expect(exportTypesRegistry.getById(id)).to.be(obj); + }); + + it(`throws an Error if the id isn't found`, function () { + expect(() => { + exportTypesRegistry.getById('foo'); + }).to.throwError(); + }); + }); + + describe('getAll', function () { + it('returns an empty Iterator if no objects have been registered', function () { + const array = Array.from(exportTypesRegistry.getAll()); + expect(array.length).to.be(0); + }); + + it('returns all objects that have been registered', function () { + const obj1 = { id: 'foo' }; + const obj2 = { id: 'bar' }; + const objs = [obj1, obj2]; + objs.forEach((obj) => exportTypesRegistry.register(obj)); + const all = Array.from(exportTypesRegistry.getAll()); + expect(all).to.contain(obj1); + expect(all).to.contain(obj2); + }); + }); + + describe('getSize', function () { + it('returns 0 initially', function () { + const size = exportTypesRegistry.getSize(); + expect(size).to.be(0); + }); + + it('returns the number of objects that have been added', function () { + exportTypesRegistry.register({ id: 'foo' }); + exportTypesRegistry.register({ id: 'bar' }); + exportTypesRegistry.register({ id: 'baz' }); + const size = exportTypesRegistry.getSize(); + expect(size).to.be(3); + }); + }); + + describe('get', function () { + it('returns obj that matches the predicate', function () { + const prop = 'fooProp'; + const match = { id: 'foo', prop }; + [match, { id: 'bar' }, { id: 'baz' }].forEach((obj) => exportTypesRegistry.register(obj)); + expect(exportTypesRegistry.get((item) => item.prop === prop)).to.be(match); + }); + + it('throws Error if multiple items match predicate', function () { + const prop = 'fooProp'; + [ + { id: 'foo', prop }, + { id: 'bar', prop }, + ].forEach((obj) => exportTypesRegistry.register(obj)); + expect(() => { + exportTypesRegistry.get((item) => item.prop === prop); + }).to.throwError(); + }); + + it('throws Error if no items match predicate', function () { + const prop = 'fooProp'; + [ + { id: 'foo', prop }, + { id: 'bar', prop }, + ].forEach((obj) => exportTypesRegistry.register(obj)); + expect(() => { + exportTypesRegistry.get((item) => item.prop !== prop); + }).to.throwError(); + }); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/check_license.test.ts b/x-pack/plugins/reporting/server/lib/check_license.test.ts new file mode 100644 index 0000000000000..aa9f86533ef61 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/check_license.test.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { checkLicense } from './check_license'; +import { ILicense } from '../../../licensing/server'; +import { ExportTypesRegistry } from './export_types_registry'; + +describe('check_license', () => { + let exportTypesRegistry: ExportTypesRegistry; + let license: ILicense; + + beforeEach(() => { + exportTypesRegistry = ({ + getAll: () => [], + } as unknown) as ExportTypesRegistry; + }); + + describe('license information is not ready', () => { + beforeEach(() => { + exportTypesRegistry = ({ + getAll: () => [{ id: 'csv' }], + } as unknown) as ExportTypesRegistry; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, undefined).management.showLinks).toEqual(true); + }); + + it('should set csv.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, undefined).csv.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, undefined).management.enableLinks).toEqual(false); + }); + + it('should set csv.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, undefined).csv.enableLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, undefined).management.jobTypes).toEqual(undefined); + }); + }); + + describe('license information is not available', () => { + beforeEach(() => { + license = { + type: undefined, + } as ILicense; + exportTypesRegistry = ({ + getAll: () => [{ id: 'csv' }], + } as unknown) as ExportTypesRegistry; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should set csv.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).csv.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).management.enableLinks).toEqual(false); + }); + + it('should set csv.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).csv.enableLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual(undefined); + }); + }); + + describe('license information is available', () => { + beforeEach(() => { + license = {} as ILicense; + }); + + describe('& license is > basic', () => { + beforeEach(() => { + license.type = 'gold'; + exportTypesRegistry = ({ + getAll: () => [{ id: 'pdf', validLicenses: ['gold'], jobType: 'printable_pdf' }], + } as unknown) as ExportTypesRegistry; + }); + + describe('& license is active', () => { + beforeEach(() => (license.isActive = true)); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should setpdf.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.enableLinks).toEqual(true); + }); + + it('should setpdf.enableLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.enableLinks).toEqual(true); + }); + + it('should set management.jobTypes to contain testJobType', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toContain( + 'printable_pdf' + ); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => { + license.isActive = false; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should set pdf.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).management.enableLinks).toEqual(false); + }); + + it('should set pdf.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.enableLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual(undefined); + }); + }); + }); + + describe('& license is basic', () => { + beforeEach(() => { + license.type = 'basic'; + exportTypesRegistry = ({ + getAll: () => [{ id: 'pdf', validLicenses: ['gold'], jobType: 'printable_pdf' }], + } as unknown) as ExportTypesRegistry; + }); + + describe('& license is active', () => { + beforeEach(() => { + license.isActive = true; + }); + + it('should set management.showLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(false); + }); + + it('should set test.showLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(false); + }); + + it('should set management.jobTypes to an empty array', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual([]); + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toHaveLength(0); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => { + license.isActive = false; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should set test.showLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual(undefined); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/check_license.ts b/x-pack/plugins/reporting/server/lib/check_license.ts new file mode 100644 index 0000000000000..a764aa1f1eec6 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/check_license.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ILicense } from '../../../licensing/server'; +import { ExportTypeDefinition } from '../types'; +import { ExportTypesRegistry } from './export_types_registry'; + +export interface LicenseCheckResult { + showLinks: boolean; + enableLinks: boolean; + message?: string; + jobTypes?: string[]; +} + +const messages = { + getUnavailable: () => { + return 'You cannot use Reporting because license information is not available at this time.'; + }, + getExpired: (license: ILicense) => { + return `You cannot use Reporting because your ${license.type} license has expired.`; + }, +}; + +const makeManagementFeature = ( + exportTypes: Array> +) => { + return { + id: 'management', + checkLicense: (license?: ILicense) => { + if (!license || !license.type) { + return { + showLinks: true, + enableLinks: false, + message: messages.getUnavailable(), + }; + } + + if (!license.isActive) { + return { + showLinks: true, + enableLinks: false, + message: messages.getExpired(license), + }; + } + + const validJobTypes = exportTypes + .filter((exportType) => exportType.validLicenses.includes(license.type || '')) + .map((exportType) => exportType.jobType); + + return { + showLinks: validJobTypes.length > 0, + enableLinks: validJobTypes.length > 0, + jobTypes: validJobTypes, + }; + }, + }; +}; + +const makeExportTypeFeature = ( + exportType: ExportTypeDefinition +) => { + return { + id: exportType.id, + checkLicense: (license?: ILicense) => { + if (!license || !license.type) { + return { + showLinks: true, + enableLinks: false, + message: messages.getUnavailable(), + }; + } + + if (!exportType.validLicenses.includes(license.type)) { + return { + showLinks: false, + enableLinks: false, + message: `Your ${license.type} license does not support ${exportType.name} Reporting. Please upgrade your license.`, + }; + } + + if (!license.isActive) { + return { + showLinks: true, + enableLinks: false, + message: messages.getExpired(license), + }; + } + + return { + showLinks: true, + enableLinks: true, + }; + }, + }; +}; + +export function checkLicense( + exportTypesRegistry: ExportTypesRegistry, + license: ILicense | undefined +) { + const exportTypes = Array.from(exportTypesRegistry.getAll()); + const reportingFeatures = [ + ...exportTypes.map(makeExportTypeFeature), + makeManagementFeature(exportTypes), + ]; + + return reportingFeatures.reduce((result, feature) => { + result[feature.id] = feature.checkLicense(license); + return result; + }, {} as Record); +} diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/plugins/reporting/server/lib/create_queue.ts similarity index 98% rename from x-pack/legacy/plugins/reporting/server/lib/create_queue.ts rename to x-pack/plugins/reporting/server/lib/create_queue.ts index 2cac4bd654487..d993a17c0b314 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/plugins/reporting/server/lib/create_queue.ts @@ -52,7 +52,7 @@ export async function createQueueFactory( interval: queueIndexInterval, timeout: queueTimeout, dateSeparator: '.', - client: elasticsearch.dataClient, + client: elasticsearch.legacy.client, logger: createTaggedLogger(logger, ['esqueue', 'queue-worker']), }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts b/x-pack/plugins/reporting/server/lib/create_tagged_logger.ts similarity index 76% rename from x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts rename to x-pack/plugins/reporting/server/lib/create_tagged_logger.ts index aaed46e629ccc..775930ec83bdf 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts +++ b/x-pack/plugins/reporting/server/lib/create_tagged_logger.ts @@ -11,13 +11,13 @@ export function createTaggedLogger(logger: LevelLogger, tags: string[]) { const allTags = [...tags, ...additionalTags]; if (allTags.includes('info')) { - const newTags = allTags.filter(t => t !== 'info'); // Ensure 'info' is not included twice + const newTags = allTags.filter((t) => t !== 'info'); // Ensure 'info' is not included twice logger.info(msg, newTags); } else if (allTags.includes('debug')) { - const newTags = allTags.filter(t => t !== 'debug'); + const newTags = allTags.filter((t) => t !== 'debug'); logger.debug(msg, newTags); } else if (allTags.includes('warn') || allTags.includes('warning')) { - const newTags = allTags.filter(t => t !== 'warn' && t !== 'warning'); + const newTags = allTags.filter((t) => t !== 'warn' && t !== 'warning'); logger.warn(msg, newTags); } else { logger.error(msg, allTags); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/plugins/reporting/server/lib/create_worker.test.ts similarity index 96% rename from x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts rename to x-pack/plugins/reporting/server/lib/create_worker.test.ts index 1193091075e3e..8e1174e01aa7f 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/plugins/reporting/server/lib/create_worker.test.ts @@ -6,7 +6,7 @@ import * as sinon from 'sinon'; import { ReportingConfig, ReportingCore } from '../../server'; -import { createMockReportingCore } from '../../test_helpers'; +import { createMockReportingCore } from '../test_helpers'; import { createWorkerFactory } from './create_worker'; // @ts-ignore import { Esqueue } from './esqueue'; @@ -42,6 +42,8 @@ describe('Create Worker', () => { mockConfig = { get: configGetStub, kbnConfig: { get: configGetStub } }; mockReporting = await createMockReportingCore(mockConfig); mockReporting.getExportTypesRegistry = () => getMockExportTypesRegistry(); + // @ts-ignore over-riding config manually + mockReporting.config = mockConfig; client = new ClientMock(); queue = new Esqueue('reporting-queue', { client }); executeJobFactoryStub.reset(); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/plugins/reporting/server/lib/create_worker.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/server/lib/create_worker.ts rename to x-pack/plugins/reporting/server/lib/create_worker.ts index 57bd61aee7195..c9e865668bb30 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/plugins/reporting/server/lib/create_worker.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CancellationToken } from '../../../../../plugins/reporting/common'; +import { CancellationToken } from '../../common'; import { PLUGIN_ID } from '../../common/constants'; import { ReportingCore } from '../../server'; import { LevelLogger } from '../../server/lib'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/crypto.ts b/x-pack/plugins/reporting/server/lib/crypto.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/crypto.ts rename to x-pack/plugins/reporting/server/lib/crypto.ts diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts similarity index 80% rename from x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts rename to x-pack/plugins/reporting/server/lib/enqueue_job.ts index 8ffb99f7a14c8..3837f593df5b2 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts @@ -5,8 +5,9 @@ */ import { EventEmitter } from 'events'; -import { get } from 'lodash'; -import { ConditionalHeaders, ESQueueCreateJobFn, RequestFacade } from '../../server/types'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { AuthenticatedUser } from '../../../security/server'; +import { ESQueueCreateJobFn } from '../../server/types'; import { ReportingCore } from '../core'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; @@ -29,9 +30,9 @@ export type Job = EventEmitter & { export type EnqueueJobFn = ( exportTypeId: string, jobParams: JobParamsType, - user: string, - headers: Record, - request: RequestFacade + user: AuthenticatedUser | null, + context: RequestHandlerContext, + request: KibanaRequest ) => Promise; export function enqueueJobFactory( @@ -42,18 +43,17 @@ export function enqueueJobFactory( const queueTimeout = config.get('queue', 'timeout'); const browserType = config.get('capture', 'browser', 'type'); const maxAttempts = config.get('capture', 'maxAttempts'); - const logger = parentLogger.clone(['queue-job']); return async function enqueueJob( exportTypeId: string, jobParams: JobParamsType, - user: string, - headers: ConditionalHeaders['headers'], - request: RequestFacade + user: AuthenticatedUser | null, + context: RequestHandlerContext, + request: KibanaRequest ): Promise { type CreateJobFn = ESQueueCreateJobFn; - + const username = user ? user.username : false; const esqueue = await reporting.getEsqueue(); const exportType = reporting.getExportTypesRegistry().getById(exportTypeId); @@ -62,11 +62,11 @@ export function enqueueJobFactory( } const createJob = exportType.createJobFactory(reporting, logger) as CreateJobFn; - const payload = await createJob(jobParams, headers, request); + const payload = await createJob(jobParams, context, request); const options = { timeout: queueTimeout, - created_by: get(user, 'username', false), + created_by: username, browser_type: browserType, max_attempts: maxAttempts, }; diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/job.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/job.js new file mode 100644 index 0000000000000..9cc62800d439f --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/job.js @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import events from 'events'; + +export class JobMock extends events.EventEmitter { + constructor(queue, index, type, payload, options = {}) { + super(); + + this.queue = queue; + this.index = index; + this.jobType = type; + this.payload = payload; + this.options = options; + } + + getProp(name) { + return this[name]; + } +} diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js rename to x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/queue.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/queue.js new file mode 100644 index 0000000000000..974cb4a5e2a6e --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/queue.js @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import events from 'events'; + +export class QueueMock extends events.EventEmitter { + constructor() { + super(); + } + + setClient(client) { + this.client = client; + } +} diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/worker.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/worker.js new file mode 100644 index 0000000000000..fe8a859ccb445 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/fixtures/worker.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import events from 'events'; + +export class WorkerMock extends events.EventEmitter { + constructor(queue, type, workerFn, opts = {}) { + super(); + + this.queue = queue; + this.type = type; + this.workerFn = workerFn; + this.options = opts; + } + + getProp(name) { + return this[name]; + } +} diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js new file mode 100644 index 0000000000000..691bd4f618a1c --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import sinon from 'sinon'; +import { createIndex } from '../../helpers/create_index'; +import { ClientMock } from '../fixtures/legacy_elasticsearch'; +import { constants } from '../../constants'; + +describe('Create Index', function () { + describe('Does not exist', function () { + let client; + let createSpy; + + beforeEach(function () { + client = new ClientMock(); + createSpy = sinon.spy(client, 'callAsInternalUser').withArgs('indices.create'); + }); + + it('should return true', function () { + const indexName = 'test-index'; + const result = createIndex(client, indexName); + + return result.then((exists) => expect(exists).to.be(true)); + }); + + it('should create the index with mappings and default settings', function () { + const indexName = 'test-index'; + const settings = constants.DEFAULT_SETTING_INDEX_SETTINGS; + const result = createIndex(client, indexName); + + return result.then(function () { + const payload = createSpy.getCall(0).args[1]; + sinon.assert.callCount(createSpy, 1); + expect(payload).to.have.property('index', indexName); + expect(payload).to.have.property('body'); + expect(payload.body).to.have.property('settings'); + expect(payload.body.settings).to.eql(settings); + expect(payload.body).to.have.property('mappings'); + expect(payload.body.mappings).to.have.property('properties'); + }); + }); + + it('should create the index with custom settings', function () { + const indexName = 'test-index'; + const settings = { + ...constants.DEFAULT_SETTING_INDEX_SETTINGS, + auto_expand_replicas: false, + number_of_shards: 3000, + number_of_replicas: 1, + format: '3000', + }; + const result = createIndex(client, indexName, settings); + + return result.then(function () { + const payload = createSpy.getCall(0).args[1]; + sinon.assert.callCount(createSpy, 1); + expect(payload).to.have.property('index', indexName); + expect(payload).to.have.property('body'); + expect(payload.body).to.have.property('settings'); + expect(payload.body.settings).to.eql(settings); + expect(payload.body).to.have.property('mappings'); + expect(payload.body.mappings).to.have.property('properties'); + }); + }); + }); + + describe('Does exist', function () { + let client; + let createSpy; + + beforeEach(function () { + client = new ClientMock(); + sinon + .stub(client, 'callAsInternalUser') + .withArgs('indices.exists') + .callsFake(() => Promise.resolve(true)); + createSpy = client.callAsInternalUser.withArgs('indices.create'); + }); + + it('should return true', function () { + const indexName = 'test-index'; + const result = createIndex(client, indexName); + + return result.then((exists) => expect(exists).to.be(true)); + }); + + it('should not create the index', function () { + const indexName = 'test-index'; + const result = createIndex(client, indexName); + + return result.then(function () { + sinon.assert.callCount(createSpy, 0); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/errors.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/errors.js similarity index 77% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/errors.js rename to x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/errors.js index a59355bd426d0..d41b29106bb9d 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/errors.js +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/errors.js @@ -7,49 +7,49 @@ import expect from '@kbn/expect'; import { WorkerTimeoutError, UnspecifiedWorkerError } from '../../helpers/errors'; -describe('custom errors', function() { - describe('WorkerTimeoutError', function() { +describe('custom errors', function () { + describe('WorkerTimeoutError', function () { it('should be function', () => { expect(WorkerTimeoutError).to.be.a('function'); }); - it('should have a name', function() { + it('should have a name', function () { const err = new WorkerTimeoutError('timeout error'); expect(err).to.have.property('name', 'WorkerTimeoutError'); }); - it('should take a jobId property', function() { + it('should take a jobId property', function () { const err = new WorkerTimeoutError('timeout error', { jobId: 'il7hl34rqlo8ro' }); expect(err).to.have.property('jobId', 'il7hl34rqlo8ro'); }); - it('should take a timeout property', function() { + it('should take a timeout property', function () { const err = new WorkerTimeoutError('timeout error', { timeout: 15000 }); expect(err).to.have.property('timeout', 15000); }); - it('should be stringifyable', function() { + it('should be stringifyable', function () { const err = new WorkerTimeoutError('timeout error'); expect(`${err}`).to.equal('WorkerTimeoutError: timeout error'); }); }); - describe('UnspecifiedWorkerError', function() { + describe('UnspecifiedWorkerError', function () { it('should be function', () => { expect(UnspecifiedWorkerError).to.be.a('function'); }); - it('should have a name', function() { + it('should have a name', function () { const err = new UnspecifiedWorkerError('unspecified error'); expect(err).to.have.property('name', 'UnspecifiedWorkerError'); }); - it('should take a jobId property', function() { + it('should take a jobId property', function () { const err = new UnspecifiedWorkerError('unspecified error', { jobId: 'il7hl34rqlo8ro' }); expect(err).to.have.property('jobId', 'il7hl34rqlo8ro'); }); - it('should be stringifyable', function() { + it('should be stringifyable', function () { const err = new UnspecifiedWorkerError('unspecified error'); expect(`${err}`).to.equal('UnspecifiedWorkerError: unspecified error'); }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js similarity index 78% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js rename to x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js index 33605dce66b28..71dc8a363e429 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js @@ -12,78 +12,78 @@ import { indexTimestamp } from '../../helpers/index_timestamp'; const anchor = '2016-04-02T01:02:03.456'; // saturday -describe('Index timestamp interval', function() { - describe('construction', function() { - it('should throw given an invalid interval', function() { +describe('Index timestamp interval', function () { + describe('construction', function () { + it('should throw given an invalid interval', function () { const init = () => indexTimestamp('bananas'); expect(init).to.throwException(/invalid.+interval/i); }); }); - describe('timestamps', function() { + describe('timestamps', function () { let clock; let separator; - beforeEach(function() { + beforeEach(function () { separator = constants.DEFAULT_SETTING_DATE_SEPARATOR; clock = sinon.useFakeTimers(moment(anchor).valueOf()); }); - afterEach(function() { + afterEach(function () { clock.restore(); }); - describe('formats', function() { - it('should return the year', function() { + describe('formats', function () { + it('should return the year', function () { const timestamp = indexTimestamp('year'); const str = `2016`; expect(timestamp).to.equal(str); }); - it('should return the year and month', function() { + it('should return the year and month', function () { const timestamp = indexTimestamp('month'); const str = `2016${separator}04`; expect(timestamp).to.equal(str); }); - it('should return the year, month, and first day of the week', function() { + it('should return the year, month, and first day of the week', function () { const timestamp = indexTimestamp('week'); const str = `2016${separator}03${separator}27`; expect(timestamp).to.equal(str); }); - it('should return the year, month, and day of the week', function() { + it('should return the year, month, and day of the week', function () { const timestamp = indexTimestamp('day'); const str = `2016${separator}04${separator}02`; expect(timestamp).to.equal(str); }); - it('should return the year, month, day and hour', function() { + it('should return the year, month, day and hour', function () { const timestamp = indexTimestamp('hour'); const str = `2016${separator}04${separator}02${separator}01`; expect(timestamp).to.equal(str); }); - it('should return the year, month, day, hour and minute', function() { + it('should return the year, month, day, hour and minute', function () { const timestamp = indexTimestamp('minute'); const str = `2016${separator}04${separator}02${separator}01${separator}02`; expect(timestamp).to.equal(str); }); }); - describe('date separator', function() { - it('should be customizable', function() { + describe('date separator', function () { + it('should be customizable', function () { const separators = ['-', '.', '_']; - separators.forEach(customSep => { + separators.forEach((customSep) => { const str = `2016${customSep}04${customSep}02${customSep}01${customSep}02`; const timestamp = indexTimestamp('minute', customSep); expect(timestamp).to.equal(str); }); }); - it('should throw if a letter is used', function() { + it('should throw if a letter is used', function () { const separators = ['a', 'B', 'YYYY']; - separators.forEach(customSep => { + separators.forEach((customSep) => { const fn = () => indexTimestamp('minute', customSep); expect(fn).to.throwException(); }); diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/index.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/index.js new file mode 100644 index 0000000000000..7cdae152ad0d7 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/index.js @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import events from 'events'; +import expect from '@kbn/expect'; +import sinon from 'sinon'; +import proxyquire from 'proxyquire'; +import { noop, times } from 'lodash'; +import { constants } from '../constants'; +import { ClientMock } from './fixtures/legacy_elasticsearch'; +import { JobMock } from './fixtures/job'; +import { WorkerMock } from './fixtures/worker'; + +const { Esqueue } = proxyquire.noPreserveCache()('../index', { + './job': { Job: JobMock }, + './worker': { Worker: WorkerMock }, +}); + +describe('Esqueue class', function () { + let client; + + beforeEach(function () { + client = new ClientMock(); + }); + + it('should be an event emitter', function () { + const queue = new Esqueue('esqueue', { client }); + expect(queue).to.be.an(events.EventEmitter); + }); + + describe('Option validation', function () { + it('should throw without an index', function () { + const init = () => new Esqueue(); + expect(init).to.throwException(/must.+specify.+index/i); + }); + }); + + describe('Queue construction', function () { + it('should ping the ES server', function () { + const pingSpy = sinon.spy(client, 'callAsInternalUser').withArgs('ping'); + new Esqueue('esqueue', { client }); + sinon.assert.calledOnce(pingSpy); + }); + }); + + describe('Adding jobs', function () { + let indexName; + let jobType; + let payload; + let queue; + + beforeEach(function () { + indexName = 'esqueue-index'; + jobType = 'test-test'; + payload = { payload: true }; + queue = new Esqueue(indexName, { client }); + }); + + it('should throw with invalid dateSeparator setting', function () { + queue = new Esqueue(indexName, { client, dateSeparator: 'a' }); + const fn = () => queue.addJob(jobType, payload); + expect(fn).to.throwException(); + }); + + it('should pass queue instance, index name, type and payload', function () { + const job = queue.addJob(jobType, payload); + expect(job.getProp('queue')).to.equal(queue); + expect(job.getProp('index')).to.match(new RegExp(indexName)); + expect(job.getProp('jobType')).to.equal(jobType); + expect(job.getProp('payload')).to.equal(payload); + }); + + it('should pass default settings', function () { + const job = queue.addJob(jobType, payload); + const options = job.getProp('options'); + expect(options).to.have.property('timeout', constants.DEFAULT_SETTING_TIMEOUT); + }); + + it('should pass queue index settings', function () { + const indexSettings = { + index: { + number_of_shards: 1, + }, + }; + + queue = new Esqueue(indexName, { client, indexSettings }); + const job = queue.addJob(jobType, payload); + expect(job.getProp('options')).to.have.property('indexSettings', indexSettings); + }); + + it('should pass headers from options', function () { + const options = { + headers: { + authorization: 'Basic cXdlcnR5', + }, + }; + const job = queue.addJob(jobType, payload, options); + expect(job.getProp('options')).to.have.property('headers', options.headers); + }); + }); + + describe('Registering workers', function () { + let queue; + + beforeEach(function () { + queue = new Esqueue('esqueue', { client }); + }); + + it('should keep track of workers', function () { + expect(queue.getWorkers()).to.eql([]); + expect(queue.getWorkers()).to.have.length(0); + + queue.registerWorker('test', noop); + queue.registerWorker('test', noop); + queue.registerWorker('test2', noop); + expect(queue.getWorkers()).to.have.length(3); + }); + + it('should pass instance of queue, type, and worker function', function () { + const workerType = 'test-worker'; + const workerFn = () => true; + + const worker = queue.registerWorker(workerType, workerFn); + expect(worker.getProp('queue')).to.equal(queue); + expect(worker.getProp('type')).to.equal(workerType); + expect(worker.getProp('workerFn')).to.equal(workerFn); + }); + + it('should pass worker options', function () { + const workerOptions = { + size: 12, + }; + + queue = new Esqueue('esqueue', { client }); + const worker = queue.registerWorker('type', noop, workerOptions); + const options = worker.getProp('options'); + expect(options.size).to.equal(workerOptions.size); + }); + }); + + describe('Destroy', function () { + it('should destroy workers', function () { + const queue = new Esqueue('esqueue', { client }); + const stubs = times(3, () => { + return { destroy: sinon.stub() }; + }); + stubs.forEach((stub) => queue._workers.push(stub)); + expect(queue.getWorkers()).to.have.length(3); + + queue.destroy(); + stubs.forEach((stub) => sinon.assert.calledOnce(stub.destroy)); + expect(queue.getWorkers()).to.have.length(0); + }); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/job.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/job.js new file mode 100644 index 0000000000000..955eed8d65722 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/job.js @@ -0,0 +1,420 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import events from 'events'; +import expect from '@kbn/expect'; +import sinon from 'sinon'; +import proxyquire from 'proxyquire'; +import { QueueMock } from './fixtures/queue'; +import { ClientMock } from './fixtures/legacy_elasticsearch'; +import { constants } from '../constants'; + +const createIndexMock = sinon.stub(); +const { Job } = proxyquire.noPreserveCache()('../job', { + './helpers/create_index': { createIndex: createIndexMock }, +}); + +const maxPriority = 20; +const minPriority = -20; +const defaultPriority = 10; +const defaultCreatedBy = false; + +function validateDoc(spy) { + sinon.assert.callCount(spy, 1); + const spyCall = spy.getCall(0); + return spyCall.args[1]; +} + +describe('Job Class', function () { + let mockQueue; + let client; + let index; + + let type; + let payload; + let options; + + beforeEach(function () { + createIndexMock.resetHistory(); + createIndexMock.returns(Promise.resolve('mock')); + index = 'test'; + + client = new ClientMock(); + mockQueue = new QueueMock(); + mockQueue.setClient(client); + }); + + it('should be an event emitter', function () { + const job = new Job(mockQueue, index, 'test', {}); + expect(job).to.be.an(events.EventEmitter); + }); + + describe('invalid construction', function () { + it('should throw with a missing type', function () { + const init = () => new Job(mockQueue, index); + expect(init).to.throwException(/type.+string/i); + }); + + it('should throw with an invalid type', function () { + const init = () => new Job(mockQueue, index, { 'not a string': true }); + expect(init).to.throwException(/type.+string/i); + }); + + it('should throw with an invalid payload', function () { + const init = () => new Job(mockQueue, index, 'type1', [1, 2, 3]); + expect(init).to.throwException(/plain.+object/i); + }); + + it(`should throw error if invalid maxAttempts`, function () { + const init = () => new Job(mockQueue, index, 'type1', { id: '123' }, { max_attempts: -1 }); + expect(init).to.throwException(/invalid.+max_attempts/i); + }); + }); + + describe('construction', function () { + let indexSpy; + beforeEach(function () { + type = 'type1'; + payload = { id: '123' }; + indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index'); + }); + + it('should create the target index', function () { + const job = new Job(mockQueue, index, type, payload, options); + return job.ready.then(() => { + sinon.assert.calledOnce(createIndexMock); + const args = createIndexMock.getCall(0).args; + expect(args[0]).to.equal(client); + expect(args[1]).to.equal(index); + }); + }); + + it('should index the payload', function () { + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs).to.have.property('index', index); + expect(indexArgs).to.have.property('body'); + expect(indexArgs.body).to.have.property('payload', payload); + }); + }); + + it('should index the job type', function () { + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs).to.have.property('index', index); + expect(indexArgs).to.have.property('body'); + expect(indexArgs.body).to.have.property('jobtype', type); + }); + }); + + it('should set event creation time', function () { + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('created_at'); + }); + }); + + it('should refresh the index', function () { + const refreshSpy = client.callAsInternalUser.withArgs('indices.refresh'); + + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + sinon.assert.calledOnce(refreshSpy); + const spyCall = refreshSpy.getCall(0); + expect(spyCall.args[1]).to.have.property('index', index); + }); + }); + + it('should emit the job information on success', function (done) { + const job = new Job(mockQueue, index, type, payload); + job.once(constants.EVENT_JOB_CREATED, (jobDoc) => { + try { + expect(jobDoc).to.have.property('id'); + expect(jobDoc).to.have.property('index'); + expect(jobDoc).to.have.property('_seq_no'); + expect(jobDoc).to.have.property('_primary_term'); + done(); + } catch (e) { + done(e); + } + }); + }); + + it('should emit error on index creation failure', function (done) { + const errMsg = 'test index creation failure'; + + createIndexMock.returns(Promise.reject(new Error(errMsg))); + const job = new Job(mockQueue, index, type, payload); + + job.once(constants.EVENT_JOB_CREATE_ERROR, (err) => { + try { + expect(err.message).to.equal(errMsg); + done(); + } catch (e) { + done(e); + } + }); + }); + + it('should emit error on client index failure', function (done) { + const errMsg = 'test document index failure'; + + client.callAsInternalUser.restore(); + sinon + .stub(client, 'callAsInternalUser') + .withArgs('index') + .callsFake(() => Promise.reject(new Error(errMsg))); + const job = new Job(mockQueue, index, type, payload); + + job.once(constants.EVENT_JOB_CREATE_ERROR, (err) => { + try { + expect(err.message).to.equal(errMsg); + done(); + } catch (e) { + done(e); + } + }); + }); + }); + + describe('event emitting', function () { + it('should trigger events on the queue instance', function (done) { + const eventName = 'test event'; + const payload1 = { + test: true, + deep: { object: 'ok' }, + }; + const payload2 = 'two'; + const payload3 = new Error('test error'); + + const job = new Job(mockQueue, index, type, payload, options); + + mockQueue.on(eventName, (...args) => { + try { + expect(args[0]).to.equal(payload1); + expect(args[1]).to.equal(payload2); + expect(args[2]).to.equal(payload3); + done(); + } catch (e) { + done(e); + } + }); + + job.emit(eventName, payload1, payload2, payload3); + }); + }); + + describe('default values', function () { + let indexSpy; + beforeEach(function () { + type = 'type1'; + payload = { id: '123' }; + indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index'); + }); + + it('should set attempt count to 0', function () { + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('attempts', 0); + }); + }); + + it('should index default created_by value', function () { + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('created_by', defaultCreatedBy); + }); + }); + + it('should set an expired process_expiration time', function () { + const now = new Date().getTime(); + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('process_expiration'); + expect(indexArgs.body.process_expiration.getTime()).to.be.lessThan(now); + }); + }); + + it('should set status as pending', function () { + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('status', constants.JOB_STATUS_PENDING); + }); + }); + + it('should have a default priority of 10', function () { + const job = new Job(mockQueue, index, type, payload, options); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('priority', defaultPriority); + }); + }); + + it('should set a browser type', function () { + const job = new Job(mockQueue, index, type, payload); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('browser_type'); + }); + }); + }); + + describe('option passing', function () { + let indexSpy; + beforeEach(function () { + type = 'type1'; + payload = { id: '123' }; + options = { + timeout: 4567, + max_attempts: 9, + headers: { + authorization: 'Basic cXdlcnR5', + }, + }; + indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index'); + }); + + it('should index the created_by value', function () { + const createdBy = 'user_identifier'; + const job = new Job(mockQueue, index, type, payload, { + created_by: createdBy, + ...options, + }); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('created_by', createdBy); + }); + }); + + it('should index timeout value from options', function () { + const job = new Job(mockQueue, index, type, payload, options); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('timeout', options.timeout); + }); + }); + + it('should set max attempt count', function () { + const job = new Job(mockQueue, index, type, payload, options); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('max_attempts', options.max_attempts); + }); + }); + + it('should add headers to the request params', function () { + const job = new Job(mockQueue, index, type, payload, options); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs).to.have.property('headers', options.headers); + }); + }); + + it(`should use upper priority of ${maxPriority}`, function () { + const job = new Job(mockQueue, index, type, payload, { priority: maxPriority * 2 }); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('priority', maxPriority); + }); + }); + + it(`should use lower priority of ${minPriority}`, function () { + const job = new Job(mockQueue, index, type, payload, { priority: minPriority * 2 }); + return job.ready.then(() => { + const indexArgs = validateDoc(indexSpy); + expect(indexArgs.body).to.have.property('priority', minPriority); + }); + }); + }); + + describe('get method', function () { + beforeEach(function () { + type = 'type2'; + payload = { id: '123' }; + }); + + it('should return the job document', function () { + const job = new Job(mockQueue, index, type, payload); + + return job.get().then((doc) => { + const jobDoc = job.document; // document should be resolved + expect(doc).to.have.property('index', index); + expect(doc).to.have.property('id', jobDoc.id); + expect(doc).to.have.property('_seq_no', jobDoc._seq_no); + expect(doc).to.have.property('_primary_term', jobDoc._primary_term); + expect(doc).to.have.property('created_by', defaultCreatedBy); + + expect(doc).to.have.property('payload'); + expect(doc).to.have.property('jobtype'); + expect(doc).to.have.property('priority'); + expect(doc).to.have.property('timeout'); + }); + }); + + it('should contain optional data', function () { + const optionals = { + created_by: 'some_ident', + }; + + const job = new Job(mockQueue, index, type, payload, optionals); + return Promise.resolve(client.callAsInternalUser('get', {}, optionals)) + .then((doc) => { + sinon.stub(client, 'callAsInternalUser').withArgs('get').returns(Promise.resolve(doc)); + }) + .then(() => { + return job.get().then((doc) => { + expect(doc).to.have.property('created_by', optionals.created_by); + }); + }); + }); + }); + + describe('toJSON method', function () { + beforeEach(function () { + type = 'type2'; + payload = { id: '123' }; + options = { + timeout: 4567, + max_attempts: 9, + priority: 8, + }; + }); + + it('should return the static information about the job', function () { + const job = new Job(mockQueue, index, type, payload, options); + + // toJSON is sync, should work before doc is written to elasticsearch + expect(job.document).to.be(undefined); + + const doc = job.toJSON(); + expect(doc).to.have.property('index', index); + expect(doc).to.have.property('jobtype', type); + expect(doc).to.have.property('created_by', defaultCreatedBy); + expect(doc).to.have.property('timeout', options.timeout); + expect(doc).to.have.property('max_attempts', options.max_attempts); + expect(doc).to.have.property('priority', options.priority); + expect(doc).to.have.property('id'); + expect(doc).to.not.have.property('version'); + }); + + it('should contain optional data', function () { + const optionals = { + created_by: 'some_ident', + }; + + const job = new Job(mockQueue, index, type, payload, optionals); + const doc = job.toJSON(); + expect(doc).to.have.property('created_by', optionals.created_by); + }); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/worker.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/worker.js new file mode 100644 index 0000000000000..b31a39a6f90cc --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/worker.js @@ -0,0 +1,1118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import sinon from 'sinon'; +import moment from 'moment'; +import { noop, random, get, find, identity } from 'lodash'; +import { ClientMock } from './fixtures/legacy_elasticsearch'; +import { QueueMock } from './fixtures/queue'; +import { formatJobObject, getUpdatedDocPath, Worker } from '../worker'; +import { constants } from '../constants'; + +const anchor = '2016-04-02T01:02:03.456'; // saturday +const defaults = { + timeout: 10000, + size: 1, + unknownMime: false, + contentBody: null, +}; + +const defaultWorkerOptions = { + interval: 3000, + intervalErrorMultiplier: 10, +}; + +describe('Worker class', function () { + // some of these tests might be a little slow, give them a little extra time + this.timeout(10000); + + let anchorMoment; + let clock; + let client; + let mockQueue; + let worker; + let worker2; + + // Allowing the Poller to poll requires intimate knowledge of the inner workings of the Poller. + // We have to ensure that the Promises internal to the `_poll` method are resolved to queue up + // the next setTimeout before incrementing the clock. + const allowPoll = async (interval) => { + clock.tick(interval); + await Promise.resolve(); + await Promise.resolve(); + }; + + beforeEach(function () { + client = new ClientMock(); + mockQueue = new QueueMock(); + mockQueue.setClient(client); + }); + + afterEach(function () { + [worker, worker2].forEach((actualWorker) => { + if (actualWorker) { + actualWorker.destroy(); + } + }); + }); + + describe('invalid construction', function () { + it('should throw without a type', function () { + const init = () => new Worker(mockQueue); + expect(init).to.throwException(/type.+string/i); + }); + + it('should throw without an invalid type', function () { + const init = () => new Worker(mockQueue, { string: false }); + expect(init).to.throwException(/type.+string/i); + }); + + it('should throw without a workerFn', function () { + const init = () => new Worker(mockQueue, 'test'); + expect(init).to.throwException(/workerFn.+function/i); + }); + + it('should throw with an invalid workerFn', function () { + const init = () => new Worker(mockQueue, 'test', { function: false }); + expect(init).to.throwException(/workerFn.+function/i); + }); + + it('should throw without an opts', function () { + const init = () => new Worker(mockQueue, 'test', noop); + expect(init).to.throwException(/opts.+object/i); + }); + + it('should throw with an invalid opts.interval', function () { + const init = () => new Worker(mockQueue, 'test', noop, {}); + expect(init).to.throwException(/opts\.interval.+number/i); + }); + + it('should throw with an invalid opts.intervalErrorMultiplier', function () { + const init = () => new Worker(mockQueue, 'test', noop, { interval: 1 }); + expect(init).to.throwException(/opts\.intervalErrorMultiplier.+number/i); + }); + }); + + describe('construction', function () { + it('should assign internal properties', function () { + const jobtype = 'testjob'; + const workerFn = noop; + worker = new Worker(mockQueue, jobtype, workerFn, defaultWorkerOptions); + expect(worker).to.have.property('id'); + expect(worker).to.have.property('queue', mockQueue); + expect(worker).to.have.property('jobtype', jobtype); + expect(worker).to.have.property('workerFn', workerFn); + }); + + it('should have a unique ID', function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + expect(worker.id).to.be.a('string'); + + worker2 = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + expect(worker2.id).to.be.a('string'); + + expect(worker.id).to.not.equal(worker2.id); + }); + }); + + describe('event emitting', function () { + beforeEach(function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + }); + + it('should trigger events on the queue instance', function (done) { + const eventName = 'test event'; + const payload1 = { + test: true, + deep: { object: 'ok' }, + }; + const payload2 = 'two'; + const payload3 = new Error('test error'); + + mockQueue.on(eventName, (...args) => { + try { + expect(args[0]).to.equal(payload1); + expect(args[1]).to.equal(payload2); + expect(args[2]).to.equal(payload3); + done(); + } catch (e) { + done(e); + } + }); + + worker.emit(eventName, payload1, payload2, payload3); + }); + }); + + describe('output formatting', function () { + let f; + + beforeEach(function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + f = (output) => worker._formatOutput(output); + }); + + it('should handle primitives', function () { + const primitives = ['test', true, 1234, { one: 1 }, [5, 6, 7, 8]]; + + primitives.forEach((val) => { + expect(f(val)).to.have.property('content_type', defaults.unknownMime); + expect(f(val)).to.have.property('content', val); + }); + }); + + it('should accept content object without type', function () { + const output = { + content: 'test output', + }; + + expect(f(output)).to.have.property('content_type', defaults.unknownMime); + expect(f(output)).to.have.property('content', output.content); + }); + + it('should accept a content type', function () { + const output = { + content_type: 'test type', + content: 'test output', + }; + + expect(f(output)).to.have.property('content_type', output.content_type); + expect(f(output)).to.have.property('content', output.content); + }); + + it('should work with no input', function () { + expect(f()).to.have.property('content_type', defaults.unknownMime); + expect(f()).to.have.property('content', defaults.contentBody); + }); + }); + + describe('polling for jobs', function () { + beforeEach(() => { + anchorMoment = moment(anchor); + clock = sinon.useFakeTimers(anchorMoment.valueOf()); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should start polling for jobs after interval', async function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + const processPendingJobsStub = sinon + .stub(worker, '_processPendingJobs') + .callsFake(() => Promise.resolve()); + sinon.assert.notCalled(processPendingJobsStub); + await allowPoll(defaultWorkerOptions.interval); + sinon.assert.calledOnce(processPendingJobsStub); + }); + + it('should use interval option to control polling', async function () { + const interval = 567; + worker = new Worker(mockQueue, 'test', noop, { ...defaultWorkerOptions, interval }); + const processPendingJobsStub = sinon + .stub(worker, '_processPendingJobs') + .callsFake(() => Promise.resolve()); + + sinon.assert.notCalled(processPendingJobsStub); + await allowPoll(interval); + sinon.assert.calledOnce(processPendingJobsStub); + }); + + it('should not poll once destroyed', async function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + + const processPendingJobsStub = sinon + .stub(worker, '_processPendingJobs') + .callsFake(() => Promise.resolve()); + + // move the clock a couple times, test for searches each time + sinon.assert.notCalled(processPendingJobsStub); + await allowPoll(defaultWorkerOptions.interval); + sinon.assert.calledOnce(processPendingJobsStub); + await allowPoll(defaultWorkerOptions.interval); + sinon.assert.calledTwice(processPendingJobsStub); + + // destroy the worker, move the clock, make sure another search doesn't happen + worker.destroy(); + await allowPoll(defaultWorkerOptions.interval); + sinon.assert.calledTwice(processPendingJobsStub); + + // manually call job poller, move the clock, make sure another search doesn't happen + worker._startJobPolling(); + await allowPoll(defaultWorkerOptions.interval); + sinon.assert.calledTwice(processPendingJobsStub); + }); + + it('should use error multiplier when processPendingJobs rejects the Promise', async function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + + const processPendingJobsStub = sinon + .stub(worker, '_processPendingJobs') + .rejects(new Error('test error')); + + await allowPoll(defaultWorkerOptions.interval); + expect(processPendingJobsStub.callCount).to.be(1); + await allowPoll(defaultWorkerOptions.interval); + expect(processPendingJobsStub.callCount).to.be(1); + await allowPoll(defaultWorkerOptions.interval * defaultWorkerOptions.intervalErrorMultiplier); + expect(processPendingJobsStub.callCount).to.be(2); + }); + + it('should not use error multiplier when processPendingJobs resolved the Promise', async function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + + const processPendingJobsStub = sinon + .stub(worker, '_processPendingJobs') + .callsFake(() => Promise.resolve()); + + await allowPoll(defaultWorkerOptions.interval); + expect(processPendingJobsStub.callCount).to.be(1); + await allowPoll(defaultWorkerOptions.interval); + expect(processPendingJobsStub.callCount).to.be(2); + }); + }); + + describe('query for pending jobs', function () { + let searchStub; + + function getSearchParams(jobtype = 'test', params = {}) { + worker = new Worker(mockQueue, jobtype, noop, { ...defaultWorkerOptions, ...params }); + worker._getPendingJobs(); + return searchStub.firstCall.args[1]; + } + + describe('error handling', function () { + it('should pass search errors', function (done) { + searchStub = sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('search') + .callsFake(() => Promise.reject()); + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + worker + ._getPendingJobs() + .then(() => done(new Error('should not resolve'))) + .catch(() => { + done(); + }); + }); + + describe('missing index', function () { + it('should swallow error', function (done) { + searchStub = sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('search') + .callsFake(() => Promise.reject({ status: 404 })); + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + worker + ._getPendingJobs() + .then(() => { + done(); + }) + .catch(() => done(new Error('should not reject'))); + }); + + it('should return an empty array', function (done) { + searchStub = sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('search') + .callsFake(() => Promise.reject({ status: 404 })); + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + worker + ._getPendingJobs() + .then((res) => { + try { + expect(res).to.be.an(Array); + expect(res).to.have.length(0); + done(); + } catch (e) { + done(e); + } + }) + .catch(() => done(new Error('should not reject'))); + }); + }); + }); + + describe('query body', function () { + const conditionPath = 'query.bool.filter.bool'; + const jobtype = 'test_jobtype'; + + beforeEach(() => { + searchStub = sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('search') + .callsFake(() => Promise.resolve({ hits: { hits: [] } })); + anchorMoment = moment(anchor); + clock = sinon.useFakeTimers(anchorMoment.valueOf()); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should query with seq_no_primary_term', function () { + const { body } = getSearchParams(jobtype); + expect(body).to.have.property('seq_no_primary_term', true); + }); + + it('should filter unwanted source data', function () { + const excludedFields = ['output.content']; + const { body } = getSearchParams(jobtype); + expect(body).to.have.property('_source'); + expect(body._source).to.eql({ excludes: excludedFields }); + }); + + it('should search for pending or expired jobs', function () { + const { body } = getSearchParams(jobtype); + const conditions = get(body, conditionPath); + expect(conditions).to.have.property('should'); + + // this works because we are stopping the clock, so all times match + const nowTime = moment().toISOString(); + const pending = { term: { status: 'pending' } }; + const expired = { + bool: { + must: [ + { term: { status: 'processing' } }, + { range: { process_expiration: { lte: nowTime } } }, + ], + }, + }; + + const pendingMatch = find(conditions.should, pending); + expect(pendingMatch).to.not.be(undefined); + + const expiredMatch = find(conditions.should, expired); + expect(expiredMatch).to.not.be(undefined); + }); + + it('specify that there should be at least one match', function () { + const { body } = getSearchParams(jobtype); + const conditions = get(body, conditionPath); + expect(conditions).to.have.property('minimum_should_match', 1); + }); + + it('should use default size', function () { + const { body } = getSearchParams(jobtype); + expect(body).to.have.property('size', defaults.size); + }); + }); + }); + + describe('claiming a job', function () { + let params; + let job; + let updateSpy; + + beforeEach(function () { + anchorMoment = moment(anchor); + clock = sinon.useFakeTimers(anchorMoment.valueOf()); + + params = { + index: 'myIndex', + type: 'test', + id: 12345, + }; + return mockQueue.client.callAsInternalUser('get', params).then((jobDoc) => { + job = jobDoc; + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + updateSpy = sinon.spy(mockQueue.client, 'callAsInternalUser').withArgs('update'); + }); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should use seqNo and primaryTerm on update', function () { + worker._claimJob(job); + const query = updateSpy.firstCall.args[1]; + expect(query).to.have.property('index', job._index); + expect(query).to.have.property('id', job._id); + expect(query).to.have.property('if_seq_no', job._seq_no); + expect(query).to.have.property('if_primary_term', job._primary_term); + }); + + it('should increment the job attempts', function () { + worker._claimJob(job); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('attempts', job._source.attempts + 1); + }); + + it('should update the job status', function () { + worker._claimJob(job); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('status', constants.JOB_STATUS_PROCESSING); + }); + + it('should set job expiration time', function () { + worker._claimJob(job); + const doc = updateSpy.firstCall.args[1].body.doc; + const expiration = anchorMoment.add(defaults.timeout).toISOString(); + expect(doc).to.have.property('process_expiration', expiration); + }); + + it('should fail job if max_attempts are hit', function () { + const failSpy = sinon.spy(worker, '_failJob'); + job._source.attempts = job._source.max_attempts; + worker._claimJob(job); + sinon.assert.calledOnce(failSpy); + }); + + it('should append error message if no existing content', function () { + const failSpy = sinon.spy(worker, '_failJob'); + job._source.attempts = job._source.max_attempts; + expect(job._source.output).to.be(undefined); + worker._claimJob(job); + const msg = failSpy.firstCall.args[1]; + expect(msg).to.contain('Max attempts reached'); + expect(msg).to.contain(job._source.max_attempts); + }); + + it('should not append message if existing output', function () { + const failSpy = sinon.spy(worker, '_failJob'); + job._source.attempts = job._source.max_attempts; + job._source.output = 'i have some output'; + worker._claimJob(job); + const msg = failSpy.firstCall.args[1]; + expect(msg).to.equal(false); + }); + + it('should reject the promise on conflict errors', function () { + mockQueue.client.callAsInternalUser.restore(); + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .returns(Promise.reject({ statusCode: 409 })); + return worker._claimJob(job).catch((err) => { + expect(err).to.eql({ statusCode: 409 }); + }); + }); + + it('should reject the promise on other errors', function () { + mockQueue.client.callAsInternalUser.restore(); + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .returns(Promise.reject({ statusCode: 401 })); + return worker._claimJob(job).catch((err) => { + expect(err).to.eql({ statusCode: 401 }); + }); + }); + }); + + describe('find a pending job to claim', function () { + const getMockJobs = (status = 'pending') => [ + { + _index: 'myIndex', + _id: 12345, + _seq_no: 3, + _primary_term: 3, + found: true, + _source: { + jobtype: 'jobtype', + created_by: false, + payload: { id: 'sample-job-1', now: 'Mon Apr 25 2016 14:13:04 GMT-0700 (MST)' }, + priority: 10, + timeout: 10000, + created_at: '2016-04-25T21:13:04.738Z', + attempts: 0, + max_attempts: 3, + status, + }, + }, + ]; + + beforeEach(function () { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + }); + + afterEach(() => { + mockQueue.client.callAsInternalUser.restore(); + }); + + it('should emit for errors from claiming job', function (done) { + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .rejects({ statusCode: 401 }); + + worker.once(constants.EVENT_WORKER_JOB_CLAIM_ERROR, function (err) { + try { + expect(err).to.have.property('error'); + expect(err).to.have.property('job'); + expect(err).to.have.property('worker'); + expect(err.error).to.have.property('statusCode', 401); + done(); + } catch (e) { + done(e); + } + }); + + worker._claimPendingJobs(getMockJobs()).catch(() => {}); + }); + + it('should reject the promise if an error claiming the job', function () { + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .rejects({ statusCode: 409 }); + return worker._claimPendingJobs(getMockJobs()).catch((err) => { + expect(err).to.eql({ statusCode: 409 }); + }); + }); + + it('should get the pending job', function () { + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .resolves({ test: 'cool' }); + sinon.stub(worker, '_performJob').callsFake(identity); + return worker._claimPendingJobs(getMockJobs()).then((claimedJob) => { + expect(claimedJob._index).to.be('myIndex'); + expect(claimedJob._source.jobtype).to.be('jobtype'); + expect(claimedJob._source.status).to.be('processing'); + expect(claimedJob.test).to.be('cool'); + worker._performJob.restore(); + }); + }); + }); + + describe('failing a job', function () { + let job; + let updateSpy; + + beforeEach(function () { + anchorMoment = moment(anchor); + clock = sinon.useFakeTimers(anchorMoment.valueOf()); + + return mockQueue.client.callAsInternalUser('get').then((jobDoc) => { + job = jobDoc; + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + updateSpy = sinon.spy(mockQueue.client, 'callAsInternalUser').withArgs('update'); + }); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should use _seq_no and _primary_term on update', function () { + worker._failJob(job); + const query = updateSpy.firstCall.args[1]; + expect(query).to.have.property('index', job._index); + expect(query).to.have.property('id', job._id); + expect(query).to.have.property('if_seq_no', job._seq_no); + expect(query).to.have.property('if_primary_term', job._primary_term); + }); + + it('should set status to failed', function () { + worker._failJob(job); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('status', constants.JOB_STATUS_FAILED); + }); + + it('should append error message if supplied', function () { + const msg = 'test message'; + worker._failJob(job, msg); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('output'); + expect(doc.output).to.have.property('content', msg); + }); + + it('should return true on conflict errors', function () { + mockQueue.client.callAsInternalUser.restore(); + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .rejects({ statusCode: 409 }); + return worker._failJob(job).then((res) => expect(res).to.equal(true)); + }); + + it('should return false on other document update errors', function () { + mockQueue.client.callAsInternalUser.restore(); + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .rejects({ statusCode: 401 }); + return worker._failJob(job).then((res) => expect(res).to.equal(false)); + }); + + it('should set completed time and status to failure', function () { + const startTime = moment().valueOf(); + const msg = 'test message'; + clock.tick(100); + + worker._failJob(job, msg); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('output'); + expect(doc).to.have.property('status', constants.JOB_STATUS_FAILED); + expect(doc).to.have.property('completed_at'); + const completedTimestamp = moment(doc.completed_at).valueOf(); + expect(completedTimestamp).to.be.greaterThan(startTime); + }); + + it('should emit worker failure event', function (done) { + worker.on(constants.EVENT_WORKER_JOB_FAIL, (err) => { + try { + expect(err).to.have.property('output'); + expect(err).to.have.property('job'); + expect(err).to.have.property('worker'); + done(); + } catch (e) { + done(e); + } + }); + + return worker._failJob(job); + }); + + it('should emit on other document update errors', function (done) { + mockQueue.client.callAsInternalUser.restore(); + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .rejects({ statusCode: 401 }); + + worker.on(constants.EVENT_WORKER_FAIL_UPDATE_ERROR, function (err) { + try { + expect(err).to.have.property('error'); + expect(err).to.have.property('job'); + expect(err).to.have.property('worker'); + expect(err.error).to.have.property('statusCode', 401); + done(); + } catch (e) { + done(e); + } + }); + worker._failJob(job); + }); + }); + + describe('performing a job', function () { + let job; + let payload; + let updateSpy; + + beforeEach(function () { + payload = { + value: random(0, 100, true), + }; + + return mockQueue.client.callAsInternalUser('get', {}, { payload }).then((jobDoc) => { + job = jobDoc; + updateSpy = sinon.spy(mockQueue.client, 'callAsInternalUser').withArgs('update'); + }); + }); + + describe('worker success', function () { + it('should call the workerFn with the payload', function (done) { + const workerFn = function (jobPayload) { + expect(jobPayload).to.eql(payload); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + + worker._performJob(job).then(() => done()); + }); + + it('should update the job with the workerFn output', function () { + const workerFn = function (job, jobPayload) { + expect(jobPayload).to.eql(payload); + return payload; + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + + return worker._performJob(job).then(() => { + sinon.assert.calledOnce(updateSpy); + const query = updateSpy.firstCall.args[1]; + + expect(query).to.have.property('index', job._index); + expect(query).to.have.property('id', job._id); + expect(query).to.have.property('if_seq_no', job._seq_no); + expect(query).to.have.property('if_primary_term', job._primary_term); + expect(query.body.doc).to.have.property('output'); + expect(query.body.doc.output).to.have.property('content_type', false); + expect(query.body.doc.output).to.have.property('content', payload); + }); + }); + + it('should update the job status and completed time', function () { + const startTime = moment().valueOf(); + const workerFn = function (job, jobPayload) { + expect(jobPayload).to.eql(payload); + return new Promise(function (resolve) { + setTimeout(() => resolve(payload), 10); + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + + return worker._performJob(job).then(() => { + sinon.assert.calledOnce(updateSpy); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('status', constants.JOB_STATUS_COMPLETED); + expect(doc).to.have.property('completed_at'); + const completedTimestamp = moment(doc.completed_at).valueOf(); + expect(completedTimestamp).to.be.greaterThan(startTime); + }); + }); + + it('handle warnings in the output by reflecting a warning status', () => { + const workerFn = () => { + return Promise.resolve({ + ...payload, + warnings: [`Don't run with scissors!`], + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + + return worker + ._performJob({ + test: true, + ...job, + }) + .then(() => { + sinon.assert.calledOnce(updateSpy); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('status', constants.JOB_STATUS_WARNINGS); + }); + }); + + it('should emit completion event', function (done) { + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + + worker.once(constants.EVENT_WORKER_COMPLETE, (workerJob) => { + try { + expect(workerJob).to.not.have.property('_source'); + + expect(workerJob).to.have.property('job'); + expect(workerJob.job).to.have.property('id'); + expect(workerJob.job).to.have.property('index'); + + expect(workerJob).to.have.property('output'); + expect(workerJob.output).to.have.property('content'); + expect(workerJob.output).to.have.property('content_type'); + + done(); + } catch (e) { + done(e); + } + }); + + worker._performJob(job); + }); + }); + + describe('worker failure', function () { + it('should append error output to job', function () { + const workerFn = function () { + throw new Error('test error'); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + const failStub = sinon.stub(worker, '_failJob'); + + return worker._performJob(job).then(() => { + sinon.assert.calledOnce(failStub); + sinon.assert.calledWith(failStub, job, 'Error: test error'); + }); + }); + + it('should handle async errors', function () { + const workerFn = function () { + return new Promise((resolve, reject) => { + reject(new Error('test error')); + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + const failStub = sinon.stub(worker, '_failJob'); + + return worker._performJob(job).then(() => { + sinon.assert.calledOnce(failStub); + sinon.assert.calledWith(failStub, job, 'Error: test error'); + }); + }); + + it('should handle rejecting with strings', function () { + const errorMessage = 'this is a string error'; + const workerFn = function () { + return new Promise((resolve, reject) => { + reject(errorMessage); + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + const failStub = sinon.stub(worker, '_failJob'); + + return worker._performJob(job).then(() => { + sinon.assert.calledOnce(failStub); + sinon.assert.calledWith(failStub, job, errorMessage); + }); + }); + + it('should handle empty rejection', function (done) { + const workerFn = function () { + return new Promise((resolve, reject) => { + reject(); + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + + worker.once(constants.EVENT_WORKER_JOB_EXECUTION_ERROR, (err) => { + try { + expect(err).to.have.property('error'); + expect(err).to.have.property('job'); + expect(err).to.have.property('worker'); + expect(err.error).to.have.property('name', 'UnspecifiedWorkerError'); + done(); + } catch (e) { + done(e); + } + }); + + worker._performJob(job); + }); + }); + }); + + describe('job failures', function () { + function getFailStub(workerWithFailure) { + return sinon.stub(workerWithFailure, '_failJob').resolves(); + } + + describe('saving output failure', () => { + it('should mark the job as failed if saving to ES fails', async () => { + const job = { + _id: 'shouldSucced', + _source: { + timeout: 1000, + payload: 'test', + }, + }; + + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('update') + .rejects({ statusCode: 413 }); + + const workerFn = function (jobPayload) { + return new Promise(function (resolve) { + setTimeout(() => resolve(jobPayload), 10); + }); + }; + const worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + const failStub = getFailStub(worker); + + await worker._performJob(job); + worker.destroy(); + + sinon.assert.called(failStub); + }); + }); + + describe('search failure', function () { + it('causes _processPendingJobs to reject the Promise', function () { + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('search') + .rejects(new Error('test error')); + worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); + return worker._processPendingJobs().then( + () => { + expect().fail('expected rejected Promise'); + }, + (err) => { + expect(err).to.be.an(Error); + } + ); + }); + }); + + describe('timeout', function () { + let failStub; + let job; + let cancellationCallback; + + beforeEach(function () { + const timeout = 20; + cancellationCallback = function () {}; + + const workerFn = function (job, payload, cancellationToken) { + cancellationToken.on(cancellationCallback); + return new Promise(function (resolve) { + setTimeout(() => { + resolve(); + }, timeout * 2); + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + failStub = getFailStub(worker); + + job = { + _id: 'testTimeoutJob', + _source: { + timeout: timeout, + payload: 'test', + }, + }; + }); + + it('should not fail job', function () { + // fire of the job worker + return worker._performJob(job).then(() => { + sinon.assert.notCalled(failStub); + }); + }); + + it('should emit timeout if not completed in time', function (done) { + worker.once(constants.EVENT_WORKER_JOB_TIMEOUT, (err) => { + try { + expect(err).to.have.property('error'); + expect(err).to.have.property('job'); + expect(err).to.have.property('worker'); + expect(err.error).to.have.property('name', 'WorkerTimeoutError'); + done(); + } catch (e) { + done(e); + } + }); + + // fire of the job worker + worker._performJob(job); + }); + + it('should call cancellation token callback if not completed in time', function (done) { + let called = false; + + cancellationCallback = () => { + called = true; + }; + + worker.once(constants.EVENT_WORKER_JOB_TIMEOUT, () => { + try { + expect(called).to.be(true); + done(); + } catch (err) { + done(err); + } + }); + + // fire of the job worker + worker._performJob(job); + }); + }); + + describe('worker failure', function () { + let failStub; + + const timeout = 20; + const job = { + _id: 'testTimeoutJob', + _source: { + timeout: timeout, + payload: 'test', + }, + }; + + beforeEach(function () { + sinon + .stub(mockQueue.client, 'callAsInternalUser') + .withArgs('search') + .callsFake(() => Promise.resolve({ hits: { hits: [] } })); + }); + + describe('workerFn rejects promise', function () { + beforeEach(function () { + const workerFn = function () { + return new Promise(function (resolve, reject) { + setTimeout(() => { + reject(); + }, timeout / 2); + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + failStub = getFailStub(worker); + }); + + it('should fail the job', function () { + return worker._performJob(job).then(() => { + sinon.assert.calledOnce(failStub); + }); + }); + + it('should emit worker execution error', function (done) { + worker.on(constants.EVENT_WORKER_JOB_EXECUTION_ERROR, (err) => { + try { + expect(err).to.have.property('error'); + expect(err).to.have.property('job'); + expect(err).to.have.property('worker'); + done(); + } catch (e) { + done(e); + } + }); + + // fire of the job worker + worker._performJob(job); + }); + }); + + describe('workerFn throws error', function () { + beforeEach(function () { + const workerFn = function () { + throw new Error('test throw'); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + + failStub = getFailStub(worker); + }); + + it('should fail the job', function () { + return worker._performJob(job).then(() => { + sinon.assert.calledOnce(failStub); + }); + }); + + it('should emit worker execution error', function (done) { + worker.on(constants.EVENT_WORKER_JOB_EXECUTION_ERROR, (err) => { + try { + expect(err).to.have.property('error'); + expect(err).to.have.property('job'); + expect(err).to.have.property('worker'); + done(); + } catch (e) { + done(e); + } + }); + + // fire of the job worker + worker._performJob(job); + }); + }); + }); + }); +}); + +describe('Format Job Object', () => { + it('pulls index and ID', function () { + const jobMock = { + _index: 'foo', + _id: 'booId', + }; + expect(formatJobObject(jobMock)).eql({ + index: 'foo', + id: 'booId', + }); + }); +}); + +describe('Get Doc Path from ES Response', () => { + it('returns a formatted string after response of an update', function () { + const responseMock = { + _index: 'foo', + _id: 'booId', + }; + expect(getUpdatedDocPath(responseMock)).equal('/foo/booId'); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/default_settings.js b/x-pack/plugins/reporting/server/lib/esqueue/constants/default_settings.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/default_settings.js rename to x-pack/plugins/reporting/server/lib/esqueue/constants/default_settings.js diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/events.js b/x-pack/plugins/reporting/server/lib/esqueue/constants/events.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/events.js rename to x-pack/plugins/reporting/server/lib/esqueue/constants/events.js diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/index.js b/x-pack/plugins/reporting/server/lib/esqueue/constants/index.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/index.js rename to x-pack/plugins/reporting/server/lib/esqueue/constants/index.js diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.ts b/x-pack/plugins/reporting/server/lib/esqueue/constants/statuses.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.ts rename to x-pack/plugins/reporting/server/lib/esqueue/constants/statuses.ts diff --git a/x-pack/plugins/reporting/server/lib/esqueue/helpers/create_index.js b/x-pack/plugins/reporting/server/lib/esqueue/helpers/create_index.js new file mode 100644 index 0000000000000..c0ce7548e2e1a --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/helpers/create_index.js @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { constants } from '../constants'; + +const schema = { + meta: { + // We are indexing these properties with both text and keyword fields because that's what will be auto generated + // when an index already exists. This schema is only used when a reporting index doesn't exist. This way existing + // reporting indexes and new reporting indexes will look the same and the data can be queried in the same + // manner. + properties: { + /** + * Type of object that is triggering this report. Should be either search, visualization or dashboard. + * Used for job listing and telemetry stats only. + */ + objectType: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + /** + * Can be either preserve_layout, print or none (in the case of csv export). + * Used for phone home stats only. + */ + layout: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + browser_type: { type: 'keyword' }, + jobtype: { type: 'keyword' }, + payload: { type: 'object', enabled: false }, + priority: { type: 'byte' }, + timeout: { type: 'long' }, + process_expiration: { type: 'date' }, + created_by: { type: 'keyword' }, + created_at: { type: 'date' }, + started_at: { type: 'date' }, + completed_at: { type: 'date' }, + attempts: { type: 'short' }, + max_attempts: { type: 'short' }, + kibana_name: { type: 'keyword' }, + kibana_id: { type: 'keyword' }, + status: { type: 'keyword' }, + output: { + type: 'object', + properties: { + content_type: { type: 'keyword' }, + size: { type: 'long' }, + content: { type: 'object', enabled: false }, + }, + }, +}; + +export function createIndex(client, indexName, indexSettings = {}) { + const body = { + settings: { + ...constants.DEFAULT_SETTING_INDEX_SETTINGS, + ...indexSettings, + }, + mappings: { + properties: schema, + }, + }; + + return client + .callAsInternalUser('indices.exists', { + index: indexName, + }) + .then((exists) => { + if (!exists) { + return client + .callAsInternalUser('indices.create', { + index: indexName, + body: body, + }) + .then(() => true) + .catch((err) => { + /* FIXME creating the index will fail if there were multiple jobs staged in parallel. + * Each staged job checks `client.indices.exists` and could each get `false` as a response. + * Only the first job in line can successfully create it though. + * The problem might only happen in automated tests, where the indices are deleted after each test run. + * This catch block is in place to not fail a job if the job runner hits this race condition. + * Unfortunately we don't have a logger in scope to log a warning. + */ + const isIndexExistsError = + err && + err.body && + err.body.error && + err.body.error.type === 'resource_already_exists_exception'; + if (isIndexExistsError) { + return true; + } + + throw err; + }); + } + return exists; + }); +} diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/errors.js b/x-pack/plugins/reporting/server/lib/esqueue/helpers/errors.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/errors.js rename to x-pack/plugins/reporting/server/lib/esqueue/helpers/errors.js diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js b/x-pack/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js rename to x-pack/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js diff --git a/x-pack/plugins/reporting/server/lib/esqueue/index.js b/x-pack/plugins/reporting/server/lib/esqueue/index.js new file mode 100644 index 0000000000000..735d19f8f6c47 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/index.js @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EventEmitter } from 'events'; +import { Job } from './job'; +import { Worker } from './worker'; +import { constants } from './constants'; +import { indexTimestamp } from './helpers/index_timestamp'; +import { omit } from 'lodash'; + +export { events } from './constants/events'; + +export class Esqueue extends EventEmitter { + constructor(index, options = {}) { + if (!index) throw new Error('Must specify an index to write to'); + + super(); + this.index = index; + this.settings = { + interval: constants.DEFAULT_SETTING_INTERVAL, + timeout: constants.DEFAULT_SETTING_TIMEOUT, + dateSeparator: constants.DEFAULT_SETTING_DATE_SEPARATOR, + ...omit(options, ['client']), + }; + this.client = options.client; + this._logger = options.logger || function () {}; + this._workers = []; + this._initTasks().catch((err) => this.emit(constants.EVENT_QUEUE_ERROR, err)); + } + + _initTasks() { + const initTasks = [this.client.callAsInternalUser('ping')]; + + return Promise.all(initTasks).catch((err) => { + this._logger(['initTasks', 'error'], err); + throw err; + }); + } + + addJob(jobtype, payload, opts = {}) { + const timestamp = indexTimestamp(this.settings.interval, this.settings.dateSeparator); + const index = `${this.index}-${timestamp}`; + const defaults = { + timeout: this.settings.timeout, + }; + + const options = Object.assign(defaults, opts, { + indexSettings: this.settings.indexSettings, + logger: this._logger, + }); + + return new Job(this, index, jobtype, payload, options); + } + + registerWorker(type, workerFn, opts) { + const worker = new Worker(this, type, workerFn, { ...opts, logger: this._logger }); + this._workers.push(worker); + return worker; + } + + getWorkers() { + return this._workers.map((fn) => fn); + } + + destroy() { + const workers = this._workers.filter((worker) => worker.destroy()); + this._workers = workers; + } +} diff --git a/x-pack/plugins/reporting/server/lib/esqueue/job.js b/x-pack/plugins/reporting/server/lib/esqueue/job.js new file mode 100644 index 0000000000000..6ab78eeb1b86b --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/job.js @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import events from 'events'; +import Puid from 'puid'; +import { constants } from './constants'; +import { createIndex } from './helpers/create_index'; +import { isPlainObject } from 'lodash'; + +const puid = new Puid(); + +export class Job extends events.EventEmitter { + constructor(queue, index, jobtype, payload, options = {}) { + if (typeof jobtype !== 'string') throw new Error('Jobtype must be a string'); + if (!isPlainObject(payload)) throw new Error('Payload must be a plain object'); + + super(); + + this.queue = queue; + this._client = this.queue.client; + this.id = puid.generate(); + this.index = index; + this.jobtype = jobtype; + this.payload = payload; + this.created_by = options.created_by || false; + this.timeout = options.timeout || 10000; + this.maxAttempts = options.max_attempts || 3; + this.priority = Math.max(Math.min(options.priority || 10, 20), -20); + this.indexSettings = options.indexSettings || {}; + this.browser_type = options.browser_type; + + if (typeof this.maxAttempts !== 'number' || this.maxAttempts < 1) { + throw new Error(`Invalid max_attempts: ${this.maxAttempts}`); + } + + this.debug = (msg, err) => { + const logger = options.logger || function () {}; + const message = `${this.id} - ${msg}`; + const tags = ['debug']; + + if (err) { + logger(`${message}: ${err}`, tags); + return; + } + + logger(message, tags); + }; + + const indexParams = { + index: this.index, + id: this.id, + body: { + jobtype: this.jobtype, + meta: { + // We are copying these values out of payload because these fields are indexed and can be aggregated on + // for tracking stats, while payload contents are not. + objectType: payload.objectType, + layout: payload.layout ? payload.layout.id : 'none', + }, + payload: this.payload, + priority: this.priority, + created_by: this.created_by, + timeout: this.timeout, + process_expiration: new Date(0), // use epoch so the job query works + created_at: new Date(), + attempts: 0, + max_attempts: this.maxAttempts, + status: constants.JOB_STATUS_PENDING, + browser_type: this.browser_type, + }, + }; + + if (options.headers) { + indexParams.headers = options.headers; + } + + this.ready = createIndex(this._client, this.index, this.indexSettings) + .then(() => this._client.callAsInternalUser('index', indexParams)) + .then((doc) => { + this.document = { + id: doc._id, + index: doc._index, + _seq_no: doc._seq_no, + _primary_term: doc._primary_term, + }; + this.debug(`Job created in index ${this.index}`); + + return this._client + .callAsInternalUser('indices.refresh', { + index: this.index, + }) + .then(() => { + this.debug(`Job index refreshed ${this.index}`); + this.emit(constants.EVENT_JOB_CREATED, this.document); + }); + }) + .catch((err) => { + this.debug('Job creation failed', err); + this.emit(constants.EVENT_JOB_CREATE_ERROR, err); + }); + } + + emit(name, ...args) { + super.emit(name, ...args); + this.queue.emit(name, ...args); + } + + get() { + return this.ready + .then(() => { + return this._client.callAsInternalUser('get', { + index: this.index, + id: this.id, + }); + }) + .then((doc) => { + return Object.assign(doc._source, { + index: doc._index, + id: doc._id, + _seq_no: doc._seq_no, + _primary_term: doc._primary_term, + }); + }); + } + + toJSON() { + return { + id: this.id, + index: this.index, + jobtype: this.jobtype, + created_by: this.created_by, + payload: this.payload, + timeout: this.timeout, + max_attempts: this.maxAttempts, + priority: this.priority, + browser_type: this.browser_type, + }; + } +} diff --git a/x-pack/plugins/reporting/server/lib/esqueue/worker.js b/x-pack/plugins/reporting/server/lib/esqueue/worker.js new file mode 100644 index 0000000000000..b26ed731c6831 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/esqueue/worker.js @@ -0,0 +1,463 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import events from 'events'; +import moment from 'moment'; +import Puid from 'puid'; +import { CancellationToken, Poller } from '../../../common'; +import { constants } from './constants'; +import { UnspecifiedWorkerError, WorkerTimeoutError } from './helpers/errors'; + +const puid = new Puid(); + +export function formatJobObject(job) { + return { + index: job._index, + id: job._id, + }; +} + +export function getUpdatedDocPath(response) { + const { _index: ind, _id: id } = response; + return `/${ind}/${id}`; +} + +const MAX_PARTIAL_ERROR_LENGTH = 1000; // 1000 of beginning, 1000 of end +const ERROR_PARTIAL_SEPARATOR = '...'; +const MAX_ERROR_LENGTH = MAX_PARTIAL_ERROR_LENGTH * 2 + ERROR_PARTIAL_SEPARATOR.length; + +function getLogger(opts, id, logLevel) { + return (msg, err) => { + /* + * This does not get the logger instance from queue.registerWorker in the createWorker function. + * The logger instance in the Equeue lib comes from createTaggedLogger, so logLevel tags are passed differently + */ + const logger = opts.logger || function () {}; + const message = `${id} - ${msg}`; + const tags = [logLevel]; + + if (err) { + // The error message string could be very long if it contains the request + // body of a request that was too large for Elasticsearch. + // This takes a partial version of the error message without scanning + // every character of the string, which would block Node. + const errString = `${message}: ${err.stack ? err.stack : err}`; + const errLength = errString.length; + const subStr = String.prototype.substring.bind(errString); + if (errLength > MAX_ERROR_LENGTH) { + const partialError = + subStr(0, MAX_PARTIAL_ERROR_LENGTH) + + ERROR_PARTIAL_SEPARATOR + + subStr(errLength - MAX_PARTIAL_ERROR_LENGTH); + + logger(partialError, tags); + logger( + `A partial version of the entire error message was logged. ` + + `The entire error message length is: ${errLength} characters.`, + tags + ); + } else { + logger(errString, tags); + } + return; + } + + logger(message, tags); + }; +} + +export class Worker extends events.EventEmitter { + constructor(queue, type, workerFn, opts) { + if (typeof type !== 'string') throw new Error('type must be a string'); + if (typeof workerFn !== 'function') throw new Error('workerFn must be a function'); + if (typeof opts !== 'object') throw new Error('opts must be an object'); + if (typeof opts.interval !== 'number') throw new Error('opts.interval must be a number'); + if (typeof opts.intervalErrorMultiplier !== 'number') + throw new Error('opts.intervalErrorMultiplier must be a number'); + + super(); + + this.id = puid.generate(); + this.kibanaId = opts.kibanaId; + this.kibanaName = opts.kibanaName; + this.queue = queue; + this._client = this.queue.client; + this.jobtype = type; + this.workerFn = workerFn; + + this.debug = getLogger(opts, this.id, 'debug'); + this.warn = getLogger(opts, this.id, 'warning'); + this.error = getLogger(opts, this.id, 'error'); + this.info = getLogger(opts, this.id, 'info'); + + this._running = true; + this.debug(`Created worker for ${this.jobtype} jobs`); + + this._poller = new Poller({ + functionToPoll: () => { + return this._processPendingJobs(); + }, + pollFrequencyInMillis: opts.interval, + trailing: true, + continuePollingOnError: true, + pollFrequencyErrorMultiplier: opts.intervalErrorMultiplier, + }); + this._startJobPolling(); + } + + destroy() { + this._running = false; + this._stopJobPolling(); + } + + toJSON() { + return { + id: this.id, + index: this.queue.index, + jobType: this.jobType, + }; + } + + emit(name, ...args) { + super.emit(name, ...args); + this.queue.emit(name, ...args); + } + + _formatErrorParams(err, job) { + const response = { + error: err, + worker: this.toJSON(), + }; + + if (job) response.job = formatJobObject(job); + return response; + } + + _claimJob(job) { + const m = moment(); + const startTime = m.toISOString(); + const expirationTime = m.add(job._source.timeout).toISOString(); + const attempts = job._source.attempts + 1; + + if (attempts > job._source.max_attempts) { + const msg = !job._source.output + ? `Max attempts reached (${job._source.max_attempts})` + : false; + return this._failJob(job, msg).then(() => false); + } + + const doc = { + attempts: attempts, + started_at: startTime, + process_expiration: expirationTime, + status: constants.JOB_STATUS_PROCESSING, + kibana_id: this.kibanaId, + kibana_name: this.kibanaName, + }; + + return this._client + .callAsInternalUser('update', { + index: job._index, + id: job._id, + if_seq_no: job._seq_no, + if_primary_term: job._primary_term, + body: { doc }, + }) + .then((response) => { + this.info(`Job marked as claimed: ${getUpdatedDocPath(response)}`); + const updatedJob = { + ...job, + ...response, + }; + updatedJob._source = { + ...job._source, + ...doc, + }; + return updatedJob; + }); + } + + _failJob(job, output = false) { + this.warn(`Failing job ${job._id}`); + + const completedTime = moment().toISOString(); + const docOutput = this._formatOutput(output); + const doc = { + status: constants.JOB_STATUS_FAILED, + completed_at: completedTime, + output: docOutput, + }; + + this.emit(constants.EVENT_WORKER_JOB_FAIL, { + job: formatJobObject(job), + worker: this.toJSON(), + output: docOutput, + }); + + return this._client + .callAsInternalUser('update', { + index: job._index, + id: job._id, + if_seq_no: job._seq_no, + if_primary_term: job._primary_term, + body: { doc }, + }) + .then((response) => { + this.info(`Job marked as failed: ${getUpdatedDocPath(response)}`); + }) + .catch((err) => { + if (err.statusCode === 409) return true; + this.error(`_failJob failed to update job ${job._id}`, err); + this.emit(constants.EVENT_WORKER_FAIL_UPDATE_ERROR, this._formatErrorParams(err, job)); + return false; + }); + } + + _formatOutput(output) { + const unknownMime = false; + const defaultOutput = null; + const docOutput = {}; + + if (typeof output === 'object' && output.content) { + docOutput.content = output.content; + docOutput.content_type = output.content_type || unknownMime; + docOutput.max_size_reached = output.max_size_reached; + docOutput.csv_contains_formulas = output.csv_contains_formulas; + docOutput.size = output.size; + docOutput.warnings = + output.warnings && output.warnings.length > 0 ? output.warnings : undefined; + } else { + docOutput.content = output || defaultOutput; + docOutput.content_type = unknownMime; + } + + return docOutput; + } + + _performJob(job) { + this.info(`Starting job`); + + const workerOutput = new Promise((resolve, reject) => { + // run the worker's workerFn + let isResolved = false; + const cancellationToken = new CancellationToken(); + const jobSource = job._source; + + Promise.resolve(this.workerFn.call(null, job, jobSource.payload, cancellationToken)) + .then((res) => { + // job execution was successful + if (res && res.warnings && res.warnings.length > 0) { + this.warn(`Job execution completed with warnings`); + } else { + this.info(`Job execution completed successfully`); + } + + isResolved = true; + resolve(res); + }) + .catch((err) => { + isResolved = true; + reject(err); + }); + + // fail if workerFn doesn't finish before timeout + const { timeout } = jobSource; + setTimeout(() => { + if (isResolved) return; + + cancellationToken.cancel(); + this.warn(`Timeout processing job ${job._id}`); + reject( + new WorkerTimeoutError(`Worker timed out, timeout = ${timeout}`, { + jobId: job._id, + timeout, + }) + ); + }, timeout); + }); + + return workerOutput.then( + (output) => { + const completedTime = moment().toISOString(); + const docOutput = this._formatOutput(output); + + const status = + output && output.warnings && output.warnings.length > 0 + ? constants.JOB_STATUS_WARNINGS + : constants.JOB_STATUS_COMPLETED; + const doc = { + status, + completed_at: completedTime, + output: docOutput, + }; + + return this._client + .callAsInternalUser('update', { + index: job._index, + id: job._id, + if_seq_no: job._seq_no, + if_primary_term: job._primary_term, + body: { doc }, + }) + .then((response) => { + const eventOutput = { + job: formatJobObject(job), + output: docOutput, + }; + this.emit(constants.EVENT_WORKER_COMPLETE, eventOutput); + + this.info(`Job data saved successfully: ${getUpdatedDocPath(response)}`); + }) + .catch((err) => { + if (err.statusCode === 409) return false; + this.error(`Failure saving job output ${job._id}`, err); + this.emit(constants.EVENT_WORKER_JOB_UPDATE_ERROR, this._formatErrorParams(err, job)); + return this._failJob(job, err.message ? err.message : false); + }); + }, + (jobErr) => { + if (!jobErr) { + jobErr = new UnspecifiedWorkerError('Unspecified worker error', { + jobId: job._id, + }); + } + + // job execution failed + if (jobErr.name === 'WorkerTimeoutError') { + this.warn(`Timeout on job ${job._id}`); + this.emit(constants.EVENT_WORKER_JOB_TIMEOUT, this._formatErrorParams(jobErr, job)); + return; + + // append the jobId to the error + } else { + try { + Object.assign(jobErr, { jobId: job._id }); + } catch (e) { + // do nothing if jobId can not be appended + } + } + + this.error(`Failure occurred on job ${job._id}`, jobErr); + this.emit(constants.EVENT_WORKER_JOB_EXECUTION_ERROR, this._formatErrorParams(jobErr, job)); + return this._failJob(job, jobErr.toString ? jobErr.toString() : false); + } + ); + } + + _startJobPolling() { + if (!this._running) { + return; + } + + this._poller.start(); + } + + _stopJobPolling() { + this._poller.stop(); + } + + _processPendingJobs() { + return this._getPendingJobs().then((jobs) => { + return this._claimPendingJobs(jobs); + }); + } + + _claimPendingJobs(jobs) { + if (!jobs || jobs.length === 0) return; + + let claimed = false; + + // claim a single job, stopping after first successful claim + return jobs + .reduce((chain, job) => { + return chain.then((claimedJob) => { + // short-circuit the promise chain if a job has been claimed + if (claimed) return claimedJob; + + return this._claimJob(job) + .then((claimResult) => { + claimed = true; + return claimResult; + }) + .catch((err) => { + if (err.statusCode === 409) { + this.warn( + `_claimPendingJobs encountered a version conflict on updating pending job ${job._id}`, + err + ); + return; // continue reducing and looking for a different job to claim + } + this.emit(constants.EVENT_WORKER_JOB_CLAIM_ERROR, this._formatErrorParams(err, job)); + return Promise.reject(err); + }); + }); + }, Promise.resolve()) + .then((claimedJob) => { + if (!claimedJob) { + this.debug(`Found no claimable jobs out of ${jobs.length} total`); + return; + } + return this._performJob(claimedJob); + }) + .catch((err) => { + this.error('Error claiming jobs', err); + return Promise.reject(err); + }); + } + + _getPendingJobs() { + const nowTime = moment().toISOString(); + const query = { + seq_no_primary_term: true, + _source: { + excludes: ['output.content'], + }, + query: { + bool: { + filter: { + bool: { + minimum_should_match: 1, + should: [ + { term: { status: 'pending' } }, + { + bool: { + must: [ + { term: { status: 'processing' } }, + { range: { process_expiration: { lte: nowTime } } }, + ], + }, + }, + ], + }, + }, + }, + }, + sort: [{ priority: { order: 'asc' } }, { created_at: { order: 'asc' } }], + size: constants.DEFAULT_WORKER_CHECK_SIZE, + }; + + return this._client + .callAsInternalUser('search', { + index: `${this.queue.index}-*`, + body: query, + }) + .then((results) => { + const jobs = results.hits.hits; + if (jobs.length > 0) { + this.debug(`${jobs.length} outstanding jobs returned`); + } + return jobs; + }) + .catch((err) => { + // ignore missing indices errors + if (err && err.status === 404) return []; + + this.error('job querying failed', err); + this.emit(constants.EVENT_WORKER_JOB_SEARCH_ERROR, this._formatErrorParams(err)); + throw err; + }); + } +} diff --git a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts b/x-pack/plugins/reporting/server/lib/export_types_registry.ts similarity index 90% rename from x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts rename to x-pack/plugins/reporting/server/lib/export_types_registry.ts index ecaabb305e23e..893a2635561ff 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts +++ b/x-pack/plugins/reporting/server/lib/export_types_registry.ts @@ -6,10 +6,10 @@ import { isString } from 'lodash'; import memoizeOne from 'memoize-one'; -import { getExportType as getTypeCsv } from '../../export_types/csv'; -import { getExportType as getTypeCsvFromSavedObject } from '../../export_types/csv_from_savedobject'; -import { getExportType as getTypePng } from '../../export_types/png'; -import { getExportType as getTypePrintablePdf } from '../../export_types/printable_pdf'; +import { getExportType as getTypeCsv } from '../export_types/csv'; +import { getExportType as getTypeCsvFromSavedObject } from '../export_types/csv_from_savedobject'; +import { getExportType as getTypePng } from '../export_types/png'; +import { getExportType as getTypePrintablePdf } from '../export_types/printable_pdf'; import { ExportTypeDefinition } from '../types'; type GetCallbackFn = ( @@ -103,7 +103,7 @@ function getExportTypesRegistryFn(): ExportTypesRegistry { getTypePng, getTypePrintablePdf, ]; - getTypeFns.forEach(getType => { + getTypeFns.forEach((getType) => { registry.register(getType()); }); return registry; diff --git a/x-pack/plugins/reporting/server/lib/get_user.ts b/x-pack/plugins/reporting/server/lib/get_user.ts new file mode 100644 index 0000000000000..49d15a7c55100 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/get_user.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaRequest } from 'kibana/server'; +import { SecurityPluginSetup } from '../../../security/server'; + +export function getUserFactory(security?: SecurityPluginSetup) { + return (request: KibanaRequest) => { + return security?.authc.getCurrentUser(request) ?? null; + }; +} diff --git a/x-pack/plugins/reporting/server/lib/index.ts b/x-pack/plugins/reporting/server/lib/index.ts new file mode 100644 index 0000000000000..0e9c49b170887 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { LevelLogger } from './level_logger'; +export { checkLicense } from './check_license'; +export { createQueueFactory } from './create_queue'; +export { cryptoFactory } from './crypto'; +export { enqueueJobFactory } from './enqueue_job'; +export { getExportTypesRegistry } from './export_types_registry'; +export { runValidations } from './validate'; +export { startTrace } from './trace'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts b/x-pack/plugins/reporting/server/lib/jobs_query.ts similarity index 82% rename from x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts rename to x-pack/plugins/reporting/server/lib/jobs_query.ts index e0c9fc05ea2b4..8784d8ff35d25 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts +++ b/x-pack/plugins/reporting/server/lib/jobs_query.ts @@ -5,10 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import Boom from 'boom'; import { errors as elasticsearchErrors } from 'elasticsearch'; import { ElasticsearchServiceSetup } from 'kibana/server'; import { get } from 'lodash'; +import { AuthenticatedUser } from '../../../security/server'; import { ReportingConfig } from '../'; import { JobSource } from '../types'; @@ -40,16 +40,14 @@ interface CountAggResult { count: number; } +const getUsername = (user: AuthenticatedUser | null) => (user ? user.username : false); + export function jobsQueryFactory( config: ReportingConfig, elasticsearch: ElasticsearchServiceSetup ) { const index = config.get('index'); - const { callAsInternalUser } = elasticsearch.adminClient; - - function getUsername(user: any) { - return get(user, 'username', false); - } + const { callAsInternalUser } = elasticsearch.legacy.client; function execQuery(queryType: string, body: QueryBody) { const defaultBody: Record = { @@ -67,7 +65,7 @@ export function jobsQueryFactory( body: Object.assign(defaultBody[queryType] || {}, body), }; - return callAsInternalUser(queryType, query).catch(err => { + return callAsInternalUser(queryType, query).catch((err) => { if (err instanceof esErrors['401']) return; if (err instanceof esErrors['403']) return; if (err instanceof esErrors['404']) return; @@ -78,13 +76,18 @@ export function jobsQueryFactory( type Result = number; function getHits(query: Promise) { - return query.then(res => get(res, 'hits.hits', [])); + return query.then((res) => get(res, 'hits.hits', [])); } return { - list(jobTypes: string[], user: any, page = 0, size = defaultSize, jobIds: string[] | null) { + list( + jobTypes: string[], + user: AuthenticatedUser | null, + page = 0, + size = defaultSize, + jobIds: string[] | null + ) { const username = getUsername(user); - const body: QueryBody = { size, from: size * page, @@ -108,9 +111,8 @@ export function jobsQueryFactory( return getHits(execQuery('search', body)); }, - count(jobTypes: string[], user: any) { + count(jobTypes: string[], user: AuthenticatedUser | null) { const username = getUsername(user); - const body: QueryBody = { query: { constant_score: { @@ -129,9 +131,12 @@ export function jobsQueryFactory( }); }, - get(user: any, id: string, opts: GetOpts = {}): Promise | void> { + get( + user: AuthenticatedUser | null, + id: string, + opts: GetOpts = {} + ): Promise | void> { if (!id) return Promise.resolve(); - const username = getUsername(user); const body: QueryBody = { @@ -153,7 +158,7 @@ export function jobsQueryFactory( }; } - return getHits(execQuery('search', body)).then(hits => { + return getHits(execQuery('search', body)).then((hits) => { if (hits.length !== 1) return; return hits[0]; }); @@ -164,14 +169,12 @@ export function jobsQueryFactory( const query = { id, index: deleteIndex }; return callAsInternalUser('delete', query); } catch (error) { - const wrappedError = new Error( + throw new Error( i18n.translate('xpack.reporting.jobsQuery.deleteError', { defaultMessage: 'Could not delete the report: {error}', values: { error: error.message }, }) ); - - throw Boom.boomify(wrappedError, { statusCode: error.status }); } }, }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/level_logger.ts b/x-pack/plugins/reporting/server/lib/level_logger.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/level_logger.ts rename to x-pack/plugins/reporting/server/lib/level_logger.ts diff --git a/x-pack/legacy/plugins/reporting/server/lib/trace.ts b/x-pack/plugins/reporting/server/lib/trace.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/trace.ts rename to x-pack/plugins/reporting/server/lib/trace.ts diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts b/x-pack/plugins/reporting/server/lib/validate/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/validate/index.ts rename to x-pack/plugins/reporting/server/lib/validate/index.ts diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts b/x-pack/plugins/reporting/server/lib/validate/validate_browser.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts rename to x-pack/plugins/reporting/server/lib/validate/validate_browser.ts diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.test.js b/x-pack/plugins/reporting/server/lib/validate/validate_max_content_length.test.js similarity index 88% rename from x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.test.js rename to x-pack/plugins/reporting/server/lib/validate/validate_max_content_length.test.js index 2551fd48b91f3..f358021560cff 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.test.js +++ b/x-pack/plugins/reporting/server/lib/validate/validate_max_content_length.test.js @@ -12,14 +12,16 @@ const ONE_HUNDRED_MEGABYTES = 104857600; describe('Reporting: Validate Max Content Length', () => { const elasticsearch = { - dataClient: { - callAsInternalUser: () => ({ - defaults: { - http: { - max_content_length: '100mb', + legacy: { + client: { + callAsInternalUser: () => ({ + defaults: { + http: { + max_content_length: '100mb', + }, }, - }, - }), + }), + }, }, }; @@ -34,14 +36,16 @@ describe('Reporting: Validate Max Content Length', () => { it('should log warning messages when reporting has a higher max-size than elasticsearch', async () => { const config = { get: sinon.stub().returns(FIVE_HUNDRED_MEGABYTES) }; const elasticsearch = { - dataClient: { - callAsInternalUser: () => ({ - defaults: { - http: { - max_content_length: '100mb', + legacy: { + client: { + callAsInternalUser: () => ({ + defaults: { + http: { + max_content_length: '100mb', + }, }, - }, - }), + }), + }, }, }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts b/x-pack/plugins/reporting/server/lib/validate/validate_max_content_length.ts similarity index 96% rename from x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts rename to x-pack/plugins/reporting/server/lib/validate/validate_max_content_length.ts index f6acf72612e01..6d34937d9bd75 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts +++ b/x-pack/plugins/reporting/server/lib/validate/validate_max_content_length.ts @@ -18,7 +18,7 @@ export async function validateMaxContentLength( elasticsearch: ElasticsearchServiceSetup, logger: LevelLogger ) { - const { callAsInternalUser } = elasticsearch.dataClient; + const { callAsInternalUser } = elasticsearch.legacy.client; const elasticClusterSettingsResponse = await callAsInternalUser('cluster.getSettings', { includeDefaults: true, diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 905ed2b237c86..d0d25f6d9e0ae 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -6,33 +6,85 @@ import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; -import { ConfigType, createConfig$ } from './config'; - -export interface PluginsSetup { - /** @deprecated */ - __legacy: { - config$: Observable; - }; -} +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server'; +import { ReportingCore } from './core'; +import { ReportingConfigType } from './config'; +import { createBrowserDriverFactory } from './browsers'; +import { buildConfig, createConfig$ } from './config'; +import { createQueueFactory, enqueueJobFactory, LevelLogger, runValidations } from './lib'; +import { registerRoutes } from './routes'; +import { setFieldFormats } from './services'; +import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types'; +import { registerReportingUsageCollector } from './usage'; -export class ReportingPlugin implements Plugin { - private readonly log: Logger; +export class ReportingPlugin + implements Plugin { + private readonly initializerContext: PluginInitializerContext; + private logger: LevelLogger; + private reportingCore?: ReportingCore; + private config$: Observable; - constructor(private readonly initializerContext: PluginInitializerContext) { - this.log = this.initializerContext.logger.get(); + constructor(context: PluginInitializerContext) { + this.logger = new LevelLogger(context.logger.get()); + this.initializerContext = context; + this.config$ = context.config.create(); } - public async setup(core: CoreSetup): Promise { - return { - __legacy: { - config$: createConfig$(core, this.initializerContext, this.log).pipe(first()), - }, - }; + public async setup(core: CoreSetup, plugins: ReportingSetupDeps) { + const { elasticsearch, http } = core; + const { licensing, security } = plugins; + const { initializerContext: initContext } = this; + const router = http.createRouter(); + const basePath = http.basePath.get; + + const coreConfig = await createConfig$(core, this.config$, this.logger) + .pipe(first()) + .toPromise(); // apply computed defaults to config + const reportingConfig = buildConfig(initContext, core, coreConfig); // combine kbnServer configs + this.reportingCore = new ReportingCore(reportingConfig); + + const browserDriverFactory = await createBrowserDriverFactory(reportingConfig, this.logger); + + this.reportingCore.pluginSetup({ + browserDriverFactory, + elasticsearch, + licensing, + basePath, + router, + security, + }); + + runValidations(reportingConfig, elasticsearch, browserDriverFactory, this.logger); + registerReportingUsageCollector(this.reportingCore, plugins); + registerRoutes(this.reportingCore, this.logger); + + return {}; } - public start() {} - public stop() {} -} + public async start(core: CoreStart, plugins: ReportingStartDeps) { + const { logger } = this; + const reportingCore = this.getReportingCore(); -export { ConfigType }; + const esqueue = await createQueueFactory(reportingCore, logger); + const enqueueJob = enqueueJobFactory(reportingCore, logger); + + reportingCore.pluginStart({ + savedObjects: core.savedObjects, + uiSettings: core.uiSettings, + esqueue, + enqueueJob, + }); + + setFieldFormats(plugins.data.fieldFormats); + logger.info('reporting plugin started'); + + return {}; + } + + public getReportingCore() { + if (!this.reportingCore) { + throw new Error('Setup is not ready'); + } + return this.reportingCore; + } +} diff --git a/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts new file mode 100644 index 0000000000000..2a12a64d67a35 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import rison from 'rison-node'; +import { schema } from '@kbn/config-schema'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; +import { HandlerErrorFunction, HandlerFunction } from './types'; +import { ReportingCore } from '../'; +import { API_BASE_URL } from '../../common/constants'; + +const BASE_GENERATE = `${API_BASE_URL}/generate`; + +export function registerGenerateFromJobParams( + reporting: ReportingCore, + handler: HandlerFunction, + handleError: HandlerErrorFunction +) { + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); + const { router } = setupDeps; + + router.post( + { + path: `${BASE_GENERATE}/{exportType}`, + validate: { + params: schema.object({ + exportType: schema.string({ minLength: 2 }), + }), + body: schema.nullable( + schema.object({ + jobParams: schema.maybe(schema.string()), + }) + ), + query: schema.nullable( + schema.object({ + jobParams: schema.string({ + defaultValue: '', + }), + }) + ), + }, + }, + userHandler(async (user, context, req, res) => { + let jobParamsRison: string | null; + + if (req.body) { + const { jobParams: jobParamsPayload } = req.body as { jobParams: string }; + jobParamsRison = jobParamsPayload; + } else { + const { jobParams: queryJobParams } = req.query as { jobParams: string }; + if (queryJobParams) { + jobParamsRison = queryJobParams; + } else { + jobParamsRison = null; + } + } + + if (!jobParamsRison) { + return res.customError({ + statusCode: 400, + body: 'A jobParams RISON string is required in the querystring or POST body', + }); + } + + const { exportType } = req.params as { exportType: string }; + let jobParams; + + try { + jobParams = rison.decode(jobParamsRison) as object | null; + if (!jobParams) { + return res.customError({ + statusCode: 400, + body: 'Missing jobParams!', + }); + } + } catch (err) { + return res.customError({ + statusCode: 400, + body: `invalid rison: ${jobParamsRison}`, + }); + } + + try { + return await handler(user, exportType, jobParams, context, req, res); + } catch (err) { + return handleError(res, err); + } + }) + ); + + // Get route to generation endpoint: show error about GET method to user + router.get( + { + path: `${BASE_GENERATE}/{p*}`, + validate: false, + }, + (context, req, res) => { + return res.customError({ statusCode: 405, body: 'GET is not allowed' }); + } + ); +} diff --git a/x-pack/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/plugins/reporting/server/routes/generate_from_savedobject.ts new file mode 100644 index 0000000000000..b8326406743b7 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { get } from 'lodash'; +import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types'; +import { ReportingCore } from '../'; +import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../common/constants'; +import { getJobParamsFromRequest } from '../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; + +/* + * This function registers API Endpoints for queuing Reporting jobs. The API inputs are: + * - saved object type and ID + * - time range and time zone + * - application state: + * - filters + * - query bar + * - local (transient) changes the user made to the saved object + */ +export function registerGenerateCsvFromSavedObject( + reporting: ReportingCore, + handleRoute: HandlerFunction, + handleRouteError: HandlerErrorFunction +) { + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); + const { router } = setupDeps; + router.post( + { + path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, + validate: { + params: schema.object({ + savedObjectType: schema.string({ minLength: 2 }), + savedObjectId: schema.string({ minLength: 2 }), + }), + body: schema.object({ + state: schema.object({}), + timerange: schema.object({ + timezone: schema.string({ defaultValue: 'UTC' }), + min: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), + max: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), + }), + }), + }, + }, + userHandler(async (user, context, req, res) => { + /* + * 1. Build `jobParams` object: job data that execution will need to reference in various parts of the lifecycle + * 2. Pass the jobParams and other common params to `handleRoute`, a shared function to enqueue the job with the params + * 3. Ensure that details for a queued job were returned + */ + let result: QueuedJobPayload; + try { + const jobParams = getJobParamsFromRequest(req, { isImmediate: false }); + result = await handleRoute( + user, + CSV_FROM_SAVEDOBJECT_JOB_TYPE, + jobParams, + context, + req, + res + ); + } catch (err) { + return handleRouteError(res, err); + } + + if (get(result, 'source.job') == null) { + return res.badRequest({ + body: `The Export handler is expected to return a result with job info! ${result}`, + }); + } + + return res.ok({ + body: result, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); +} diff --git a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts new file mode 100644 index 0000000000000..1221f67855410 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { ReportingCore } from '../'; +import { API_BASE_GENERATE_V1 } from '../../common/constants'; +import { createJobFactory } from '../export_types/csv_from_savedobject/server/create_job'; +import { executeJobFactory } from '../export_types/csv_from_savedobject/server/execute_job'; +import { getJobParamsFromRequest } from '../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; +import { JobDocPayloadPanelCsv } from '../export_types/csv_from_savedobject/types'; +import { LevelLogger as Logger } from '../lib'; +import { JobDocOutput } from '../types'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; +import { HandlerErrorFunction } from './types'; + +/* + * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: + * - saved object type and ID + * - time range and time zone + * - application state: + * - filters + * - query bar + * - local (transient) changes the user made to the saved object + */ +export function registerGenerateCsvFromSavedObjectImmediate( + reporting: ReportingCore, + handleError: HandlerErrorFunction, + parentLogger: Logger +) { + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); + const { router } = setupDeps; + + /* + * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: + * - re-use the createJob function to build up es query config + * - re-use the executeJob function to run the scan and scroll queries and capture the entire CSV in a result object. + */ + router.post( + { + path: `${API_BASE_GENERATE_V1}/immediate/csv/saved-object/{savedObjectType}:{savedObjectId}`, + validate: { + params: schema.object({ + savedObjectType: schema.string({ minLength: 5 }), + savedObjectId: schema.string({ minLength: 5 }), + }), + body: schema.object({ + state: schema.object({}, { unknowns: 'allow' }), + timerange: schema.object({ + timezone: schema.string({ defaultValue: 'UTC' }), + min: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), + max: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), + }), + }), + }, + }, + userHandler(async (user, context, req, res) => { + const logger = parentLogger.clone(['savedobject-csv']); + const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); + const createJobFn = createJobFactory(reporting, logger); + const executeJobFn = await executeJobFactory(reporting, logger); // FIXME: does not "need" to be async + + try { + const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( + jobParams, + req.headers, + context, + req + ); + const { + content_type: jobOutputContentType, + content: jobOutputContent, + size: jobOutputSize, + }: JobDocOutput = await executeJobFn(null, jobDocPayload, context, req); + + logger.info(`Job output size: ${jobOutputSize} bytes`); + + /* + * ESQueue worker function defaults `content` to null, even if the + * executeJob returned undefined. + * + * This converts null to undefined so the value can be sent to h.response() + */ + if (jobOutputContent === null) { + logger.warn('CSV Job Execution created empty content result'); + } + + return res.ok({ + body: jobOutputContent || '', + headers: { + 'content-type': jobOutputContentType, + 'accept-ranges': 'none', + }, + }); + } catch (err) { + return handleError(res, err); + } + }) + ); +} diff --git a/x-pack/plugins/reporting/server/routes/generation.test.ts b/x-pack/plugins/reporting/server/routes/generation.test.ts new file mode 100644 index 0000000000000..f9b3e5446cfce --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/generation.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import supertest from 'supertest'; +import { UnwrapPromise } from '@kbn/utility-types'; +import { setupServer } from 'src/core/server/test_utils'; +import { registerJobGenerationRoutes } from './generation'; +import { createMockReportingCore } from '../test_helpers'; +import { ReportingCore } from '..'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; +import { ExportTypeDefinition } from '../types'; +import { LevelLogger } from '../lib'; +import { of } from 'rxjs'; + +type setupServerReturn = UnwrapPromise>; + +describe('POST /api/reporting/generate', () => { + let server: setupServerReturn['server']; + let httpSetup: setupServerReturn['httpSetup']; + let exportTypesRegistry: ExportTypesRegistry; + let core: ReportingCore; + + const config = { + get: jest.fn().mockImplementation((...args) => { + const key = args.join('.'); + switch (key) { + case 'queue.indexInterval': + return 10000; + case 'queue.timeout': + return 10000; + case 'index': + return '.reporting'; + case 'queue.pollEnabled': + return false; + default: + return; + } + }), + kbnConfig: { get: jest.fn() }, + }; + const mockLogger = ({ + error: jest.fn(), + debug: jest.fn(), + } as unknown) as jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup } = await setupServer()); + const mockDeps = ({ + elasticsearch: { + legacy: { + client: { callAsInternalUser: jest.fn() }, + }, + }, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + router: httpSetup.createRouter(''), + licensing: { + license$: of({ + isActive: true, + isAvailable: true, + type: 'gold', + }), + }, + } as unknown) as any; + core = await createMockReportingCore(config, mockDeps); + exportTypesRegistry = new ExportTypesRegistry(); + exportTypesRegistry.register({ + id: 'printablePdf', + jobType: 'printable_pdf', + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + validLicenses: ['basic', 'gold'], + } as ExportTypeDefinition); + core.getExportTypesRegistry = () => exportTypesRegistry; + }); + + afterEach(async () => { + mockLogger.debug.mockReset(); + mockLogger.error.mockReset(); + await server.stop(); + }); + + it('returns 400 if there are no job params', async () => { + registerJobGenerationRoutes(core, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf') + .expect(400) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot( + '"A jobParams RISON string is required in the querystring or POST body"' + ) + ); + }); + + it('returns 400 if job params query is invalid', async () => { + registerJobGenerationRoutes(core, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf?jobParams=foo:') + .expect(400) + .then(({ body }) => expect(body.message).toMatchInlineSnapshot('"invalid rison: foo:"')); + }); + + it('returns 400 if job params body is invalid', async () => { + registerJobGenerationRoutes(core, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf') + .send({ jobParams: `foo:` }) + .expect(400) + .then(({ body }) => expect(body.message).toMatchInlineSnapshot('"invalid rison: foo:"')); + }); + + it('returns 400 export type is invalid', async () => { + registerJobGenerationRoutes(core, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/TonyHawksProSkater2') + .send({ jobParams: `abc` }) + .expect(400) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot('"Invalid export-type of TonyHawksProSkater2"') + ); + }); + + it('returns 400 if job handler throws an error', async () => { + const errorText = 'you found me'; + core.getEnqueueJob = async () => + jest.fn().mockImplementation(() => ({ + toJSON: () => { + throw new Error(errorText); + }, + })); + + registerJobGenerationRoutes(core, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf') + .send({ jobParams: `abc` }) + .expect(400) + .then(({ body }) => { + expect(body.message).toMatchInlineSnapshot(`"${errorText}"`); + }); + }); +}); diff --git a/x-pack/plugins/reporting/server/routes/generation.ts b/x-pack/plugins/reporting/server/routes/generation.ts new file mode 100644 index 0000000000000..f2e616c0803a7 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/generation.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { errors as elasticsearchErrors } from 'elasticsearch'; +import { kibanaResponseFactory } from 'src/core/server'; +import { ReportingCore } from '../'; +import { API_BASE_URL } from '../../common/constants'; +import { LevelLogger as Logger } from '../lib'; +import { registerGenerateFromJobParams } from './generate_from_jobparams'; +import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; +import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; +import { HandlerFunction } from './types'; + +const esErrors = elasticsearchErrors as Record; + +export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Logger) { + const config = reporting.getConfig(); + const downloadBaseUrl = + config.kbnConfig.get('server', 'basePath') + `${API_BASE_URL}/jobs/download`; + + /* + * Generates enqueued job details to use in responses + */ + const handler: HandlerFunction = async (user, exportTypeId, jobParams, context, req, res) => { + const licenseInfo = await reporting.getLicenseInfo(); + const licenseResults = licenseInfo[exportTypeId]; + + if (!licenseResults) { + return res.badRequest({ body: `Invalid export-type of ${exportTypeId}` }); + } + + if (!licenseResults.enableLinks) { + return res.forbidden({ body: licenseResults.message }); + } + + const enqueueJob = await reporting.getEnqueueJob(); + const job = await enqueueJob(exportTypeId, jobParams, user, context, req); + + // return the queue's job information + const jobJson = job.toJSON(); + + return res.ok({ + headers: { + 'content-type': 'application/json', + }, + body: { + path: `${downloadBaseUrl}/${jobJson.id}`, + job: jobJson, + }, + }); + }; + + function handleError(res: typeof kibanaResponseFactory, err: Error | Boom) { + if (err instanceof Boom) { + return res.customError({ + statusCode: err.output.statusCode, + body: err.output.payload.message, + }); + } + + if (err instanceof esErrors['401']) { + return res.unauthorized({ + body: `Sorry, you aren't authenticated`, + }); + } + + if (err instanceof esErrors['403']) { + return res.forbidden({ + body: `Sorry, you are not authorized`, + }); + } + + if (err instanceof esErrors['404']) { + return res.notFound({ + body: err.message, + }); + } + + return res.badRequest({ + body: err.message, + }); + } + + registerGenerateFromJobParams(reporting, handler, handleError); + + // Register beta panel-action download-related API's + if (config.get('csv', 'enablePanelActionDownload')) { + registerGenerateCsvFromSavedObject(reporting, handler, handleError); + registerGenerateCsvFromSavedObjectImmediate(reporting, handleError, logger); + } +} diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts new file mode 100644 index 0000000000000..005d82086665c --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { LevelLogger as Logger } from '../lib'; +import { registerJobGenerationRoutes } from './generation'; +import { registerJobInfoRoutes } from './jobs'; +import { ReportingCore } from '../core'; + +export function registerRoutes(reporting: ReportingCore, logger: Logger) { + registerJobGenerationRoutes(reporting, logger); + registerJobInfoRoutes(reporting); +} diff --git a/x-pack/plugins/reporting/server/routes/jobs.test.ts b/x-pack/plugins/reporting/server/routes/jobs.test.ts new file mode 100644 index 0000000000000..22d60d62d5fdb --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/jobs.test.ts @@ -0,0 +1,349 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UnwrapPromise } from '@kbn/utility-types'; +import { of } from 'rxjs'; +import { setupServer } from 'src/core/server/test_utils'; +import supertest from 'supertest'; +import { ReportingCore } from '..'; +import { ReportingInternalSetup } from '../core'; +import { LevelLogger } from '../lib'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; +import { createMockReportingCore } from '../test_helpers'; +import { ExportTypeDefinition } from '../types'; +import { registerJobInfoRoutes } from './jobs'; + +type setupServerReturn = UnwrapPromise>; + +describe('GET /api/reporting/jobs/download', () => { + let server: setupServerReturn['server']; + let httpSetup: setupServerReturn['httpSetup']; + let exportTypesRegistry: ExportTypesRegistry; + let core: ReportingCore; + + const config = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; + const mockLogger = ({ + error: jest.fn(), + debug: jest.fn(), + } as unknown) as jest.Mocked; + + const getHits = (...sources: any) => { + return { + hits: { + hits: sources.map((source: object) => ({ _source: source })), + }, + }; + }; + + beforeEach(async () => { + ({ server, httpSetup } = await setupServer()); + core = await createMockReportingCore(config, ({ + elasticsearch: { + legacy: { client: { callAsInternalUser: jest.fn() } }, + }, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + router: httpSetup.createRouter(''), + licensing: { + license$: of({ + isActive: true, + isAvailable: true, + type: 'gold', + }), + }, + } as unknown) as ReportingInternalSetup); + // @ts-ignore + exportTypesRegistry = new ExportTypesRegistry(); + exportTypesRegistry.register({ + id: 'unencoded', + jobType: 'unencodedJobType', + jobContentExtension: 'csv', + validLicenses: ['basic', 'gold'], + } as ExportTypeDefinition); + exportTypesRegistry.register({ + id: 'base64Encoded', + jobType: 'base64EncodedJobType', + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + validLicenses: ['basic', 'gold'], + } as ExportTypeDefinition); + core.getExportTypesRegistry = () => exportTypesRegistry; + }); + + afterEach(async () => { + mockLogger.debug.mockReset(); + mockLogger.error.mockReset(); + await server.stop(); + }); + + it('fails on malformed download IDs', async () => { + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), + }; + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/1') + .expect(400) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot( + '"[request params.docId]: value has length [1] but it must have a minimum length of [3]."' + ) + ); + }); + + it('fails on unauthenticated users', async () => { + // @ts-ignore + core.pluginSetupDeps = ({ + // @ts-ignore + ...core.pluginSetupDeps, + security: { + authc: { + getCurrentUser: () => undefined, + }, + }, + } as unknown) as ReportingInternalSetup; + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dope') + .expect(401) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot(`"Sorry, you aren't authenticated"`) + ); + }); + + it('fails on users without the appropriate role', async () => { + // @ts-ignore + core.pluginSetupDeps = ({ + // @ts-ignore + ...core.pluginSetupDeps, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['peasant'], + username: 'Tom Riddle', + }), + }, + }, + } as unknown) as ReportingInternalSetup; + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dope') + .expect(403) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot(`"Sorry, you don't have access to Reporting"`) + ); + }); + + it('returns 404 if job not found', async () => { + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), + }; + + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(404); + }); + + it('returns a 401 if not a valid job type', async () => { + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest + .fn() + .mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))), + }; + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(401); + }); + + it('when a job is incomplete', async () => { + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest + .fn() + .mockReturnValue( + Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' })) + ), + }; + registerJobInfoRoutes(core); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(503) + .expect('Content-Type', 'text/plain; charset=utf-8') + .expect('Retry-After', '30') + .then(({ text }) => expect(text).toEqual('pending')); + }); + + it('when a job fails', async () => { + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue( + Promise.resolve( + getHits({ + jobtype: 'unencodedJobType', + status: 'failed', + output: { content: 'job failure message' }, + }) + ) + ), + }; + registerJobInfoRoutes(core); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(500) + .expect('Content-Type', 'application/json; charset=utf-8') + .then(({ body }) => + expect(body.message).toEqual('Reporting generation failed: job failure message') + ); + }); + + describe('successful downloads', () => { + const getCompleteHits = async ({ + jobType = 'unencodedJobType', + outputContent = 'job output content', + outputContentType = 'text/plain', + title = '', + } = {}) => { + return getHits({ + jobtype: jobType, + status: 'completed', + output: { content: outputContent, content_type: outputContentType }, + payload: { + title, + }, + }); + }; + + it('when a known job-type is complete', async () => { + const hits = getCompleteHits(); + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + registerJobInfoRoutes(core); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(200) + .expect('Content-Type', 'text/plain; charset=utf-8') + .expect('content-disposition', 'inline; filename="report.csv"'); + }); + + it('succeeds when security is not there or disabled', async () => { + const hits = getCompleteHits(); + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + + // @ts-ignore + core.pluginSetupDeps.security = null; + + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dope') + .expect(200) + .expect('Content-Type', 'text/plain; charset=utf-8') + .expect('content-disposition', 'inline; filename="report.csv"'); + }); + + it(`doesn't encode output-content for non-specified job-types`, async () => { + const hits = getCompleteHits({ + jobType: 'unencodedJobType', + outputContent: 'test', + }); + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + registerJobInfoRoutes(core); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(200) + .expect('Content-Type', 'text/plain; charset=utf-8') + .then(({ text }) => expect(text).toEqual('test')); + }); + + it(`base64 encodes output content for configured jobTypes`, async () => { + const hits = getCompleteHits({ + jobType: 'base64EncodedJobType', + outputContent: 'test', + outputContentType: 'application/pdf', + }); + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + registerJobInfoRoutes(core); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(200) + .expect('Content-Type', 'application/pdf') + .expect('content-disposition', 'inline; filename="report.pdf"') + .then(({ body }) => expect(Buffer.from(body).toString('base64')).toEqual('test')); + }); + + it('refuses to return unknown content-types', async () => { + const hits = getCompleteHits({ + jobType: 'unencodedJobType', + outputContent: 'alert("all your base mine now");', + outputContentType: 'application/html', + }); + // @ts-ignore + core.pluginSetupDeps.elasticsearch.legacy.client = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + registerJobInfoRoutes(core); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(400) + .then(({ body }) => { + expect(body).toEqual({ + error: 'Bad Request', + message: 'Unsupported content-type of application/html specified by job output', + statusCode: 400, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts new file mode 100644 index 0000000000000..8c35f79ec0fb4 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -0,0 +1,213 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { schema } from '@kbn/config-schema'; +import { ReportingCore } from '../'; +import { API_BASE_URL } from '../../common/constants'; +import { jobsQueryFactory } from '../lib/jobs_query'; +import { + deleteJobResponseHandlerFactory, + downloadJobResponseHandlerFactory, +} from './lib/job_response_handler'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; + +interface ListQuery { + page: string; + size: string; + ids?: string; // optional field forbids us from extending RequestQuery +} +const MAIN_ENTRY = `${API_BASE_URL}/jobs`; + +export async function registerJobInfoRoutes(reporting: ReportingCore) { + const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); + const { elasticsearch, router } = setupDeps; + const jobsQuery = jobsQueryFactory(config, elasticsearch); + + // list jobs in the queue, paginated + router.get( + { + path: `${MAIN_ENTRY}/list`, + validate: false, + }, + userHandler(async (user, context, req, res) => { + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); + const { + page: queryPage = '0', + size: querySize = '10', + ids: queryIds = null, + } = req.query as ListQuery; + const page = parseInt(queryPage, 10) || 0; + const size = Math.min(100, parseInt(querySize, 10) || 10); + const jobIds = queryIds ? queryIds.split(',') : null; + const results = await jobsQuery.list(jobTypes, user, page, size, jobIds); + + return res.ok({ + body: results, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); + + // return the count of all jobs in the queue + router.get( + { + path: `${MAIN_ENTRY}/count`, + validate: false, + }, + userHandler(async (user, context, req, res) => { + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); + + const count = await jobsQuery.count(jobTypes, user); + + return res.ok({ + body: count.toString(), + headers: { + 'content-type': 'text/plain', + }, + }); + }) + ); + + // return the raw output from a job + router.get( + { + path: `${MAIN_ENTRY}/output/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 2 }), + }), + }, + }, + userHandler(async (user, context, req, res) => { + const { docId } = req.params as { docId: string }; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); + + const result = await jobsQuery.get(user, docId, { includeContent: true }); + + if (!result) { + throw Boom.notFound(); + } + + const { + _source: { jobtype: jobType, output: jobOutput }, + } = result; + + if (!jobTypes.includes(jobType)) { + throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); + } + + return res.ok({ + body: jobOutput, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); + + // return some info about the job + router.get( + { + path: `${MAIN_ENTRY}/info/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 2 }), + }), + }, + }, + userHandler(async (user, context, req, res) => { + const { docId } = req.params as { docId: string }; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); + + const result = await jobsQuery.get(user, docId); + + if (!result) { + throw Boom.notFound(); + } + + const { _source: job } = result; + const { jobtype: jobType, payload: jobPayload } = job; + + if (!jobTypes.includes(jobType)) { + throw Boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); + } + + return res.ok({ + body: { + ...job, + payload: { + ...jobPayload, + headers: undefined, + }, + }, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); + + // trigger a download of the output from a job + const exportTypesRegistry = reporting.getExportTypesRegistry(); + const downloadResponseHandler = downloadJobResponseHandlerFactory( + config, + elasticsearch, + exportTypesRegistry + ); + + router.get( + { + path: `${MAIN_ENTRY}/download/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 3 }), + }), + }, + }, + userHandler(async (user, context, req, res) => { + const { docId } = req.params as { docId: string }; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); + + return downloadResponseHandler(res, jobTypes, user, { docId }); + }) + ); + + // allow a report to be deleted + const deleteResponseHandler = deleteJobResponseHandlerFactory(config, elasticsearch); + router.delete( + { + path: `${MAIN_ENTRY}/delete/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 3 }), + }), + }, + }, + userHandler(async (user, context, req, res) => { + const { docId } = req.params as { docId: string }; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); + + return deleteResponseHandler(res, jobTypes, user, { docId }); + }) + ); +} diff --git a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts new file mode 100644 index 0000000000000..b218f9e4607dd --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaRequest, RequestHandlerContext, KibanaResponseFactory } from 'kibana/server'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { ReportingCore } from '../../'; +import { createMockReportingCore } from '../../test_helpers'; +import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; +import { ReportingInternalSetup } from '../../core'; + +let mockCore: ReportingCore; +const kbnConfig = { + 'server.basePath': '/sbp', +}; +const reportingConfig = { + 'roles.allow': ['reporting_user'], +}; +const mockReportingConfig = { + get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')] || 'whoah!', + kbnConfig: { get: (...keys: string[]) => (kbnConfig as any)[keys.join('.')] }, +}; + +const getMockContext = () => + (({ + core: coreMock.createRequestHandlerContext(), + } as unknown) as RequestHandlerContext); + +const getMockRequest = () => + ({ + url: { port: '5601', query: '', path: '/foo' }, + route: { path: '/foo', options: {} }, + } as KibanaRequest); + +const getMockResponseFactory = () => + (({ + ...httpServerMock.createResponseFactory(), + forbidden: (obj: unknown) => obj, + unauthorized: (obj: unknown) => obj, + } as unknown) as KibanaResponseFactory); + +describe('authorized_user_pre_routing', function () { + beforeEach(async () => { + mockCore = await createMockReportingCore(mockReportingConfig); + }); + + it('should return from handler with null user when security is disabled', async function () { + mockCore.getPluginSetupDeps = () => + (({ + // @ts-ignore + ...mockCore.pluginSetupDeps, + security: undefined, // disable security + } as unknown) as ReportingInternalSetup); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); + const mockResponseFactory = httpServerMock.createResponseFactory() as KibanaResponseFactory; + + let handlerCalled = false; + authorizedUserPreRouting((user: unknown) => { + expect(user).toBe(null); // verify the user is a null value + handlerCalled = true; + return Promise.resolve({ status: 200, options: {} }); + })(getMockContext(), getMockRequest(), mockResponseFactory); + + expect(handlerCalled).toBe(true); + }); + + it('should return with 401 when security is enabled but no authenticated user', async function () { + mockCore.getPluginSetupDeps = () => + (({ + // @ts-ignore + ...mockCore.pluginSetupDeps, + security: { + authc: { getCurrentUser: () => null }, + }, + } as unknown) as ReportingInternalSetup); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); + const mockHandler = () => { + throw new Error('Handler callback should not be called'); + }; + const requestHandler = authorizedUserPreRouting(mockHandler); + const mockResponseFactory = getMockResponseFactory(); + + expect(requestHandler(getMockContext(), getMockRequest(), mockResponseFactory)).toMatchObject({ + body: `Sorry, you aren't authenticated`, + }); + }); + + it(`should return with 403 when security is enabled but user doesn't have allowed role`, async function () { + mockCore.getPluginSetupDeps = () => + (({ + // @ts-ignore + ...mockCore.pluginSetupDeps, + security: { + authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['cowboy'] }) }, + }, + } as unknown) as ReportingInternalSetup); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); + const mockResponseFactory = getMockResponseFactory(); + + const mockHandler = () => { + throw new Error('Handler callback should not be called'); + }; + expect( + authorizedUserPreRouting(mockHandler)(getMockContext(), getMockRequest(), mockResponseFactory) + ).toMatchObject({ body: `Sorry, you don't have access to Reporting` }); + }); + + it('should return from handler when security is enabled and user has explicitly allowed role', async function (done) { + mockCore.getPluginSetupDeps = () => + (({ + // @ts-ignore + ...mockCore.pluginSetupDeps, + security: { + authc: { + getCurrentUser: () => ({ username: 'friendlyuser', roles: ['reporting_user'] }), + }, + }, + } as unknown) as ReportingInternalSetup); + // @ts-ignore overloading config getter + mockCore.config = mockReportingConfig; + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); + const mockResponseFactory = getMockResponseFactory(); + + authorizedUserPreRouting((user) => { + expect(user).toMatchObject({ roles: ['reporting_user'], username: 'friendlyuser' }); + done(); + return Promise.resolve({ status: 200, options: {} }); + })(getMockContext(), getMockRequest(), mockResponseFactory); + }); + + it('should return from handler when security is enabled and user has superuser role', async function () {}); +}); diff --git a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts new file mode 100644 index 0000000000000..2ad974c9dd8e1 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandler, RouteMethod } from 'src/core/server'; +import { AuthenticatedUser } from '../../../../security/server'; +import { getUserFactory } from '../../lib/get_user'; +import { ReportingCore } from '../../core'; + +type ReportingUser = AuthenticatedUser | null; +const superuserRole = 'superuser'; + +export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer R + ? (user: ReportingUser, ...a: U) => R + : never; + +export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( + reporting: ReportingCore +) { + const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); + const getUser = getUserFactory(setupDeps.security); + return (handler: RequestHandlerUser): RequestHandler => { + return (context, req, res) => { + let user: ReportingUser = null; + if (setupDeps.security) { + // find the authenticated user, or null if security is not enabled + user = getUser(req); + if (!user) { + // security is enabled but the user is null + return res.unauthorized({ body: `Sorry, you aren't authenticated` }); + } + } + + if (user) { + // check allowance with the configured set of roleas + "superuser" + const allowedRoles = config.get('roles', 'allow') || []; + const authorizedRoles = [superuserRole, ...allowedRoles]; + + if (!user.roles.find((role) => authorizedRoles.includes(role))) { + // user's roles do not allow + return res.forbidden({ body: `Sorry, you don't have access to Reporting` }); + } + } + + return handler(user, context, req, res); + }; + }; +}; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts similarity index 92% rename from x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts rename to x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index 6a228c1915615..e16f5278c8cc7 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -12,15 +12,10 @@ import { statuses } from '../../lib/esqueue/constants/statuses'; import { ExportTypesRegistry } from '../../lib/export_types_registry'; import { ExportTypeDefinition, JobDocOutput, JobSource } from '../../types'; -interface ICustomHeaders { - [x: string]: any; -} - type ExportTypeType = ExportTypeDefinition; interface ErrorFromPayload { message: string; - reason: string | null; } // A camelCase version of JobDocOutput @@ -37,7 +32,7 @@ const getTitle = (exportType: ExportTypeType, title?: string): string => `${title || DEFAULT_TITLE}.${exportType.jobContentExtension}`; const getReportingHeaders = (output: JobDocOutput, exportType: ExportTypeType) => { - const metaDataHeaders: ICustomHeaders = {}; + const metaDataHeaders: Record = {}; if (exportType.jobType === CSV_JOB_TYPE) { const csvContainsFormulas = _.get(output, 'csv_contains_formulas', false); @@ -76,12 +71,13 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist }; } + // @TODO: These should be semantic HTTP codes as 500/503's indicate + // error then these are really operating properly. function getFailure(output: JobDocOutput): Payload { return { statusCode: 500, content: { - message: 'Reporting generation failed', - reason: output.content, + message: `Reporting generation failed: ${output.content}`, }, contentType: 'application/json', headers: {}, @@ -92,7 +88,7 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist return { statusCode: 503, content: status, - contentType: 'application/json', + contentType: 'text/plain', headers: { 'retry-after': 30 }, }; } diff --git a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts new file mode 100644 index 0000000000000..1a2e10cf355a2 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ElasticsearchServiceSetup, kibanaResponseFactory } from 'kibana/server'; +import { AuthenticatedUser } from '../../../../security/server'; +import { ReportingConfig } from '../../'; +import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; +import { ExportTypesRegistry } from '../../lib/export_types_registry'; +import { jobsQueryFactory } from '../../lib/jobs_query'; +import { getDocumentPayloadFactory } from './get_document_payload'; + +interface JobResponseHandlerParams { + docId: string; +} + +interface JobResponseHandlerOpts { + excludeContent?: boolean; +} + +export function downloadJobResponseHandlerFactory( + config: ReportingConfig, + elasticsearch: ElasticsearchServiceSetup, + exportTypesRegistry: ExportTypesRegistry +) { + const jobsQuery = jobsQueryFactory(config, elasticsearch); + const getDocumentPayload = getDocumentPayloadFactory(exportTypesRegistry); + + return async function jobResponseHandler( + res: typeof kibanaResponseFactory, + validJobTypes: string[], + user: AuthenticatedUser | null, + params: JobResponseHandlerParams, + opts: JobResponseHandlerOpts = {} + ) { + const { docId } = params; + + const doc = await jobsQuery.get(user, docId, { includeContent: !opts.excludeContent }); + if (!doc) { + return res.notFound(); + } + + const { jobtype: jobType } = doc._source; + + if (!validJobTypes.includes(jobType)) { + return res.unauthorized({ + body: `Sorry, you are not authorized to download ${jobType} reports`, + }); + } + + const response = getDocumentPayload(doc); + + if (!WHITELISTED_JOB_CONTENT_TYPES.includes(response.contentType)) { + return res.badRequest({ + body: `Unsupported content-type of ${response.contentType} specified by job output`, + }); + } + + return res.custom({ + body: typeof response.content === 'string' ? Buffer.from(response.content) : response.content, + statusCode: response.statusCode, + headers: { + ...response.headers, + 'content-type': response.contentType, + }, + }); + }; +} + +export function deleteJobResponseHandlerFactory( + config: ReportingConfig, + elasticsearch: ElasticsearchServiceSetup +) { + const jobsQuery = jobsQueryFactory(config, elasticsearch); + + return async function deleteJobResponseHander( + res: typeof kibanaResponseFactory, + validJobTypes: string[], + user: AuthenticatedUser | null, + params: JobResponseHandlerParams + ) { + const { docId } = params; + const doc = await jobsQuery.get(user, docId, { includeContent: false }); + + if (!doc) { + return res.notFound(); + } + + const { jobtype: jobType } = doc._source; + + if (!validJobTypes.includes(jobType)) { + return res.unauthorized({ + body: `Sorry, you are not authorized to delete ${jobType} reports`, + }); + } + + try { + const docIndex = doc._index; + await jobsQuery.delete(docIndex, docId); + return res.ok({ + body: { deleted: true }, + }); + } catch (error) { + return res.customError({ + statusCode: error.statusCode, + body: error.message, + }); + } + }; +} diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts new file mode 100644 index 0000000000000..5eceed0a7f2ab --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; +import { AuthenticatedUser } from '../../../security/server'; +import { JobDocPayload } from '../types'; + +export type HandlerFunction = ( + user: AuthenticatedUser | null, + exportType: string, + jobParams: object, + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory +) => any; + +export type HandlerErrorFunction = (res: KibanaResponseFactory, err: Error) => any; + +export interface QueuedJobPayload { + error?: boolean; + source: { + job: { + payload: JobDocPayload; + }; + }; +} diff --git a/x-pack/plugins/reporting/server/services.ts b/x-pack/plugins/reporting/server/services.ts new file mode 100644 index 0000000000000..9f4897a69f4ed --- /dev/null +++ b/x-pack/plugins/reporting/server/services.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createGetterSetter } from '../../../../src/plugins/kibana_utils/server'; +import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; + +export const [getFieldFormats, setFieldFormats] = createGetterSetter< + DataPluginStart['fieldFormats'] +>('FieldFormats'); diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts similarity index 96% rename from x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts rename to x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts index 260c94c31df1c..5b0d740e031ab 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts @@ -6,11 +6,11 @@ import { Page } from 'puppeteer'; import * as Rx from 'rxjs'; +import { HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../browsers'; +import { createDriverFactory } from '../browsers/chromium'; import * as contexts from '../export_types/common/lib/screenshots/constants'; -import { HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../server/browsers'; -import { createDriverFactory } from '../server/browsers/chromium'; -import { LevelLogger } from '../server/lib'; -import { CaptureConfig, ElementsPositionAndAttribute } from '../server/types'; +import { LevelLogger } from '../lib'; +import { CaptureConfig, ElementsPositionAndAttribute } from '../types'; interface CreateMockBrowserDriverFactoryOpts { evaluate: jest.Mock, any[]>; diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_layoutinstance.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_layoutinstance.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/test_helpers/create_mock_layoutinstance.ts rename to x-pack/plugins/reporting/server/test_helpers/create_mock_layoutinstance.ts index 7f4330e7f6bc6..22da9eb418e9a 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_layoutinstance.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_layoutinstance.ts @@ -5,7 +5,7 @@ */ import { createLayout, LayoutInstance, LayoutTypes } from '../export_types/common/layouts'; -import { CaptureConfig } from '../server/types'; +import { CaptureConfig } from '../types'; export const createMockLayoutInstance = (captureConfig: CaptureConfig) => { const mockLayout = createLayout(captureConfig, { diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts new file mode 100644 index 0000000000000..b04e697d0a118 --- /dev/null +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../routes'); +jest.mock('../usage'); +jest.mock('../browsers'); +jest.mock('../browsers'); +jest.mock('../lib/create_queue'); +jest.mock('../lib/enqueue_job'); +jest.mock('../lib/validate'); + +import { of } from 'rxjs'; +import { coreMock } from 'src/core/server/mocks'; +import { ReportingConfig, ReportingCore } from '../'; +import { ReportingInternalSetup } from '../core'; +import { ReportingPlugin } from '../plugin'; +import { ReportingSetupDeps, ReportingStartDeps } from '../types'; + +const createMockSetupDeps = (setupMock?: any): ReportingSetupDeps => { + return { + security: setupMock.security, + licensing: { + license$: of({ isAvailable: true, isActive: true, type: 'basic' }), + } as any, + usageCollection: {} as any, + }; +}; + +export const createMockStartDeps = (startMock?: any): ReportingStartDeps => ({ + data: startMock.data, +}); + +const createMockReportingPlugin = async (config: ReportingConfig): Promise => { + const mockConfig = { + index: '.reporting', + kibanaServer: { + hostname: 'localhost', + port: '80', + }, + capture: { + browser: { + chromium: { + disableSandbox: true, + }, + }, + }, + ...config, + }; + const plugin = new ReportingPlugin(coreMock.createPluginInitializerContext(mockConfig)); + const setupMock = coreMock.createSetup(); + const coreStartMock = coreMock.createStart(); + const startMock = { + ...coreStartMock, + data: { fieldFormats: {} }, + }; + + await plugin.setup(setupMock, createMockSetupDeps(setupMock)); + await plugin.start(startMock, createMockStartDeps(startMock)); + + return plugin; +}; + +export const createMockReportingCore = async ( + config: ReportingConfig, + setupDepsMock?: ReportingInternalSetup +): Promise => { + config = config || {}; + const plugin = await createMockReportingPlugin(config); + const core = plugin.getReportingCore(); + + if (setupDepsMock) { + // @ts-ignore overwriting private properties + core.pluginSetupDeps = setupDepsMock; + } + + return core; +}; diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_server.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_server.ts new file mode 100644 index 0000000000000..01b9f6cbd9cd6 --- /dev/null +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_server.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createHttpServer, createCoreContext } from 'src/core/server/http/test_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { coreMock } from 'src/core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ContextService } from 'src/core/server/context/context_service'; + +const coreId = Symbol('reporting'); + +export const createMockServer = async () => { + const coreContext = createCoreContext({ coreId }); + const contextService = new ContextService(coreContext); + + const server = createHttpServer(coreContext); + const httpSetup = await server.setup({ + context: contextService.setup({ pluginDependencies: new Map() }), + }); + const handlerContext = coreMock.createRequestHandlerContext(); + + httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { + return handlerContext; + }); + + return { + server, + httpSetup, + handlerContext, + }; +}; diff --git a/x-pack/legacy/plugins/reporting/test_helpers/index.ts b/x-pack/plugins/reporting/server/test_helpers/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/test_helpers/index.ts rename to x-pack/plugins/reporting/server/test_helpers/index.ts diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts new file mode 100644 index 0000000000000..409a89899bee0 --- /dev/null +++ b/x-pack/plugins/reporting/server/types.ts @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as Rx from 'rxjs'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { DataPluginStart } from 'src/plugins/data/server/plugin'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { CancellationToken } from '../../../plugins/reporting/common'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { SecurityPluginSetup } from '../../security/server'; +import { JobStatus } from '../common/types'; +import { ReportingConfigType } from './config'; +import { ReportingCore } from './core'; +import { LayoutInstance } from './export_types/common/layouts'; +import { LevelLogger } from './lib'; + +/* + * Routing / API types + */ + +interface ListQuery { + page: string; + size: string; + ids?: string; // optional field forbids us from extending RequestQuery +} + +interface GenerateQuery { + jobParams: string; +} + +export type ReportingRequestQuery = ListQuery | GenerateQuery; + +export interface ReportingRequestPre { + management: { + jobTypes: any; + }; + user: string; +} + +// generate a report with unparsed jobParams +export interface GenerateExportTypePayload { + jobParams: string; +} + +export type ReportingRequestPayload = GenerateExportTypePayload | JobParamPostPayload; + +export interface TimeRangeParams { + timezone: string; + min: Date | string | number | null; + max: Date | string | number | null; +} + +export interface JobParamPostPayload { + timerange: TimeRangeParams; +} + +export interface JobDocPayload { + headers?: string; // serialized encrypted headers + jobParams: JobParamsType; + title: string; + type: string | null; +} + +export interface JobSource { + _id: string; + _index: string; + _source: { + jobtype: string; + output: JobDocOutput; + payload: JobDocPayload; + status: JobStatus; + }; +} + +export interface JobDocOutput { + content_type: string; + content: string | null; + size: number; + max_size_reached?: boolean; + warnings?: string[]; +} + +interface ConditionalHeadersConditions { + protocol: string; + hostname: string; + port: number; + basePath: string; +} + +export interface ConditionalHeaders { + headers: Record; + conditions: ConditionalHeadersConditions; +} + +/* + * Screenshots + */ + +export interface ScreenshotObservableOpts { + logger: LevelLogger; + urls: string[]; + conditionalHeaders: ConditionalHeaders; + layout: LayoutInstance; + browserTimezone: string; +} + +export interface AttributesMap { + [key: string]: any; +} + +export interface ElementPosition { + boundingClientRect: { + // modern browsers support x/y, but older ones don't + top: number; + left: number; + width: number; + height: number; + }; + scroll: { + x: number; + y: number; + }; +} + +export interface ElementsPositionAndAttribute { + position: ElementPosition; + attributes: AttributesMap; +} + +export interface Screenshot { + base64EncodedData: string; + title: string; + description: string; +} + +export interface ScreenshotResults { + timeRange: string | null; + screenshots: Screenshot[]; + error?: Error; + elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing +} + +export type ScreenshotsObservableFn = ({ + logger, + urls, + conditionalHeaders, + layout, + browserTimezone, +}: ScreenshotObservableOpts) => Rx.Observable; + +/* + * Plugin Contract + */ + +export interface ReportingSetupDeps { + licensing: LicensingPluginSetup; + security?: SecurityPluginSetup; + usageCollection?: UsageCollectionSetup; +} + +export interface ReportingStartDeps { + data: DataPluginStart; +} + +export type ReportingStart = object; +export type ReportingSetup = object; + +/* + * Internal Types + */ + +export type ESQueueCreateJobFn = ( + jobParams: JobParamsType, + context: RequestHandlerContext, + request: KibanaRequest +) => Promise; + +export type ESQueueWorkerExecuteFn = ( + jobId: string, + job: JobDocPayloadType, + cancellationToken?: CancellationToken +) => Promise; + +export type CaptureConfig = ReportingConfigType['capture']; +export type ScrollConfig = ReportingConfigType['csv']['scroll']; + +export type CreateJobFactory = ( + reporting: ReportingCore, + logger: LevelLogger +) => CreateJobFnType; + +export type ExecuteJobFactory = ( + reporting: ReportingCore, + logger: LevelLogger +) => Promise; // FIXME: does not "need" to be async + +export interface ExportTypeDefinition< + JobParamsType, + CreateJobFnType, + JobPayloadType, + ExecuteJobFnType +> { + id: string; + name: string; + jobType: string; + jobContentEncoding?: string; + jobContentExtension: string; + createJobFactory: CreateJobFactory; + executeJobFactory: ExecuteJobFactory; + validLicenses: string[]; +} diff --git a/x-pack/legacy/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap b/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap rename to x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap diff --git a/x-pack/legacy/plugins/reporting/server/usage/decorate_range_stats.ts b/x-pack/plugins/reporting/server/usage/decorate_range_stats.ts similarity index 98% rename from x-pack/legacy/plugins/reporting/server/usage/decorate_range_stats.ts rename to x-pack/plugins/reporting/server/usage/decorate_range_stats.ts index ef985d2dd1cf3..30befcf291a54 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/decorate_range_stats.ts +++ b/x-pack/plugins/reporting/server/usage/decorate_range_stats.ts @@ -21,7 +21,7 @@ function getForFeature( type AdditionalType = { [K in keyof typeof additional]: K }; const filledAdditional: AdditionalType = {}; if (additional) { - Object.keys(additional).forEach(k => { + Object.keys(additional).forEach((k) => { filledAdditional[k] = { ...additional[k], ...jobType[k] }; }); } diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts b/x-pack/plugins/reporting/server/usage/get_export_type_handler.ts similarity index 83% rename from x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts rename to x-pack/plugins/reporting/server/usage/get_export_type_handler.ts index 7874f67ef4c0c..020e94b2d54b6 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts +++ b/x-pack/plugins/reporting/server/usage/get_export_type_handler.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; import { ExportTypesRegistry } from '../lib/export_types_registry'; +import { FeaturesAvailability } from './'; /* * Gets a handle to the Reporting export types registry and returns a few @@ -21,10 +21,10 @@ export function getExportTypesHandler(exportTypesRegistry: ExportTypesRegistry) * @param {Object} xpackInfo: xpack_main plugin info object * @return {Object} availability of each export type */ - getAvailability(xpackInfo: XPackMainPlugin['info']) { + getAvailability(featuresAvailability: FeaturesAvailability): { [exportType: string]: boolean } { const exportTypesAvailability: { [exportType: string]: boolean } = {}; - const xpackInfoAvailable = xpackInfo && xpackInfo.isAvailable(); - const licenseType: string | undefined = xpackInfo.license.getType(); + const xpackInfoAvailable = featuresAvailability && featuresAvailability.isAvailable(); + const licenseType = featuresAvailability.license.getType(); if (!licenseType) { throw new Error('No license type returned from XPackMainPlugin#info!'); } diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts rename to x-pack/plugins/reporting/server/usage/get_reporting_usage.ts index 6771d61bf263d..2a6d08c0740dd 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts @@ -7,8 +7,10 @@ import { get } from 'lodash'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; import { ReportingConfig } from '../'; -import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; import { ExportTypesRegistry } from '../lib/export_types_registry'; +import { GetLicense } from './'; +import { decorateRangeStats } from './decorate_range_stats'; +import { getExportTypesHandler } from './get_export_type_handler'; import { AggregationResultBuckets, AppCounts, @@ -21,10 +23,6 @@ import { SearchResponse, StatusByAppBucket, } from './types'; -import { decorateRangeStats } from './decorate_range_stats'; -import { getExportTypesHandler } from './get_export_type_handler'; - -type XPackInfo = XPackMainPlugin['info']; const JOB_TYPES_KEY = 'jobTypes'; const JOB_TYPES_FIELD = 'jobtype'; @@ -123,10 +121,10 @@ async function handleResponse(response: SearchResponse): Promise { const reportingIndex = config.get('index'); const params = { @@ -170,6 +168,7 @@ export async function getReportingUsage( }, }; + const featureAvailability = await getLicense(); return callCluster('search', params) .then((response: SearchResponse) => handleResponse(response)) .then( @@ -180,7 +179,7 @@ export async function getReportingUsage( const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); const availability = exportTypesHandler.getAvailability( - xpackMainInfo + featureAvailability ) as FeatureAvailabilityMap; const { last7Days, ...all } = usage; diff --git a/x-pack/plugins/reporting/server/usage/index.ts b/x-pack/plugins/reporting/server/usage/index.ts new file mode 100644 index 0000000000000..a426451db2282 --- /dev/null +++ b/x-pack/plugins/reporting/server/usage/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { LicenseType } from '../../../licensing/server'; + +export interface FeaturesAvailability { + isAvailable: () => boolean; + license: { + getType: () => LicenseType | undefined; + }; +} +export type GetLicense = () => Promise; +export { registerReportingUsageCollector } from './reporting_usage_collector'; diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts rename to x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts index 2d0934657b2b1..d5dccaca3042a 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts +++ b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as Rx from 'rxjs'; import sinon from 'sinon'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { ReportingConfig } from '../'; -import { createMockReportingCore } from '../../test_helpers'; +import { createMockReportingCore } from '../test_helpers'; import { getExportTypesRegistry } from '../lib/export_types_registry'; -import { ReportingUsageType, SearchResponse } from './types'; +import { ReportingSetupDeps } from '../types'; +import { FeaturesAvailability } from './'; import { getReportingUsageCollector, registerReportingUsageCollector, } from './reporting_usage_collector'; +import { ReportingUsageType, SearchResponse } from './types'; const exportTypesRegistry = getExportTypesRegistry(); @@ -32,30 +36,22 @@ function getMockUsageCollection() { }; } +const getLicenseMock = (licenseType = 'platinum') => () => { + return Promise.resolve({ + isAvailable: () => true, + license: { getType: () => licenseType }, + } as FeaturesAvailability); +}; + function getPluginsMock( { license, usageCollection = getMockUsageCollection() } = { license: 'platinum' } ) { - const mockXpackMain = { - info: { - isAvailable: sinon.stub().returns(true), - feature: () => ({ - getLicenseCheckResults: sinon.stub(), - }), - license: { - isOneOf: sinon.stub().returns(false), - getType: sinon.stub().returns(license), - }, - toJSON: () => ({ b: 1 }), - }, - }; - return { + return ({ + licensing: { license$: Rx.of(getLicenseMock(license)) }, usageCollection, - __LEGACY: { - plugins: { - xpack_main: mockXpackMain, - }, - }, - } as any; + elasticsearch: {}, + security: {}, + } as unknown) as ReportingSetupDeps & { usageCollection: UsageCollectionSetup }; } const getMockReportingConfig = () => ({ @@ -78,7 +74,7 @@ describe('license checks', () => { const { fetch } = getReportingUsageCollector( mockConfig, plugins.usageCollection, - plugins.__LEGACY.plugins.xpack_main.info, + getLicenseMock('basic'), exportTypesRegistry, function isReady() { return Promise.resolve(true); @@ -108,7 +104,7 @@ describe('license checks', () => { const { fetch } = getReportingUsageCollector( mockConfig, plugins.usageCollection, - plugins.__LEGACY.plugins.xpack_main.info, + getLicenseMock('none'), exportTypesRegistry, function isReady() { return Promise.resolve(true); @@ -138,7 +134,7 @@ describe('license checks', () => { const { fetch } = getReportingUsageCollector( mockConfig, plugins.usageCollection, - plugins.__LEGACY.plugins.xpack_main.info, + getLicenseMock('platinum'), exportTypesRegistry, function isReady() { return Promise.resolve(true); @@ -168,7 +164,7 @@ describe('license checks', () => { const { fetch } = getReportingUsageCollector( mockConfig, plugins.usageCollection, - plugins.__LEGACY.plugins.xpack_main.info, + getLicenseMock('basic'), exportTypesRegistry, function isReady() { return Promise.resolve(true); @@ -194,7 +190,7 @@ describe('data modeling', () => { const { fetch } = getReportingUsageCollector( mockConfig, plugins.usageCollection, - plugins.__LEGACY.plugins.xpack_main.info, + getLicenseMock(), exportTypesRegistry, function isReady() { return Promise.resolve(true); @@ -247,7 +243,7 @@ describe('data modeling', () => { const { fetch } = getReportingUsageCollector( mockConfig, plugins.usageCollection, - plugins.__LEGACY.plugins.xpack_main.info, + getLicenseMock(), exportTypesRegistry, function isReady() { return Promise.resolve(true); @@ -300,7 +296,7 @@ describe('data modeling', () => { const { fetch } = getReportingUsageCollector( mockConfig, plugins.usageCollection, - plugins.__LEGACY.plugins.xpack_main.info, + getLicenseMock(), exportTypesRegistry, function isReady() { return Promise.resolve(true); @@ -461,7 +457,7 @@ describe('Ready for collection observable', () => { const makeCollectorSpy = sinon.spy(); usageCollection.makeUsageCollector = makeCollectorSpy; - const plugins = getPluginsMock({ usageCollection } as any); + const plugins = getPluginsMock({ usageCollection, license: 'platinum' }); registerReportingUsageCollector(mockReporting, plugins); const [args] = makeCollectorSpy.firstCall.args; diff --git a/x-pack/plugins/reporting/server/usage/reporting_usage_collector.ts b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.ts new file mode 100644 index 0000000000000..d77d1b5396844 --- /dev/null +++ b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { first, map } from 'rxjs/operators'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { ReportingCore } from '../'; +import { KIBANA_REPORTING_TYPE } from '../../common/constants'; +import { ReportingConfig } from '../../server'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; +import { ReportingSetupDeps } from '../types'; +import { GetLicense } from './'; +import { getReportingUsage } from './get_reporting_usage'; +import { RangeStats } from './types'; + +// places the reporting data as kibana stats +const METATYPE = 'kibana_stats'; + +/* + * @return {Object} kibana usage stats type collection object + */ +export function getReportingUsageCollector( + config: ReportingConfig, + usageCollection: UsageCollectionSetup, + getLicense: GetLicense, + exportTypesRegistry: ExportTypesRegistry, + isReady: () => Promise +) { + return usageCollection.makeUsageCollector({ + type: KIBANA_REPORTING_TYPE, + fetch: (callCluster: CallCluster) => + getReportingUsage(config, getLicense, callCluster, exportTypesRegistry), + isReady, + + /* + * Format the response data into a model for internal upload + * 1. Make this data part of the "kibana_stats" type + * 2. Organize the payload in the usage.xpack.reporting namespace of the data payload + */ + formatForBulkUpload: (result: RangeStats) => { + return { + type: METATYPE, + payload: { + usage: { + xpack: { + reporting: result, + }, + }, + }, + }; + }, + }); +} + +export function registerReportingUsageCollector( + reporting: ReportingCore, + { licensing, usageCollection }: ReportingSetupDeps +) { + if (!usageCollection) { + return; + } + + const config = reporting.getConfig(); + const exportTypesRegistry = reporting.getExportTypesRegistry(); + const getLicense = async () => { + return await licensing.license$ + .pipe( + map(({ isAvailable, type }) => ({ + isAvailable: () => isAvailable, + license: { + getType: () => type, + }, + })), + first() + ) + .toPromise(); + }; + const collectionIsReady = reporting.pluginHasStarted.bind(reporting); + + const collector = getReportingUsageCollector( + config, + usageCollection, + getLicense, + exportTypesRegistry, + collectionIsReady + ); + usageCollection.registerCollector(collector); +} diff --git a/x-pack/legacy/plugins/reporting/server/usage/types.ts b/x-pack/plugins/reporting/server/usage/types.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/usage/types.ts rename to x-pack/plugins/reporting/server/usage/types.ts diff --git a/x-pack/plugins/rollup/README.md b/x-pack/plugins/rollup/README.md index b43f4d5981409..9c4dfee2d8bbe 100644 --- a/x-pack/plugins/rollup/README.md +++ b/x-pack/plugins/rollup/README.md @@ -1,4 +1,7 @@ +# Rollup + ## Summary + Welcome to the Kibana rollup plugin! This plugin provides Kibana support for [Elasticsearch's rollup feature](https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-rollup.html). Please refer to the Elasticsearch documentation to understand rollup indices and how to create rollup jobs. This plugin allows Kibana to: @@ -10,11 +13,25 @@ This plugin allows Kibana to: The rest of this doc dives into the implementation details of each of the above functionality. +## Quick steps for testing + +The pattern for creating a rollup job and rollup index pattern is: + +1. Install sample data (web logs is a good one). +2. Create a rollup job with an index pattern that captures this index (e.g. `k*`). +3. Set frequency to "minute". Clear the latency buffer field. +4. Select the time field which is the same time field selected in the installed index pattern (`timestamp` without an `@` in the case of web logs). +5. Specify a time bucket size (`10m` will do). +6. Select a few terms, histogram, and metrics fields. +7. Create and start the rollup job. Wait a minute for the job to run. You should see the numbers for documents and pages processed change in the detail panel. +8. Create a rollup index pattern in the Index Patterns app. +9. Now you can create visualizations using this index pattern. + --- ## Create and manage rollup jobs -The most straight forward part of this plugin! A new app called Rollup Jobs is registered in the Management section and follows a typical CRUD UI pattern. This app allows users to create, start, stop, clone, and delete rollup jobs. There is no way to edit an existing rollup job; instead, the UI offers a cloning ability. The client-side portion of this app lives in [public/crud_app](public/crud_app) and uses endpoints registered in [(server/routes/api/jobs](server/routes/api/jobs). +The most straight forward part of this plugin! A new app called Rollup Jobs is registered in the Management section and follows a typical CRUD UI pattern. This app allows users to create, start, stop, clone, and delete rollup jobs. There is no way to edit an existing rollup job; instead, the UI offers a cloning ability. The client-side portion of this app lives in [public/crud_app](public/crud_app) and uses endpoints registered in [server/routes/api/jobs](server/routes/api/jobs). Refer to the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-getting-started.html) to understand rollup indices and how to create rollup jobs. diff --git a/x-pack/plugins/rollup/fixtures/job.js b/x-pack/plugins/rollup/fixtures/job.js index 310244a5031e7..edcc99f7cae1b 100644 --- a/x-pack/plugins/rollup/fixtures/job.js +++ b/x-pack/plugins/rollup/fixtures/job.js @@ -48,4 +48,4 @@ const statuses = [ export const getJob = (values = { id: getRandomString() }) => ({ ...initialValues, ...values }); export const jobCount = statuses.length; -export const getJobs = () => statuses.map(status => getJob({ status, id: getRandomString() })); +export const getJobs = () => statuses.map((status) => getJob({ status, id: getRandomString() })); diff --git a/x-pack/plugins/rollup/public/application.tsx b/x-pack/plugins/rollup/public/application.tsx index 1bdf940d746b2..16a0312341118 100644 --- a/x-pack/plugins/rollup/public/application.tsx +++ b/x-pack/plugins/rollup/public/application.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { ChromeBreadcrumb, CoreSetup } from 'kibana/public'; +import { CoreSetup } from 'kibana/public'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; @@ -16,28 +16,28 @@ import { App } from './crud_app/app'; import './index.scss'; +import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; + /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. */ export const renderApp = async ( core: CoreSetup, - { - element, - setBreadcrumbs, - }: { element: HTMLElement; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void } + { history, element, setBreadcrumbs }: ManagementAppMountParams ) => { const [coreStart] = await core.getStartServices(); const I18nContext = coreStart.i18n.Context; + const services = { + history, + setBreadcrumbs, + }; + render( - + - + , diff --git a/x-pack/plugins/rollup/public/crud_app/app.js b/x-pack/plugins/rollup/public/crud_app/app.js index 0ef3253eeb94e..4eff849776aef 100644 --- a/x-pack/plugins/rollup/public/crud_app/app.js +++ b/x-pack/plugins/rollup/public/crud_app/app.js @@ -6,10 +6,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { HashRouter, Switch, Route, Redirect, withRouter } from 'react-router-dom'; +import { Router, Switch, Route, Redirect, withRouter } from 'react-router-dom'; import { UIM_APP_LOAD } from '../../common'; -import { CRUD_APP_BASE_PATH } from './constants'; import { registerRouter, setUserHasLeftApp, METRIC_TYPE } from './services'; import { trackUiMetric } from '../kibana_services'; import { JobList, JobCreate } from './sections'; @@ -53,15 +52,15 @@ export class App extends Component { render() { return ( - + - - - + + + - + ); } } diff --git a/x-pack/plugins/rollup/public/crud_app/constants/index.js b/x-pack/plugins/rollup/public/crud_app/constants/index.js index f3a218fc3b493..132affafea87d 100644 --- a/x-pack/plugins/rollup/public/crud_app/constants/index.js +++ b/x-pack/plugins/rollup/public/crud_app/constants/index.js @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { CRUD_APP_BASE_PATH } from './paths'; - export { METRICS_CONFIG } from './metrics_config'; diff --git a/x-pack/plugins/rollup/public/crud_app/constants/paths.js b/x-pack/plugins/rollup/public/crud_app/constants/paths.js deleted file mode 100644 index 44829f38e79cd..0000000000000 --- a/x-pack/plugins/rollup/public/crud_app/constants/paths.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const CRUD_APP_BASE_PATH = '/management/data/rollup_jobs'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/field_list/field_list.js b/x-pack/plugins/rollup/public/crud_app/sections/components/field_list/field_list.js index 993f17d4006d4..b12f8fb7b2be4 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/components/field_list/field_list.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/components/field_list/field_list.js @@ -37,7 +37,7 @@ export const FieldList = ({ icon: 'trash', type: 'icon', color: 'danger', - onClick: field => onRemoveField(field), + onClick: (field) => onRemoveField(field), }, ], }); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.container.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.container.js index 6b716f535cc63..cb86e26c0cb3e 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.container.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.container.js @@ -12,14 +12,14 @@ import { startJobs, stopJobs, deleteJobs, cloneJob } from '../../../store/action import { JobActionMenu as JobActionMenuComponent } from './job_action_menu'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { isUpdating: isUpdating(state), }; }; const mapDispatchToProps = (dispatch, { jobs }) => { - const jobIds = jobs.map(job => job.id); + const jobIds = jobs.map((job) => job.id); return { startJobs: () => { dispatch(startJobs(jobIds)); @@ -30,7 +30,7 @@ const mapDispatchToProps = (dispatch, { jobs }) => { deleteJobs: () => { dispatch(deleteJobs(jobIds)); }, - cloneJob: jobConfig => { + cloneJob: (jobConfig) => { dispatch(cloneJob(jobConfig)); }, }; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.js index a2575c0a6fc62..d5b5679e8eb81 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/job_action_menu.js @@ -130,7 +130,7 @@ class JobActionMenuUi extends Component { } onButtonClick = () => { - this.setState(prevState => ({ + this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen, })); }; @@ -151,12 +151,12 @@ class JobActionMenuUi extends Component { canStartJobs() { const { jobs } = this.props; - return jobs.some(job => job.status === 'stopped'); + return jobs.some((job) => job.status === 'stopped'); } canStopJobs() { const { jobs } = this.props; - return jobs.some(job => job.status === 'started'); + return jobs.some((job) => job.status === 'started'); } canCloneJob() { @@ -166,7 +166,7 @@ class JobActionMenuUi extends Component { canDeleteJobs() { const { jobs } = this.props; - const areAllJobsStopped = jobs.findIndex(job => job.status === 'started') === -1; + const areAllJobsStopped = jobs.findIndex((job) => job.status === 'started') === -1; return areAllJobsStopped; } diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.container.js index 8b8e72d976c37..7c3da7f05ee44 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.container.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.container.js @@ -11,7 +11,7 @@ import { isSaving, getCreateJobError, getCloneJobConfig } from '../../store/sele import { createJob, clearCreateJobErrors, clearCloneJob } from '../../store/actions'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { isSaving: isSaving(state), saveError: getCreateJobError(state), @@ -19,9 +19,9 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { - createJob: jobConfig => { + createJob: (jobConfig) => { dispatch(createJob(jobConfig)); }, clearCreateJobErrors: () => { diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js index 4458054f30dd1..151eff31f8a01 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js @@ -94,7 +94,7 @@ export class JobCreateUi extends Component { props.kibana.services.setBreadcrumbs([listBreadcrumb, createBreadcrumb]); const { jobToClone: stepDefaultOverrides } = props; - const stepsFields = mapValues(stepIdToStepConfigMap, step => + const stepsFields = mapValues(stepIdToStepConfigMap, (step) => cloneDeep(step.getDefaultFields(stepDefaultOverrides)) ); @@ -164,7 +164,7 @@ export class JobCreateUi extends Component { const lastIndexPatternValidationTime = (this.lastIndexPatternValidationTime = Date.now()); validateIndexPattern(indexPattern) - .then(response => { + .then((response) => { // We don't need to do anything if this component has been unmounted. if (!this._isMounted) { return; @@ -287,7 +287,7 @@ export class JobCreateUi extends Component { isValidatingIndexPattern: false, }); }) - .catch(error => { + .catch((error) => { // We don't need to do anything if this component has been unmounted. if (!this._isMounted) { return; @@ -392,7 +392,7 @@ export class JobCreateUi extends Component { // Check every step before this one and see if it's been completed. const prerequisiteSteps = stepIds.slice(0, indexOfStep); - return prerequisiteSteps.every(prerequisiteStepId => !this.hasStepErrors(prerequisiteStepId)); + return prerequisiteSteps.every((prerequisiteStepId) => !this.hasStepErrors(prerequisiteStepId)); } hasStepErrors(stepId) { @@ -405,7 +405,7 @@ export class JobCreateUi extends Component { } const stepFieldErrors = stepsFieldErrors[stepId]; - return Object.values(stepFieldErrors).some(error => error != null); + return Object.values(stepFieldErrors).some((error) => error != null); } getStepsFieldsErrors(newStepsFields) { @@ -515,7 +515,7 @@ export class JobCreateUi extends Component { } else { errorBody = (
      - {cause.map(causeValue => ( + {cause.map((causeValue) => (
    • {causeValue}
    • ))}
    @@ -650,7 +650,7 @@ export class JobCreateUi extends Component { } } - onToggleStartAfterCreate = eve => { + onToggleStartAfterCreate = (eve) => { this.setState({ startJobAfterCreation: eve.target.checked }); }; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js index 2e55af6416fdf..dfbde846cb18a 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js @@ -43,14 +43,14 @@ export class FieldChooser extends Component { }; } - onSearch = e => { + onSearch = (e) => { this.setState({ searchValue: e.target.value, }); }; onButtonClick = () => { - this.setState(state => ({ + this.setState((state) => ({ isOpen: !state.isOpen, })); }; @@ -74,7 +74,7 @@ export class FieldChooser extends Component { const { isOpen, searchValue } = this.state; - const getRowProps = item => { + const getRowProps = (item) => { return { onClick: () => { onSelectField(item); @@ -90,7 +90,7 @@ export class FieldChooser extends Component { }); const searchedItems = searchValue - ? unselectedFields.filter(item => { + ? unselectedFields.filter((item) => { const normalizedSearchValue = searchValue.trim().toLowerCase(); return ( item.name.toLowerCase().includes(normalizedSearchValue) || diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_date_histogram.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_date_histogram.js index c327e51a6b7ee..a4106564576ae 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_date_histogram.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_date_histogram.js @@ -31,7 +31,7 @@ import { getDateHistogramDetailsUrl, getDateHistogramAggregationUrl } from '../. import { StepError } from './components'; -const timeZoneOptions = moment.tz.names().map(name => ({ +const timeZoneOptions = moment.tz.names().map((name) => ({ value: name, text: name, })); @@ -49,7 +49,7 @@ export class StepDateHistogram extends Component { static getDerivedStateFromProps(props) { const { dateFields } = props; - const dateHistogramFieldOptions = dateFields.map(dateField => ({ + const dateHistogramFieldOptions = dateFields.map((dateField) => ({ value: dateField, text: dateField, })); @@ -255,7 +255,7 @@ export class StepDateHistogram extends Component { isInvalid={Boolean(areStepErrorsVisible && errorDateHistogramField)} options={dateHistogramFieldOptions} value={dateHistogramField} - onChange={e => onFieldsChange({ dateHistogramField: e.target.value })} + onChange={(e) => onFieldsChange({ dateHistogramField: e.target.value })} fullWidth data-test-subj="rollupJobCreateDateFieldSelect" /> @@ -275,7 +275,7 @@ export class StepDateHistogram extends Component { > onFieldsChange({ dateHistogramInterval: e.target.value })} + onChange={(e) => onFieldsChange({ dateHistogramInterval: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorDateHistogramInterval)} fullWidth data-test-subj="rollupJobInterval" @@ -296,7 +296,7 @@ export class StepDateHistogram extends Component { onFieldsChange({ dateHistogramTimeZone: e.target.value })} + onChange={(e) => onFieldsChange({ dateHistogramTimeZone: e.target.value })} fullWidth data-test-subj="rollupJobCreateTimeZoneSelect" /> diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js index 9307c9074e663..c0e8830c41276 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js @@ -36,7 +36,7 @@ export class StepHistogram extends Component { histogramFields: PropTypes.array.isRequired, }; - onSelectField = field => { + onSelectField = (field) => { const { fields: { histogram }, onFieldsChange, @@ -45,13 +45,13 @@ export class StepHistogram extends Component { onFieldsChange({ histogram: histogram.concat(field) }); }; - onRemoveField = field => { + onRemoveField = (field) => { const { fields: { histogram }, onFieldsChange, } = this.props; - onFieldsChange({ histogram: histogram.filter(histogramField => histogramField !== field) }); + onFieldsChange({ histogram: histogram.filter((histogramField) => histogramField !== field) }); }; render() { @@ -191,7 +191,7 @@ export class StepHistogram extends Component { > onFieldsChange({ histogramInterval: e.target.value })} + onChange={(e) => onFieldsChange({ histogramInterval: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorHistogramInterval)} fullWidth data-test-subj="rollupJobCreateHistogramInterval" diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js index 0fd76e572b2e5..5e9c2f62ceef8 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js @@ -157,7 +157,7 @@ export class StepLogistics extends Component { > onFieldsChange({ rollupCron: e.target.value })} + onChange={(e) => onFieldsChange({ rollupCron: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorRollupCron)} fullWidth data-test-subj="rollupAdvancedCron" @@ -306,7 +306,7 @@ export class StepLogistics extends Component { onFieldsChange({ id: e.target.value })} + onChange={(e) => onFieldsChange({ id: e.target.value })} fullWidth data-test-subj="rollupJobName" /> @@ -351,7 +351,7 @@ export class StepLogistics extends Component { > onFieldsChange({ indexPattern: e.target.value })} + onChange={(e) => onFieldsChange({ indexPattern: e.target.value })} isInvalid={ Boolean(areStepErrorsVisible && errorIndexPattern) || Boolean(indexPatternAsyncErrors) @@ -382,7 +382,7 @@ export class StepLogistics extends Component { > onFieldsChange({ rollupIndex: e.target.value })} + onChange={(e) => onFieldsChange({ rollupIndex: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorRollupIndex)} fullWidth data-test-subj="rollupIndexName" @@ -444,7 +444,7 @@ export class StepLogistics extends Component { > onFieldsChange({ rollupPageSize: e.target.value })} + onChange={(e) => onFieldsChange({ rollupPageSize: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorRollupPageSize)} fullWidth min={0} @@ -497,7 +497,7 @@ export class StepLogistics extends Component { > onFieldsChange({ rollupDelay: e.target.value })} + onChange={(e) => onFieldsChange({ rollupDelay: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorRollupDelay)} fullWidth data-test-subj="rollupDelay" diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js index b71b6bfc805bf..d70fbb89c065d 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js @@ -51,8 +51,8 @@ const checkWhiteListedMetricByFieldType = (fieldType, metricType) => { // associated field types. After processing each of these // objects should have a fieldTypes: { date: true, numeric: true } // like object. -const metricTypesConfig = (function() { - return METRICS_CONFIG.map(config => { +const metricTypesConfig = (function () { + return METRICS_CONFIG.map((config) => { const fieldTypes = {}; for (const [fieldType, metrics] of Object.entries(whiteListedMetricByFieldType)) { fieldTypes[fieldType] = !!metrics[config.type]; @@ -132,7 +132,9 @@ export class StepMetrics extends Component { if (isAllMetricTypes) { applicableMetrics.forEach(({ types, type }) => { const whiteListedSubset = Object.keys(whiteListedMetricByFieldType[type]); - if (whiteListedSubset.every(metricName => types.some(type => type === metricName))) { + if ( + whiteListedSubset.every((metricName) => types.some((type) => type === metricName)) + ) { ++checkedCount; } }); @@ -140,7 +142,7 @@ export class StepMetrics extends Component { isDisabled = metrics.length === 0; } else { applicableMetrics.forEach(({ types }) => { - const metricSelected = types.some(type => type === metricType); + const metricSelected = types.some((type) => type === metricType); if (metricSelected) { ++checkedCount; } @@ -228,7 +230,7 @@ export class StepMetrics extends Component { const onChange = () => { const isSelected = hasSelectedItems ? types.length !== maxItemsToBeSelected : true; const newMetrics = metricTypesConfig - .filter(config => config.fieldTypes[fieldType]) + .filter((config) => config.fieldTypes[fieldType]) .reduce((acc, { type: typeConfig }) => { return this.setMetric(fieldName, typeConfig, isSelected); }, null); @@ -278,7 +280,7 @@ export class StepMetrics extends Component {
    ); }) - .filter(checkbox => checkbox !== undefined); + .filter((checkbox) => checkbox !== undefined); return ( @@ -292,7 +294,7 @@ export class StepMetrics extends Component { }); } - onSelectField = field => { + onSelectField = (field) => { const { fields: { metrics }, onFieldsChange, @@ -306,7 +308,7 @@ export class StepMetrics extends Component { onFieldsChange({ metrics: newMetrics }); }; - onRemoveField = field => { + onRemoveField = (field) => { const { fields: { metrics }, onFieldsChange, @@ -323,7 +325,7 @@ export class StepMetrics extends Component { } = this.props; return fields - .filter(field => checkWhiteListedMetricByFieldType(field.type, metricType)) + .filter((field) => checkWhiteListedMetricByFieldType(field.type, metricType)) .reduce((acc, metric) => { return this.setMetric(metric.name, metricType, isSelected); }, []); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js index 0097792db3105..c2b39e52220dd 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js @@ -43,7 +43,7 @@ export class StepReview extends Component { }; } - selectTab = tab => { + selectTab = (tab) => { this.setState({ selectedTab: tab, }); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js index 48e045e19f478..cc3f05170eb63 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js @@ -30,7 +30,7 @@ export class StepTerms extends Component { termsFields: PropTypes.array.isRequired, }; - onSelectField = field => { + onSelectField = (field) => { const { fields: { terms }, onFieldsChange, @@ -39,13 +39,13 @@ export class StepTerms extends Component { onFieldsChange({ terms: terms.concat(field) }); }; - onRemoveField = field => { + onRemoveField = (field) => { const { fields: { terms }, onFieldsChange, } = this.props; - onFieldsChange({ terms: terms.filter(term => term !== field) }); + onFieldsChange({ terms: terms.filter((term) => term !== field) }); }; render() { diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js index eca624e16cb86..3a61518a850e9 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js @@ -80,7 +80,7 @@ export const stepIdToStepConfigMap = { clonedRollupId, }; }, - fieldsValidator: fields => { + fieldsValidator: (fields) => { const { id, indexPattern, @@ -113,7 +113,7 @@ export const stepIdToStepConfigMap = { ...pick(overrides, Object.keys(defaults)), }; }, - fieldsValidator: fields => { + fieldsValidator: (fields) => { const { dateHistogramField, dateHistogramInterval } = fields; return { @@ -131,14 +131,14 @@ export const stepIdToStepConfigMap = { }, }, [STEP_HISTOGRAM]: { - getDefaultFields: overrides => { + getDefaultFields: (overrides) => { return { histogram: [], histogramInterval: undefined, ...pick(overrides, ['histogram', 'histogramInterval']), }; }, - fieldsValidator: fields => { + fieldsValidator: (fields) => { const { histogram, histogramInterval } = fields; return { @@ -153,7 +153,7 @@ export const stepIdToStepConfigMap = { ...pick(overrides, ['metrics']), }; }, - fieldsValidator: fields => { + fieldsValidator: (fields) => { const { metrics } = fields; return { @@ -184,5 +184,5 @@ export function getAffectedStepsFields(fields, stepsFields) { export function hasErrors(fieldErrors) { const errorValues = Object.values(fieldErrors); - return errorValues.some(error => error !== undefined); + return errorValues.some((error) => error !== undefined); } diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js index e4de4196537ca..381d4add071f7 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js @@ -17,7 +17,7 @@ import { import { closeDetailPanel, openDetailPanel } from '../../../store/actions'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { isOpen: isDetailPanelOpen(state), isLoading: isLoading(state), @@ -27,7 +27,7 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { closeDetailPanel: () => { dispatch(closeDetailPanel()); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js index cea5b3c3e96e5..f9f3cda6f2c20 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js @@ -86,15 +86,15 @@ describe('', () => { const tabActive = JOB_DETAILS_TAB_SUMMARY; const { component } = initTestBed({ panelType: tabActive }); const tabs = component.find('EuiTab'); - const getTab = id => { - const found = tabs.findWhere(tab => { + const getTab = (id) => { + const found = tabs.findWhere((tab) => { return tab.text() === tabToHumanizedMap[id].props.defaultMessage; }); return found.first(); }; it('should have 5 tabs visible', () => { - const tabsLabel = tabs.map(tab => tab.text()); + const tabsLabel = tabs.map((tab) => tab.text()); expect(tabsLabel).toEqual(['Summary', 'Terms', 'Histogram', 'Metrics', 'JSON']); }); @@ -153,7 +153,7 @@ describe('', () => { }); it('should set the correct job value for each of the subsection', () => { - LOGISTICS_SUBSECTIONS.forEach(subSection => { + LOGISTICS_SUBSECTIONS.forEach((subSection) => { const wrapper = find(`rollupJobDetailLogistics${subSection}Description`); expect(wrapper.length).toBe(1); const description = wrapper.text(); @@ -198,7 +198,7 @@ describe('', () => { }); it('should set the correct job value for each of the subsection', () => { - DATE_HISTOGRAMS_SUBSECTIONS.forEach(subSection => { + DATE_HISTOGRAMS_SUBSECTIONS.forEach((subSection) => { const wrapper = find(`rollupJobDetailDateHistogram${subSection}Description`); expect(wrapper.length).toBe(1); const description = wrapper.text(); @@ -242,7 +242,7 @@ describe('', () => { }); it('should set the correct job value for each of the subsection', () => { - STATS_SUBSECTIONS.forEach(subSection => { + STATS_SUBSECTIONS.forEach((subSection) => { const wrapper = find(`rollupJobDetailStats${subSection}Description`); expect(wrapper.length).toBe(1); const description = wrapper.text(); @@ -285,7 +285,7 @@ describe('', () => { const { tableCellsValues } = table.getMetaData('detailPanelTermsTabTable'); it('should list the Job terms fields', () => { - const expected = defaultJob.terms.map(term => [term.name]); + const expected = defaultJob.terms.map((term) => [term.name]); expect(tableCellsValues).toEqual(expected); }); }); @@ -297,7 +297,7 @@ describe('', () => { const { tableCellsValues } = table.getMetaData('detailPanelHistogramTabTable'); it('should list the Job histogram fields', () => { - const expected = defaultJob.histogram.map(h => [h.name]); + const expected = defaultJob.histogram.map((h) => [h.name]); expect(tableCellsValues).toEqual(expected); }); }); @@ -309,7 +309,7 @@ describe('', () => { const { tableCellsValues } = table.getMetaData('detailPanelMetricsTabTable'); it('should list the Job metrics fields and their types', () => { - const expected = defaultJob.metrics.map(metric => [metric.name, metric.types.join(', ')]); + const expected = defaultJob.metrics.map((metric) => [metric.name, metric.types.join(', ')]); expect(tableCellsValues).toEqual(expected); }); }); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js index df0f933c905f7..e361964e577ba 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js @@ -18,7 +18,7 @@ import { import { JobList as JobListView } from './job_list'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { hasJobs: Boolean(getJobsList(state).length), isLoading: isLoading(state), @@ -26,7 +26,7 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { loadJobs: () => { dispatch(loadJobs()); @@ -34,13 +34,13 @@ const mapDispatchToProps = dispatch => { refreshJobs: () => { dispatch(refreshJobs()); }, - openDetailPanel: jobId => { + openDetailPanel: (jobId) => { dispatch(openDetailPanel({ jobId: jobId })); }, closeDetailPanel: () => { dispatch(closeDetailPanel()); }, - cloneJob: jobConfig => { + cloneJob: (jobConfig) => { dispatch(cloneJob(jobConfig)); }, }; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js index 011becded148c..85cd6e742d27f 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js @@ -27,7 +27,6 @@ import { import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { getRouterLinkProps, extractQueryParams, listBreadcrumb } from '../../services'; import { JobTable } from './job_table'; @@ -166,7 +165,7 @@ export class JobListUi extends Component { actions={ @@ -210,7 +209,7 @@ export class JobListUi extends Component { {this.getHeaderSection()} - + { const services = require.requireActual('../../services'); return { ...services, - getRouterLinkProps: link => ({ href: link }), + getRouterLinkProps: (link) => ({ href: link }), }; }); @@ -33,7 +33,7 @@ const defaultProps = { const services = { setBreadcrumbs: startMock.chrome.setBreadcrumbs, }; -const Component = props => ( +const Component = (props) => ( @@ -72,11 +72,7 @@ describe('', () => { it('should display a callout with the status and the message', () => { expect(exists('jobListError')).toBeTruthy(); - expect( - find('jobListError') - .find('EuiText') - .text() - ).toEqual('400 Houston we got a problem.'); + expect(find('jobListError').find('EuiText').text()).toEqual('400 Houston we got a problem.'); }); }); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js index e352ca48fd861..6208b4963b6ef 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js @@ -25,7 +25,7 @@ import { import { JobTable as JobTableComponent } from './job_table'; -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { jobs: getPageOfJobs(state), pager: getPager(state), @@ -35,24 +35,24 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch) => { return { closeDetailPanel: () => { dispatch(closeDetailPanel()); }, - filterChanged: filter => { + filterChanged: (filter) => { dispatch(filterChanged({ filter })); }, - pageChanged: pageNumber => { + pageChanged: (pageNumber) => { dispatch(pageChanged({ pageNumber })); }, - pageSizeChanged: pageSize => { + pageSizeChanged: (pageSize) => { dispatch(pageSizeChanged({ pageSize })); }, sortChanged: (sortField, isSortAscending) => { dispatch(sortChanged({ sortField, isSortAscending })); }, - openDetailPanel: jobId => { + openDetailPanel: (jobId) => { dispatch(openDetailPanel({ jobId: jobId })); }, }; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js index f47992e1b501a..6337e6812ca4b 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js @@ -94,10 +94,10 @@ const COLUMNS = [ fieldName: 'groups', isSortable: false, truncateText: true, - render: job => + render: (job) => ['histogram', 'terms'].reduce((text, field) => { if (job[field].length) { - return text ? `${text}, ${field}` : field.replace(/^\w/, char => char.toUpperCase()); + return text ? `${text}, ${field}` : field.replace(/^\w/, (char) => char.toUpperCase()); } return text; }, ''), @@ -109,11 +109,11 @@ const COLUMNS = [ fieldName: 'metrics', isSortable: false, truncateText: true, - render: job => { + render: (job) => { const { metrics } = job; if (metrics.length) { - return metrics.map(metric => metric.name).join(', '); + return metrics.map((metric) => metric.name).join(', '); } return ''; @@ -143,15 +143,15 @@ export class JobTable extends Component { static getDerivedStateFromProps(props, state) { // Deselct any jobs which no longer exist, e.g. they've been deleted. const { idToSelectedJobMap } = state; - const jobIds = props.jobs.map(job => job.id); + const jobIds = props.jobs.map((job) => job.id); const selectedJobIds = Object.keys(idToSelectedJobMap); - const missingJobIds = selectedJobIds.filter(selectedJobId => { + const missingJobIds = selectedJobIds.filter((selectedJobId) => { return !jobIds.includes(selectedJobId); }); if (missingJobIds.length) { const newMap = { ...idToSelectedJobMap }; - missingJobIds.forEach(missingJobId => delete newMap[missingJobId]); + missingJobIds.forEach((missingJobId) => delete newMap[missingJobId]); return { idToSelectedJobMap: newMap }; } @@ -183,7 +183,7 @@ export class JobTable extends Component { this.setState({ idToSelectedJobMap }); }; - toggleItem = id => { + toggleItem = (id) => { this.setState(({ idToSelectedJobMap }) => { const newMap = { ...idToSelectedJobMap }; @@ -201,33 +201,33 @@ export class JobTable extends Component { this.setState({ idToSelectedJobMap: {} }); }; - deselectItems = itemIds => { + deselectItems = (itemIds) => { this.setState(({ idToSelectedJobMap }) => { const newMap = { ...idToSelectedJobMap }; - itemIds.forEach(id => delete newMap[id]); + itemIds.forEach((id) => delete newMap[id]); return { idToSelectedJobMap: newMap }; }); }; areAllItemsSelected = () => { const { jobs } = this.props; - const indexOfUnselectedItem = jobs.findIndex(job => !this.isItemSelected(job.id)); + const indexOfUnselectedItem = jobs.findIndex((job) => !this.isItemSelected(job.id)); return indexOfUnselectedItem === -1; }; - isItemSelected = id => { + isItemSelected = (id) => { return !!this.state.idToSelectedJobMap[id]; }; getSelectedJobs() { const { jobs } = this.props; const { idToSelectedJobMap } = this.state; - return Object.keys(idToSelectedJobMap).map(jobId => { - return jobs.find(job => job.id === jobId); + return Object.keys(idToSelectedJobMap).map((jobId) => { + return jobs.find((job) => job.id === jobId); }); } - onSort = column => { + onSort = (column) => { const { sortField, isSortAscending, sortChanged } = this.props; const newIsSortAscending = sortField === column ? !isSortAscending : true; @@ -296,7 +296,7 @@ export class JobTable extends Component { buildRows() { const { jobs } = this.props; - return jobs.map(job => { + return jobs.map((job) => { const { id } = job; return ( @@ -357,7 +357,7 @@ export class JobTable extends Component { { + onChange={(event) => { filterChanged(event.target.value); }} data-test-subj="jobTableFilterInput" diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js index c6a3d3a8f69ad..481c419403754 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js @@ -70,11 +70,8 @@ describe('', () => { expect(tableColumns).toEqual(expectedColumns); }); - const getRowTextGetter = row => field => - row - .find(`[data-test-subj="jobTableCell-${field}"]`) - .hostNodes() - .text(); + const getRowTextGetter = (row) => (field) => + row.find(`[data-test-subj="jobTableCell-${field}"]`).hostNodes().text(); it('should set the correct job value in each row cell', () => { const unformattedFields = [ @@ -88,7 +85,7 @@ describe('', () => { const job = jobs[0]; const getCellText = getRowTextGetter(row); - unformattedFields.forEach(field => { + unformattedFields.forEach((field) => { const cellText = getCellText(field); expect(cellText).toEqual(job[field]); }); @@ -114,10 +111,7 @@ describe('', () => { it('should open the detail panel when clicking on the job id', () => { const row = tableRows.first(); const job = jobs[0]; - const linkJobId = row - .find(`[data-test-subj="jobTableCell-id"]`) - .hostNodes() - .find('EuiLink'); + const linkJobId = row.find(`[data-test-subj="jobTableCell-id"]`).hostNodes().find('EuiLink'); linkJobId.simulate('click'); @@ -174,8 +168,8 @@ describe('', () => { expect(contextMenu.length).toBeTruthy(); const contextMenuButtons = contextMenu.find('button'); - const buttonsLabel = contextMenuButtons.map(btn => btn.text()); - const hasExpectedLabels = ['Start job', 'Delete job'].every(expectedLabel => + const buttonsLabel = contextMenuButtons.map((btn) => btn.text()); + const hasExpectedLabels = ['Start job', 'Delete job'].every((expectedLabel) => buttonsLabel.includes(expectedLabel) ); @@ -191,7 +185,7 @@ describe('', () => { find('jobActionMenuButton').simulate('click'); const contextMenuButtons = find('jobActionContextMenu').find('button'); - const buttonsLabel = contextMenuButtons.map(btn => btn.text()); + const buttonsLabel = contextMenuButtons.map((btn) => btn.text()); const hasExpectedLabels = buttonsLabel.includes('Stop job'); expect(hasExpectedLabels).toBe(true); }); @@ -207,8 +201,8 @@ describe('', () => { find('jobActionMenuButton').simulate('click'); const contextMenuButtons = find('jobActionContextMenu').find('button'); - const buttonsLabel = contextMenuButtons.map(btn => btn.text()); - const hasExpectedLabels = ['Start jobs', 'Stop jobs'].every(expectedLabel => + const buttonsLabel = contextMenuButtons.map((btn) => btn.text()); + const hasExpectedLabels = ['Start jobs', 'Stop jobs'].every((expectedLabel) => buttonsLabel.includes(expectedLabel) ); diff --git a/x-pack/plugins/rollup/public/crud_app/services/breadcrumbs.js b/x-pack/plugins/rollup/public/crud_app/services/breadcrumbs.js index e830dca0ef151..24bffb9fedebc 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/breadcrumbs.js +++ b/x-pack/plugins/rollup/public/crud_app/services/breadcrumbs.js @@ -5,13 +5,12 @@ */ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../constants'; export const listBreadcrumb = { text: i18n.translate('xpack.rollupJobs.listBreadcrumbTitle', { defaultMessage: 'Rollups Jobs', }), - href: `#${CRUD_APP_BASE_PATH}`, + href: `/`, }; export const createBreadcrumb = { diff --git a/x-pack/plugins/rollup/public/crud_app/services/filter_items.js b/x-pack/plugins/rollup/public/crud_app/services/filter_items.js index 7fb3fb8e5a61c..52bba757419ad 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/filter_items.js +++ b/x-pack/plugins/rollup/public/crud_app/services/filter_items.js @@ -6,9 +6,9 @@ export const filterItems = (fields, filter = '', items = []) => { const normalizedFilter = filter.toLowerCase(); - return items.filter(item => { + return items.filter((item) => { const actualFields = fields || Object.keys(item); - const indexOfMatch = actualFields.findIndex(field => { + const indexOfMatch = actualFields.findIndex((field) => { const normalizedField = String(item[field]).toLowerCase(); return normalizedField.includes(normalizedFilter); }); diff --git a/x-pack/plugins/rollup/public/crud_app/services/flatten_panel_tree.js b/x-pack/plugins/rollup/public/crud_app/services/flatten_panel_tree.js index e060e22965cb3..2bb3903a6ef45 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/flatten_panel_tree.js +++ b/x-pack/plugins/rollup/public/crud_app/services/flatten_panel_tree.js @@ -8,7 +8,7 @@ export const flattenPanelTree = (tree, array = []) => { array.push(tree); if (tree.items) { - tree.items.forEach(item => { + tree.items.forEach((item) => { if (item.panel) { flattenPanelTree(item.panel, array); item.panel = item.panel.id; diff --git a/x-pack/plugins/rollup/public/crud_app/services/format_fields.js b/x-pack/plugins/rollup/public/crud_app/services/format_fields.js index 96fc82517c58b..2d620630fcddb 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/format_fields.js +++ b/x-pack/plugins/rollup/public/crud_app/services/format_fields.js @@ -5,7 +5,7 @@ */ export function formatFields(fieldNames, type) { - return fieldNames.map(fieldName => ({ + return fieldNames.map((fieldName) => ({ name: fieldName, type, })); diff --git a/x-pack/plugins/rollup/public/crud_app/services/jobs.js b/x-pack/plugins/rollup/public/crud_app/services/jobs.js index c6e11934bb268..63f67e9be6610 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/jobs.js +++ b/x-pack/plugins/rollup/public/crud_app/services/jobs.js @@ -5,7 +5,7 @@ */ function removeEmptyValues(object) { - Object.keys(object).forEach(key => { + Object.keys(object).forEach((key) => { if (object[key] == null || object[key].trim() === '') { delete object[key]; } @@ -142,11 +142,11 @@ export function deserializeJob(job) { } if (terms) { - deserializedJob.terms = terms.fields.map(name => ({ name })); + deserializedJob.terms = terms.fields.map((name) => ({ name })); } if (histogram) { - deserializedJob.histogram = histogram.fields.map(name => ({ name })); + deserializedJob.histogram = histogram.fields.map((name) => ({ name })); deserializedJob.histogramInterval = histogram.interval; } diff --git a/x-pack/plugins/rollup/public/crud_app/services/noticeable_delay.js b/x-pack/plugins/rollup/public/crud_app/services/noticeable_delay.js index c9a3495f100cb..c84d21bd4ec5a 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/noticeable_delay.js +++ b/x-pack/plugins/rollup/public/crud_app/services/noticeable_delay.js @@ -7,7 +7,7 @@ // Ensure an API request resolves after a brief yet noticeable delay, giving users time to recognize // a spinner or other feedback without it flickering. export function createNoticeableDelay(promise) { - const noticeableDelay = new Promise(resolve => + const noticeableDelay = new Promise((resolve) => setTimeout(() => { resolve(); }, 300) diff --git a/x-pack/plugins/rollup/public/crud_app/services/query_params.js b/x-pack/plugins/rollup/public/crud_app/services/query_params.js index 23bf41d344c5f..bdb5f5bed5c63 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/query_params.js +++ b/x-pack/plugins/rollup/public/crud_app/services/query_params.js @@ -13,7 +13,7 @@ export function extractQueryParams(queryString) { const queryParamPairs = queryString .split('?')[1] .split('&') - .map(paramString => paramString.split('=')); + .map((paramString) => paramString.split('=')); queryParamPairs.forEach(([key, value]) => { extractedQueryParams[key] = decodeURIComponent(value); diff --git a/x-pack/plugins/rollup/public/crud_app/services/retype_metrics.js b/x-pack/plugins/rollup/public/crud_app/services/retype_metrics.js index cdbb8e891b6ee..5b1bf36b199fe 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/retype_metrics.js +++ b/x-pack/plugins/rollup/public/crud_app/services/retype_metrics.js @@ -14,9 +14,11 @@ * @returns { { : string, type: string, types: string[] }[] } */ export function retypeMetrics({ metrics, typeMaps }) { - return metrics.map(metric => { + return metrics.map((metric) => { const { name: metricName } = metric; - const { type } = typeMaps.find(type => type.fields.some(field => field.name === metricName)); + const { type } = typeMaps.find((type) => + type.fields.some((field) => field.name === metricName) + ); return { ...metric, type, diff --git a/x-pack/plugins/rollup/public/crud_app/services/routing.js b/x-pack/plugins/rollup/public/crud_app/services/routing.js index 3b78e73c6b3af..6e60f75fd8bb3 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/routing.js +++ b/x-pack/plugins/rollup/public/crud_app/services/routing.js @@ -20,10 +20,10 @@ export function getUserHasLeftApp() { return _userHasLeftApp; } -const isModifiedEvent = event => +const isModifiedEvent = (event) => !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); -const isLeftClickEvent = event => event.button === 0; +const isLeftClickEvent = (event) => event.button === 0; let router; export function registerRouter(reactRouter) { @@ -44,7 +44,7 @@ export function getRouterLinkProps(to) { const href = router.history.createHref(location); - const onClick = event => { + const onClick = (event) => { if (event.defaultPrevented) { return; } diff --git a/x-pack/plugins/rollup/public/crud_app/services/sort_table.js b/x-pack/plugins/rollup/public/crud_app/services/sort_table.js index 57ae267b2e1cc..dc3fa6fa057b8 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/sort_table.js +++ b/x-pack/plugins/rollup/public/crud_app/services/sort_table.js @@ -6,7 +6,7 @@ import { sortBy } from 'lodash'; -const stringSort = fieldName => item => item[fieldName]; +const stringSort = (fieldName) => (item) => item[fieldName]; const sorters = {}; diff --git a/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts b/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts index 5d9340a140500..29ca1dc265ee8 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts +++ b/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts @@ -15,7 +15,7 @@ export { METRIC_TYPE }; */ export function trackUserRequest(request: Promise, actionType: string) { // Only track successful actions. - return request.then(response => { + return request.then((response) => { // NOTE: METRIC_TYPE.LOADED is probably the wrong metric type here. The correct metric type // is more likely METRIC_TYPE.APPLICATION_USAGE. This change was introduced in // https://github.com/elastic/kibana/pull/41113/files#diff-58ac12bdd1a3a05a24e69ff20633c482R20 diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/change_job_status.js b/x-pack/plugins/rollup/public/crud_app/store/actions/change_job_status.js index 4252def4ce1df..9be60e56fc74e 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/change_job_status.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/change_job_status.js @@ -17,7 +17,7 @@ import { UPDATE_JOB_START, UPDATE_JOB_SUCCESS, UPDATE_JOB_FAILURE } from '../act import { refreshJobs } from './refresh_jobs'; -export const startJobs = jobIds => async dispatch => { +export const startJobs = (jobIds) => async (dispatch) => { dispatch({ type: UPDATE_JOB_START, }); @@ -44,7 +44,7 @@ export const startJobs = jobIds => async dispatch => { dispatch(refreshJobs()); }; -export const stopJobs = jobIds => async dispatch => { +export const stopJobs = (jobIds) => async (dispatch) => { dispatch({ type: UPDATE_JOB_START, }); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/clone_job.js b/x-pack/plugins/rollup/public/crud_app/store/actions/clone_job.js index 7647157e1446e..3f587f65d7de5 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/clone_job.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/clone_job.js @@ -6,14 +6,14 @@ import { CLONE_JOB_START, CLONE_JOB_CLEAR } from '../action_types'; -export const cloneJob = jobToClone => dispatch => { +export const cloneJob = (jobToClone) => (dispatch) => { dispatch({ type: CLONE_JOB_START, payload: jobToClone, }); }; -export const clearCloneJob = () => dispatch => { +export const clearCloneJob = () => (dispatch) => { dispatch({ type: CLONE_JOB_CLEAR, }); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/create_job.js b/x-pack/plugins/rollup/public/crud_app/store/actions/create_job.js index c85b4c55f665e..c404471f803f3 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/create_job.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/create_job.js @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { createJob as sendCreateJobRequest, serializeJob, @@ -25,7 +24,7 @@ import { import { getFatalErrors } from '../../../kibana_services'; -export const createJob = jobConfig => async dispatch => { +export const createJob = (jobConfig) => async (dispatch) => { dispatch({ type: CREATE_JOB_START, }); @@ -36,7 +35,7 @@ export const createJob = jobConfig => async dispatch => { [newJob] = await Promise.all([ sendCreateJobRequest(serializeJob(jobConfig)), // Wait at least half a second to avoid a weird flicker of the saving feedback. - new Promise(resolve => setTimeout(resolve, 500)), + new Promise((resolve) => setTimeout(resolve, 500)), ]); } catch (error) { if (error) { @@ -102,12 +101,12 @@ export const createJob = jobConfig => async dispatch => { // This will open the new job in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. getRouter().history.push({ - pathname: `${CRUD_APP_BASE_PATH}/job_list`, + pathname: `/job_list`, search: `?job=${jobConfig.id}`, }); }; -export const clearCreateJobErrors = () => dispatch => { +export const clearCreateJobErrors = () => (dispatch) => { dispatch({ type: CLEAR_CREATE_JOB_ERRORS, }); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/delete_jobs.js b/x-pack/plugins/rollup/public/crud_app/store/actions/delete_jobs.js index 0cfc8c24d46e9..ad38b506d8ec4 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/delete_jobs.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/delete_jobs.js @@ -20,7 +20,7 @@ import { closeDetailPanel } from './detail_panel'; import { getNotifications } from '../../../kibana_services'; -export const deleteJobs = jobIds => async (dispatch, getState) => { +export const deleteJobs = (jobIds) => async (dispatch, getState) => { dispatch({ type: UPDATE_JOB_START, }); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js index 897602fefcf62..d01bc6b49c94c 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js @@ -7,7 +7,7 @@ import { extractQueryParams, getRouter } from '../../services'; import { OPEN_DETAIL_PANEL, CLOSE_DETAIL_PANEL } from '../action_types'; -export const openDetailPanel = ({ panelType, jobId }) => dispatch => { +export const openDetailPanel = ({ panelType, jobId }) => (dispatch) => { const { history } = getRouter(); const search = history.location.search; const { job: deepLinkedJobId } = extractQueryParams(search); @@ -25,7 +25,7 @@ export const openDetailPanel = ({ panelType, jobId }) => dispatch => { }); }; -export const closeDetailPanel = () => dispatch => { +export const closeDetailPanel = () => (dispatch) => { dispatch({ type: CLOSE_DETAIL_PANEL, }); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js b/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js index e4df28709e75a..5df33fb25e725 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { loadJobs as sendLoadJobsRequest, deserializeJobs, showApiError } from '../../services'; import { LOAD_JOBS_START, LOAD_JOBS_SUCCESS, LOAD_JOBS_FAILURE } from '../action_types'; -export const loadJobs = () => async dispatch => { +export const loadJobs = () => async (dispatch) => { dispatch({ type: LOAD_JOBS_START, }); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js b/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js index 201c430bbde73..2505ddbac0cb7 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { loadJobs as sendLoadJobsRequest, deserializeJobs, showApiWarning } from '../../services'; import { REFRESH_JOBS_SUCCESS } from '../action_types'; -export const refreshJobs = () => async dispatch => { +export const refreshJobs = () => async (dispatch) => { let jobs; try { jobs = await sendLoadJobsRequest(); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js b/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js index 26c10a5416930..fbc2ca7f035ca 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js @@ -6,28 +6,28 @@ import { FILTER_CHANGED, PAGE_CHANGED, PAGE_SIZE_CHANGED, SORT_CHANGED } from '../action_types'; -export const filterChanged = ({ filter }) => dispatch => { +export const filterChanged = ({ filter }) => (dispatch) => { dispatch({ type: FILTER_CHANGED, payload: { filter }, }); }; -export const pageChanged = ({ pageNumber }) => dispatch => { +export const pageChanged = ({ pageNumber }) => (dispatch) => { dispatch({ type: PAGE_CHANGED, payload: { pageNumber }, }); }; -export const pageSizeChanged = ({ pageSize }) => dispatch => { +export const pageSizeChanged = ({ pageSize }) => (dispatch) => { dispatch({ type: PAGE_SIZE_CHANGED, payload: { pageSize }, }); }; -export const sortChanged = ({ sortField, isSortAscending }) => dispatch => { +export const sortChanged = ({ sortField, isSortAscending }) => (dispatch) => { dispatch({ type: SORT_CHANGED, payload: { sortField, isSortAscending }, diff --git a/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js b/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js index e424232cce0a1..b8495a1c95a8e 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js +++ b/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js @@ -6,15 +6,14 @@ import { getRouter, getUserHasLeftApp } from '../../services'; import { CLONE_JOB_START } from '../action_types'; -import { CRUD_APP_BASE_PATH } from '../../constants'; -export const cloneJob = () => next => action => { +export const cloneJob = () => (next) => (action) => { const { type } = action; if (type === CLONE_JOB_START) { if (!getUserHasLeftApp()) { getRouter().history.push({ - pathname: `${CRUD_APP_BASE_PATH}/create`, + pathname: `/create`, }); } } diff --git a/x-pack/plugins/rollup/public/crud_app/store/middleware/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/store/middleware/detail_panel.js index cbe86fb7065b6..3e9f5b7af7e68 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/middleware/detail_panel.js +++ b/x-pack/plugins/rollup/public/crud_app/store/middleware/detail_panel.js @@ -7,7 +7,7 @@ import { getRouter, getUserHasLeftApp } from '../../services'; import { CLOSE_DETAIL_PANEL } from '../action_types'; -export const detailPanel = () => next => action => { +export const detailPanel = () => (next) => (action) => { const { type } = action; switch (type) { diff --git a/x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.js b/x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.js index cb5011688b874..b7cd9e9baa493 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.js +++ b/x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.js @@ -20,14 +20,14 @@ const initialState = { function mapJobsToIds(jobs) { const jobsById = {}; - jobs.forEach(job => { + jobs.forEach((job) => { jobsById[job.id] = job; }); return jobsById; } function getJobsIds(jobs) { - return jobs.map(job => job.id); + return jobs.map((job) => job.id); } export function jobs(state = initialState, action) { diff --git a/x-pack/plugins/rollup/public/crud_app/store/selectors/index.js b/x-pack/plugins/rollup/public/crud_app/store/selectors/index.js index 6a68609539c07..2979a9286a646 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/selectors/index.js +++ b/x-pack/plugins/rollup/public/crud_app/store/selectors/index.js @@ -9,24 +9,24 @@ import { Pager } from '@elastic/eui'; import { createSelector } from 'reselect'; import { filterItems, sortTable } from '../../services'; -export const getJobs = state => state.jobs.byId; -export const getJobsList = state => state.jobs.allIds; +export const getJobs = (state) => state.jobs.byId; +export const getJobsList = (state) => state.jobs.allIds; export const getJobByJobId = (state, id) => getJobs(state)[id]; -export const getFilteredIds = state => state.jobs.filteredIds; -export const getTableState = state => state.tableState; +export const getFilteredIds = (state) => state.jobs.filteredIds; +export const getTableState = (state) => state.tableState; -export const getDetailPanelType = state => state.detailPanel.panelType; -export const isDetailPanelOpen = state => state.detailPanel.isOpen; -export const getDetailPanelJob = state => getJobByJobId(state, state.detailPanel.jobId); -export const getDetailPanelJobId = state => state.detailPanel.jobId; +export const getDetailPanelType = (state) => state.detailPanel.panelType; +export const isDetailPanelOpen = (state) => state.detailPanel.isOpen; +export const getDetailPanelJob = (state) => getJobByJobId(state, state.detailPanel.jobId); +export const getDetailPanelJobId = (state) => state.detailPanel.jobId; -export const isLoading = state => state.jobs.isLoading; -export const jobLoadError = state => state.jobs.jobLoadError; -export const isSaving = state => state.createJob.isSaving; -export const getCreateJobError = state => state.createJob.error; -export const isUpdating = state => state.updateJob.isUpdating; +export const isLoading = (state) => state.jobs.isLoading; +export const jobLoadError = (state) => state.jobs.jobLoadError; +export const isSaving = (state) => state.createJob.isSaving; +export const getCreateJobError = (state) => state.createJob.error; +export const isUpdating = (state) => state.updateJob.isUpdating; -export const getCloneJobConfig = state => state.cloneJob.job; +export const getCloneJobConfig = (state) => state.cloneJob.job; export const getJobStatusByJobName = (state, jobName) => { const jobs = getJobs(state); @@ -35,11 +35,11 @@ export const getJobStatusByJobName = (state, jobName) => { }; const getFilteredJobs = createSelector(getJobs, getTableState, (jobs, tableState) => { - const jobArray = Object.keys(jobs).map(jobName => jobs[jobName]); + const jobArray = Object.keys(jobs).map((jobName) => jobs[jobName]); return filterItems(['id', 'indexPattern', 'rollupIndex'], tableState.filter, jobArray); }); -export const getTotalItems = createSelector(getFilteredJobs, filteredJobs => { +export const getTotalItems = createSelector(getFilteredJobs, (filteredJobs) => { return Object.keys(filteredJobs).length; }); @@ -63,15 +63,15 @@ export const getPageOfJobs = createSelector( } ); -export const getHasNextPage = createSelector(getPager, pager => { +export const getHasNextPage = createSelector(getPager, (pager) => { return pager.hasNextPage; }); -export const getHasPreviousPage = createSelector(getPager, pager => { +export const getHasPreviousPage = createSelector(getPager, (pager) => { return pager.hasPreviousPage; }); -export const getCurrentPage = createSelector(getPager, pager => { +export const getCurrentPage = createSelector(getPager, (pager) => { return pager.currentPage; }); diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index a1c1fb4c7b388..4ff189d8f1be0 100644 --- a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -90,7 +90,7 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig : null; } - isRollupIndex = indexName => { + isRollupIndex = (indexName) => { return this.rollupIndices.includes(indexName); }; @@ -105,14 +105,14 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig : []; } - checkIndicesForErrors = indices => { + checkIndicesForErrors = (indices) => { this.rollupIndex = null; if (!indices || !indices.length) { return; } - const rollupIndices = indices.filter(index => this.isRollupIndex(index.name)); + const rollupIndices = indices.filter((index) => this.isRollupIndex(index.name)); if (!rollupIndices.length) { return [rollupIndexPatternNoMatchError]; diff --git a/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js b/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js index e61287d303643..ea9d00b51c1bb 100644 --- a/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js +++ b/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js @@ -14,7 +14,7 @@ function isRollup(indexPattern) { export class RollupIndexPatternListConfig extends IndexPatternListConfig { key = 'rollup'; - getIndexPatternTags = indexPattern => { + getIndexPatternTags = (indexPattern) => { return isRollup(indexPattern) ? [ { @@ -31,14 +31,14 @@ export class RollupIndexPatternListConfig extends IndexPatternListConfig { } const allAggs = indexPattern.typeMeta && indexPattern.typeMeta.aggs; - const fieldAggs = allAggs && Object.keys(allAggs).filter(agg => allAggs[agg][field]); + const fieldAggs = allAggs && Object.keys(allAggs).filter((agg) => allAggs[agg][field]); if (!fieldAggs || !fieldAggs.length) { return []; } return ['Rollup aggregations:'].concat( - fieldAggs.map(aggName => { + fieldAggs.map((aggName) => { const agg = allAggs[aggName][field]; switch (aggName) { case 'date_histogram': @@ -55,7 +55,7 @@ export class RollupIndexPatternListConfig extends IndexPatternListConfig { ); }; - areScriptedFieldsEnabled = indexPattern => { + areScriptedFieldsEnabled = (indexPattern) => { return !isRollup(indexPattern); }; } diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts index b2e793d7e75e9..b55760c5cc5aa 100644 --- a/x-pack/plugins/rollup/public/plugin.ts +++ b/x-pack/plugins/rollup/public/plugin.ts @@ -16,8 +16,6 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../src/plugins/home/public'; -// @ts-ignore -import { CRUD_APP_BASE_PATH } from './crud_app/constants'; import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public'; @@ -71,7 +69,7 @@ export class RollupPlugin implements Plugin { 'Summarize and store historical data in a smaller index for future analysis.', }), icon: 'indexRollupApp', - path: `#${CRUD_APP_BASE_PATH}/job_list`, + path: `/app/management/data/rollup_jobs/job_list`, showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js index fa9929a207615..d0abb223a3cfc 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js @@ -12,7 +12,7 @@ import { deserializeJob } from '../../../crud_app/services'; import { wrapComponent } from './setup_context'; -export const setup = props => { +export const setup = (props) => { const initTestBed = registerTestBed(wrapComponent(JobCreate), { store: createRollupJobsStore({ cloneJob: { job: deserializeJob(JOB_TO_CLONE.jobs[0]) }, diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js index 7ddcfa9eb83ff..b991fe4c45eae 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js @@ -14,7 +14,7 @@ import { wrapComponent } from './setup_context'; const initTestBed = registerTestBed(wrapComponent(JobCreate), { store: rollupJobsStore }); -export const setup = props => { +export const setup = (props) => { const testBed = initTestBed(props); const { component, form, table } = testBed; @@ -38,7 +38,7 @@ export const setup = props => { }; // Forms - const fillFormFields = async step => { + const fillFormFields = async (step) => { switch (step) { case 'logistics': form.setInputValue('rollupJobName', JOB_TO_CREATE.id); @@ -54,7 +54,7 @@ export const setup = props => { }; // Navigation - const goToStep = async targetStep => { + const goToStep = async (targetStep) => { const stepHandlers = { 1: () => fillFormFields('logistics'), 2: () => fillFormFields('date-histogram'), @@ -78,18 +78,18 @@ export const setup = props => { return rows; }; - const getFieldListTableRow = row => { + const getFieldListTableRow = (row) => { const rows = getFieldListTableRows(); return rows[row]; }; - const getFieldChooserColumnForRow = row => { + const getFieldChooserColumnForRow = (row) => { const selectedRow = getFieldListTableRow(row); const [, , fieldChooserColumn] = selectedRow.columns; return fieldChooserColumn; }; - const getSelectAllInputForRow = row => { + const getSelectAllInputForRow = (row) => { const fieldChooser = getFieldChooserColumnForRow(row); return fieldChooser.reactWrapper.find('input').first(); }; diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js index fda1ec8cfed22..f05b7046de344 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js @@ -14,7 +14,7 @@ import { wrapComponent } from './setup_context'; const testBedConfig = { store: createRollupJobsStore, memoryRouter: { - onRouter: router => { + onRouter: (router) => { // register our react memory router registerRouter(router); }, diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js index 7b61a03dcde45..aa95bbbd9cf0d 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js @@ -9,7 +9,7 @@ import { mockHttpRequest, pageHelpers, nextTick } from './helpers'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); const { setup } = pageHelpers.jobClone; const { @@ -80,7 +80,7 @@ describe('Cloning a rollup job through create job wizard', () => { expect(tableCellValuesTerms.length).toBe(terms.length); for (const [keyword] of tableCellValuesTerms) { - expect(terms.find(term => term === keyword)).toBe(keyword); + expect(terms.find((term) => term === keyword)).toBe(keyword); } await actions.clickNextStep(); @@ -99,7 +99,7 @@ describe('Cloning a rollup job through create job wizard', () => { expect(tableCellValuesHisto.length).toBe(histogramsTerms.length); for (const [keyword] of tableCellValuesHisto) { - expect(histogramsTerms.find(term => term === keyword)).toBe(keyword); + expect(histogramsTerms.find((term) => term === keyword)).toBe(keyword); } await actions.clickNextStep(); @@ -123,10 +123,10 @@ describe('Cloning a rollup job through create job wizard', () => { let checkedCountActual = 0; const checkedCountExpected = checkedMetrics.length; - checkboxColumn.find('input').forEach(el => { + checkboxColumn.find('input').forEach((el) => { const props = el.props(); const shouldBeChecked = checkedMetrics.some( - checkedMetric => props['data-test-subj'] === `rollupJobMetricsCheckbox-${checkedMetric}` + (checkedMetric) => props['data-test-subj'] === `rollupJobMetricsCheckbox-${checkedMetric}` ); if (shouldBeChecked) ++checkedCountActual; expect(props.checked).toBe(shouldBeChecked); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js index 095609ac2b2d7..8791b5173b893 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js @@ -10,7 +10,7 @@ import { setHttp } from '../../crud_app/services'; import { mockHttpRequest, pageHelpers } from './helpers'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); const { setup } = pageHelpers.jobCreate; @@ -78,7 +78,7 @@ describe('Create Rollup Job, step 2: Date histogram', () => { const dateFieldSelectOptionsValues = find('rollupJobCreateDateFieldSelect') .find('option') - .map(option => option.text()); + .map((option) => option.text()); expect(dateFieldSelectOptionsValues).toEqual(dateFields); }); @@ -90,7 +90,7 @@ describe('Create Rollup Job, step 2: Date histogram', () => { const dateFieldSelectOptionsValues = find('rollupJobCreateDateFieldSelect') .find('option') - .map(option => option.text()); + .map((option) => option.text()); expect(dateFieldSelectOptionsValues).toEqual(dateFields.sort()); }); }); @@ -100,7 +100,7 @@ describe('Create Rollup Job, step 2: Date histogram', () => { await goToStep(2); const timeZoneSelect = find('rollupJobCreateTimeZoneSelect'); - const options = timeZoneSelect.find('option').map(option => option.text()); + const options = timeZoneSelect.find('option').map((option) => option.text()); expect(options).toEqual(moment.tz.names()); }); }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js index 141aa06843556..50898f94586fc 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js @@ -8,7 +8,7 @@ import { setHttp } from '../../crud_app/services'; import { mockHttpRequest, pageHelpers } from './helpers'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); const { setup } = pageHelpers.jobCreate; diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js index 7b89244bb52a9..a1edf87c33bad 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js @@ -17,7 +17,7 @@ import { setHttp } from '../../crud_app/services'; import { mockHttpRequest, pageHelpers } from './helpers'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); const { setup } = pageHelpers.jobCreate; @@ -150,7 +150,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should not allow invalid characters', () => { - const expectInvalidChar = char => { + const expectInvalidChar = (char) => { form.setInputValue('rollupIndexName', `rollup_index_${char}`); actions.clickNextStep(); expect(form.getErrorsMessages()).toContain( @@ -171,17 +171,17 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); describe('rollup cron', () => { - const changeFrequency = value => { + const changeFrequency = (value) => { find('cronFrequencySelect').simulate('change', { target: { value } }); }; - const generateStringSequenceOfNumbers = total => + const generateStringSequenceOfNumbers = (total) => new Array(total).fill('').map((_, i) => (i < 10 ? `0${i}` : i.toString())); describe('frequency', () => { it('should allow "minute", "hour", "day", "week", "month", "year"', () => { const frequencySelect = find('cronFrequencySelect'); - const options = frequencySelect.find('option').map(option => option.text()); + const options = frequencySelect.find('option').map((option) => option.text()); expect(options).toEqual(['minute', 'hour', 'day', 'week', 'month', 'year']); }); @@ -204,7 +204,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { it('should allow to select any minute from 00 -> 59', () => { const minutSelect = find('cronFrequencyHourlyMinuteSelect'); - const options = minutSelect.find('option').map(option => option.text()); + const options = minutSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); }); @@ -222,13 +222,13 @@ describe('Create Rollup Job, step 1: Logistics', () => { it('should allow to select any hour from 00 -> 23', () => { const hourSelect = find('cronFrequencyDailyHourSelect'); - const options = hourSelect.find('option').map(option => option.text()); + const options = hourSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { const minutSelect = find('cronFrequencyDailyMinuteSelect'); - const options = minutSelect.find('option').map(option => option.text()); + const options = minutSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); }); @@ -247,7 +247,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { it('should allow to select any day of the week', () => { const hourSelect = find('cronFrequencyWeeklyDaySelect'); - const options = hourSelect.find('option').map(option => option.text()); + const options = hourSelect.find('option').map((option) => option.text()); expect(options).toEqual([ 'Sunday', 'Monday', @@ -261,13 +261,13 @@ describe('Create Rollup Job, step 1: Logistics', () => { it('should allow to select any hour from 00 -> 23', () => { const hourSelect = find('cronFrequencyWeeklyHourSelect'); - const options = hourSelect.find('option').map(option => option.text()); + const options = hourSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { const minutSelect = find('cronFrequencyWeeklyMinuteSelect'); - const options = minutSelect.find('option').map(option => option.text()); + const options = minutSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); }); @@ -286,19 +286,19 @@ describe('Create Rollup Job, step 1: Logistics', () => { it('should allow to select any date of the month from 1st to 31st', () => { const dateSelect = find('cronFrequencyMonthlyDateSelect'); - const options = dateSelect.find('option').map(option => option.text()); + const options = dateSelect.find('option').map((option) => option.text()); expect(options.length).toEqual(31); }); it('should allow to select any hour from 00 -> 23', () => { const hourSelect = find('cronFrequencyMonthlyHourSelect'); - const options = hourSelect.find('option').map(option => option.text()); + const options = hourSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { const minutSelect = find('cronFrequencyMonthlyMinuteSelect'); - const options = minutSelect.find('option').map(option => option.text()); + const options = minutSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); }); @@ -318,7 +318,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { it('should allow to select any month of the year', () => { const monthSelect = find('cronFrequencyYearlyMonthSelect'); - const options = monthSelect.find('option').map(option => option.text()); + const options = monthSelect.find('option').map((option) => option.text()); expect(options).toEqual([ 'January', 'February', @@ -337,19 +337,19 @@ describe('Create Rollup Job, step 1: Logistics', () => { it('should allow to select any date of the month from 1st to 31st', () => { const dateSelect = find('cronFrequencyYearlyDateSelect'); - const options = dateSelect.find('option').map(option => option.text()); + const options = dateSelect.find('option').map((option) => option.text()); expect(options.length).toEqual(31); }); it('should allow to select any hour from 00 -> 23', () => { const hourSelect = find('cronFrequencyYearlyHourSelect'); - const options = hourSelect.find('option').map(option => option.text()); + const options = hourSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { const minutSelect = find('cronFrequencyYearlyMinuteSelect'); - const options = minutSelect.find('option').map(option => option.text()); + const options = minutSelect.find('option').map((option) => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js index 1e9dd88648da6..7f58482d35b19 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js @@ -8,7 +8,7 @@ import { setHttp } from '../../crud_app/services'; import { mockHttpRequest, pageHelpers } from './helpers'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); const { setup } = pageHelpers.jobCreate; @@ -174,7 +174,7 @@ describe('Create Rollup Job, step 5: Metrics', () => { it('should have "avg", "max", "min", "sum" & "value count" metrics for *numeric* fields', () => { addFieldToList('numeric'); - numericTypeMetrics.forEach(type => { + numericTypeMetrics.forEach((type) => { try { expect(exists(`rollupJobMetricsCheckbox-${type}`)).toBe(true); } catch (e) { @@ -198,7 +198,7 @@ describe('Create Rollup Job, step 5: Metrics', () => { it('should have "max", "min", & "value count" metrics for *date* fields', () => { addFieldToList('date'); - dateTypeMetrics.forEach(type => { + dateTypeMetrics.forEach((type) => { try { expect(exists(`rollupJobMetricsCheckbox-${type}`)).toBe(true); } catch (e) { @@ -270,7 +270,7 @@ describe('Create Rollup Job, step 5: Metrics', () => { const expectAllFieldChooserInputs = (fieldChooserColumn, expected) => { const inputs = fieldChooserColumn.reactWrapper.find('input'); - inputs.forEach(input => { + inputs.forEach((input) => { expect(input.props().checked).toBe(expected); }); }; @@ -302,13 +302,13 @@ describe('Create Rollup Job, step 5: Metrics', () => { const rows = getFieldListTableRows(); rows - .filter(row => { + .filter((row) => { const [, metricTypeCol] = row.columns; return metricTypeCol.value === 'numeric'; }) .forEach((row, idx) => { const fieldChooser = getFieldChooserColumnForRow(idx); - fieldChooser.reactWrapper.find('input').forEach(input => { + fieldChooser.reactWrapper.find('input').forEach((input) => { const props = input.props(); if (props['data-test-subj'].endsWith('avg')) { expect(props.checked).toBe(true); @@ -335,7 +335,7 @@ describe('Create Rollup Job, step 5: Metrics', () => { return; } const fieldChooser = getFieldChooserColumnForRow(idx); - fieldChooser.reactWrapper.find('input').forEach(input => { + fieldChooser.reactWrapper.find('input').forEach((input) => { expect(input.props().checked).toBe(false); }); }); @@ -363,13 +363,9 @@ describe('Create Rollup Job, step 5: Metrics', () => { */ // 1. - find('rollupJobMetricsSelectAllCheckbox-avg') - .first() - .simulate('change', { checked: true }); + find('rollupJobMetricsSelectAllCheckbox-avg').first().simulate('change', { checked: true }); // 2. - find('rollupJobMetricsSelectAllCheckbox-max') - .first() - .simulate('change', { checked: true }); + find('rollupJobMetricsSelectAllCheckbox-max').first().simulate('change', { checked: true }); const selectAllCheckbox = getSelectAllInputForRow(0); @@ -380,25 +376,15 @@ describe('Create Rollup Job, step 5: Metrics', () => { selectAllCheckbox.simulate('change', { checked: false }); // 4. - expect( - find('rollupJobMetricsSelectAllCheckbox-avg') - .first() - .props().checked - ).toBe(false); - expect( - find('rollupJobMetricsSelectAllCheckbox-max') - .first() - .props().checked - ).toBe(false); + expect(find('rollupJobMetricsSelectAllCheckbox-avg').first().props().checked).toBe(false); + expect(find('rollupJobMetricsSelectAllCheckbox-max').first().props().checked).toBe(false); let rows = getFieldListTableRows(); // 5. getSelectAllInputForRow(rows.length - 1).simulate('change', { checked: true }); // 6. - find('rollupJobMetricsSelectAllCheckbox-max') - .first() - .simulate('change', { checked: true }); + find('rollupJobMetricsSelectAllCheckbox-max').first().simulate('change', { checked: true }); find('rollupJobMetricsSelectAllCheckbox-max') .first() .simulate('change', { checked: false }); @@ -406,7 +392,7 @@ describe('Create Rollup Job, step 5: Metrics', () => { rows = getFieldListTableRows(); const lastRowFieldChooserColumn = getFieldChooserColumnForRow(rows.length - 1); // 7. - lastRowFieldChooserColumn.reactWrapper.find('input').forEach(input => { + lastRowFieldChooserColumn.reactWrapper.find('input').forEach((input) => { const props = input.props(); if (props['data-test-subj'].endsWith('max') || props['data-test-subj'].endsWith('All')) { expect(props.checked).toBe(false); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js index d625b8f11208f..59118ef6f8ec3 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js @@ -10,7 +10,7 @@ import { setHttp } from '../../crud_app/services'; import { JOBS } from './helpers/constants'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); jest.mock('../../kibana_services', () => { const services = require.requireActual('../../kibana_services'); @@ -75,8 +75,8 @@ describe('Create Rollup Job, step 6: Review', () => { }); describe('tabs', () => { - const getTabsText = () => find('stepReviewTab').map(tab => tab.text()); - const selectFirstField = step => { + const getTabsText = () => find('stepReviewTab').map((tab) => tab.text()); + const selectFirstField = (step) => { find('rollupJobShowFieldChooserButton').simulate('click'); // Select the first term field @@ -149,7 +149,7 @@ describe('Create Rollup Job, step 6: Review', () => { actions.clickSave(); // Given the following anti-jitter sleep x-pack/plugins/rollup/public/crud_app/store/actions/create_job.js // we add a longer sleep here :( - await new Promise(res => setTimeout(res, 750)); + await new Promise((res) => setTimeout(res, 750)); expect(startMock.http.put).toHaveBeenCalledWith(jobCreateApiPath, expect.anything()); // It has been called! expect(startMock.http.get).not.toHaveBeenCalledWith(jobStartApiPath); // It has still not been called! @@ -173,7 +173,7 @@ describe('Create Rollup Job, step 6: Review', () => { actions.clickSave(); // Given the following anti-jitter sleep x-pack/plugins/rollup/public/crud_app/store/actions/create_job.js // we add a longer sleep here :( - await new Promise(res => setTimeout(res, 750)); + await new Promise((res) => setTimeout(res, 750)); expect(startMock.http.post).toHaveBeenCalledWith(jobStartApiPath, expect.anything()); // It has been called! }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js index 61993f3092840..f21fc2c12a007 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js @@ -8,7 +8,7 @@ import { setHttp } from '../../crud_app/services'; import { pageHelpers, mockHttpRequest } from './helpers'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); const { setup } = pageHelpers.jobCreate; diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js index c6988236d6b7c..f05e9fd1decd6 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js @@ -13,7 +13,7 @@ jest.mock('../../crud_app/services', () => { const services = require.requireActual('../../crud_app/services'); return { ...services, - getRouterLinkProps: link => ({ href: link }), + getRouterLinkProps: (link) => ({ href: link }), }; }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js index bdf57a555cdad..53a3af38f3235 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js @@ -8,10 +8,9 @@ import { mockHttpRequest, pageHelpers, nextTick } from './helpers'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; import { getRouter } from '../../crud_app/services/routing'; import { setHttp } from '../../crud_app/services'; -import { CRUD_APP_BASE_PATH } from '../../crud_app/constants'; import { coreMock } from '../../../../../../src/core/public/mocks'; -jest.mock('lodash/function/debounce', () => fn => fn); +jest.mock('lodash/function/debounce', () => (fn) => fn); jest.mock('../../kibana_services', () => { const services = require.requireActual('../../kibana_services'); @@ -65,8 +64,8 @@ describe('Smoke test cloning an existing rollup job from job list', () => { find('jobActionMenuButton').simulate('click'); - expect(router.history.location.pathname).not.toBe(`${CRUD_APP_BASE_PATH}/create`); + expect(router.history.location.pathname).not.toBe(`/create`); find('jobCloneActionContextMenu').simulate('click'); - expect(router.history.location.pathname).toBe(`${CRUD_APP_BASE_PATH}/create`); + expect(router.history.location.pathname).toBe(`/create`); }); }); diff --git a/x-pack/plugins/rollup/server/collectors/register.ts b/x-pack/plugins/rollup/server/collectors/register.ts index 02ad5dc92fd13..629dd8b180fdd 100644 --- a/x-pack/plugins/rollup/server/collectors/register.ts +++ b/x-pack/plugins/rollup/server/collectors/register.ts @@ -50,7 +50,7 @@ async function fetchRollupIndexPatterns(kibanaIndex: string, callCluster: CallCl const esResponse = await callCluster('search', searchParams); - return get(esResponse, 'hits.hits', []).map(indexPattern => { + return get(esResponse, 'hits.hits', []).map((indexPattern) => { const { _id: savedObjectId } = indexPattern; return getIdFromSavedObjectId(savedObjectId); }); @@ -138,7 +138,7 @@ async function fetchRollupVisualizations( let rollupVisualizations = 0; let rollupVisualizationsFromSavedSearches = 0; - visualizations.forEach(visualization => { + visualizations.forEach((visualization) => { const { _source: { visualization: { @@ -153,7 +153,7 @@ async function fetchRollupVisualizations( if (savedSearchRefName) { // This visualization depends upon a saved search. - const savedSearch = references.find(ref => ref.name === savedSearchRefName); + const savedSearch = references.find((ref) => ref.name === savedSearchRefName); if (rollupSavedSearchesToFlagMap[savedSearch.id]) { rollupVisualizations++; rollupVisualizationsFromSavedSearches++; diff --git a/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js b/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js index 2bdbe8fc6ea15..a67f67de859f5 100644 --- a/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js +++ b/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js @@ -32,27 +32,13 @@ describe('areJobsCompatible', () => { describe('mergeJobConfigurations', () => { it('should throw an error for null/invalid jobs', () => { - expect(mergeJobConfigurations) - .withArgs() - .to.throwException(); - expect(mergeJobConfigurations) - .withArgs(null) - .to.throwException(); - expect(mergeJobConfigurations) - .withArgs(undefined) - .to.throwException(); - expect(mergeJobConfigurations) - .withArgs(true) - .to.throwException(); - expect(mergeJobConfigurations) - .withArgs('foo') - .to.throwException(); - expect(mergeJobConfigurations) - .withArgs(123) - .to.throwException(); - expect(mergeJobConfigurations) - .withArgs([]) - .to.throwException(); + expect(mergeJobConfigurations).withArgs().to.throwException(); + expect(mergeJobConfigurations).withArgs(null).to.throwException(); + expect(mergeJobConfigurations).withArgs(undefined).to.throwException(); + expect(mergeJobConfigurations).withArgs(true).to.throwException(); + expect(mergeJobConfigurations).withArgs('foo').to.throwException(); + expect(mergeJobConfigurations).withArgs(123).to.throwException(); + expect(mergeJobConfigurations).withArgs([]).to.throwException(); }); it('should return aggregations for one job', () => { @@ -147,8 +133,6 @@ describe('mergeJobConfigurations', () => { }); it('should throw an error if jobs are not compatible', () => { - expect(mergeJobConfigurations) - .withArgs([jobs[0], jobs[1], jobs[2]]) - .to.throwException(); + expect(mergeJobConfigurations).withArgs([jobs[0], jobs[1], jobs[2]]).to.throwException(); }); }); diff --git a/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts b/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts index f93641e5962b7..f5f54cf9a54e8 100644 --- a/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts +++ b/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts @@ -45,7 +45,7 @@ export function mergeJobConfigurations(jobs = []) { const fieldNames = Object.keys(fields); // Check each field - fieldNames.forEach(fieldName => { + fieldNames.forEach((fieldName) => { const fieldAggs = fields[fieldName]; // Look through each field's capabilities (aggregations) diff --git a/x-pack/plugins/rollup/server/lib/map_capabilities.ts b/x-pack/plugins/rollup/server/lib/map_capabilities.ts index e0f8af865beb4..233c6d1dd4b4b 100644 --- a/x-pack/plugins/rollup/server/lib/map_capabilities.ts +++ b/x-pack/plugins/rollup/server/lib/map_capabilities.ts @@ -10,7 +10,7 @@ export function getCapabilitiesForRollupIndices(indices: { [key: string]: any }) const indexNames = Object.keys(indices); const capabilities = {} as { [key: string]: any }; - indexNames.forEach(index => { + indexNames.forEach((index) => { try { capabilities[index] = mergeJobConfigurations(indices[index].rollup_jobs); } catch (e) { diff --git a/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts b/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts index 24abe9045aae8..51111e9e45d0a 100644 --- a/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts +++ b/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts @@ -19,7 +19,7 @@ export const mergeCapabilitiesWithFields = ( const rollupFields = [...previousFields]; const rollupFieldNames: string[] = []; - Object.keys(rollupIndexCapabilities).forEach(agg => { + Object.keys(rollupIndexCapabilities).forEach((agg) => { // Field names of the aggregation const fields = Object.keys(rollupIndexCapabilities[agg]); @@ -41,7 +41,7 @@ export const mergeCapabilitiesWithFields = ( ...defaultField, name: timeFieldName, }; - const existingField = rollupFields.find(field => field.name === timeFieldName); + const existingField = rollupFields.find((field) => field.name === timeFieldName); if (existingField) { Object.assign(existingField, newField); @@ -56,8 +56,8 @@ export const mergeCapabilitiesWithFields = ( else { rollupFields.push( ...fields - .filter(field => !rollupFieldNames.includes(field)) - .map(field => { + .filter((field) => !rollupFieldNames.includes(field)) + .map((field) => { // Expand each field into object format that end consumption expects. const fieldCapsKey = `${field}.${agg}.value`; rollupFieldNames.push(field); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js index 800f7d2761907..977601247594f 100644 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js @@ -8,7 +8,7 @@ import { getRollupSearchCapabilities } from './rollup_search_capabilities'; class DefaultSearchCapabilities { constructor(request, fieldsCapabilities = {}) { this.fieldsCapabilities = fieldsCapabilities; - this.parseInterval = jest.fn(interval => interval); + this.parseInterval = jest.fn((interval) => interval); } } diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index 575bc9bf9dff1..f5ddab70342de 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -137,7 +137,7 @@ export class RollupPlugin implements Plugin { this.globalConfig$ .pipe(first()) .toPromise() - .then(globalConfig => { + .then((globalConfig) => { registerRollupUsageCollector(usageCollection, globalConfig.kibana.index); }) .catch((e: any) => { diff --git a/x-pack/plugins/rollup/server/rollup_data_enricher.ts b/x-pack/plugins/rollup/server/rollup_data_enricher.ts index b06cf971a6460..bd7730d511279 100644 --- a/x-pack/plugins/rollup/server/rollup_data_enricher.ts +++ b/x-pack/plugins/rollup/server/rollup_data_enricher.ts @@ -18,7 +18,7 @@ export const rollupDataEnricher = async (indicesList: Index[], callWithRequest: try { const rollupJobData = await callWithRequest('transport.request', params); - return indicesList.map(index => { + return indicesList.map((index) => { const isRollupIndex = !!rollupJobData[index.name]; return { ...index, diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts index 32f23314c5259..546d9d628277f 100644 --- a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts @@ -25,7 +25,7 @@ const getFieldsForWildcardRequest = async ( response: any, IndexPatternsFetcher: any ) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, meta_fields: metaFields } = request.query; @@ -79,7 +79,7 @@ export const registerFieldsForWildcardRoute = ({ if (!rollupIndex) { return '[request query.params]: "rollup_index" is required'; } else if (keys.length > 1) { - const invalidParams = keys.filter(key => key !== 'rollup_index'); + const invalidParams = keys.filter((key) => key !== 'rollup_index'); return `[request query.params]: ${invalidParams.join(', ')} is not allowed`; } } catch (err) { diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts index 9e22060b9beb7..81e457fb4f608 100644 --- a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts @@ -46,7 +46,7 @@ function isNumericField(fieldCapability: FieldCapability) { 'half_float', 'scaled_float', ]; - return numericTypes.some(numericType => fieldCapability[numericType as NumericField] != null); + return numericTypes.some((numericType) => fieldCapability[numericType as NumericField] != null); } /** diff --git a/x-pack/plugins/rollup/server/services/license.ts b/x-pack/plugins/rollup/server/services/license.ts index bfd357867c3e2..5424092a01ee5 100644 --- a/x-pack/plugins/rollup/server/services/license.ts +++ b/x-pack/plugins/rollup/server/services/license.ts @@ -37,7 +37,7 @@ export class License { { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } ) { - licensing.license$.subscribe(license => { + licensing.license$.subscribe((license) => { const { state, message } = license.check(pluginId, minimumLicenseType); const hasRequiredLicense = state === 'valid'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/unsafe_utils.test.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/unsafe_utils.test.ts index ca423310e8375..975af90db0e4b 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/unsafe_utils.test.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/unsafe_utils.test.ts @@ -10,15 +10,15 @@ import { normalized, breakdown } from './fixtures/breakdown'; import { inputTimes, normalizedTimes } from './fixtures/normalize_times'; import { inputIndices, normalizedIndices } from './fixtures/normalize_indices'; -describe('normalizeBreakdown', function() { - it('returns correct breakdown', function() { +describe('normalizeBreakdown', function () { + it('returns correct breakdown', function () { const result = util.normalizeBreakdown(breakdown); expect(result).to.eql(normalized); }); }); -describe('normalizeTime', function() { - it('returns correct normalization', function() { +describe('normalizeTime', function () { + it('returns correct normalization', function () { const totalTime = 0.447365; // Deep clone the object to preserve the original @@ -33,8 +33,8 @@ describe('normalizeTime', function() { }); }); -describe('normalizeIndices', function() { - it('returns correct ordering', function() { +describe('normalizeIndices', function () { + it('returns correct ordering', function () { // Deep clone the object to preserve the original const input = JSON.parse(JSON.stringify(inputIndices)); util.normalizeIndices(input, 'searches'); diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts index 1a29e77f74e89..f5daa6b38de48 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts @@ -51,9 +51,9 @@ export function mutateSearchTimesTree(shard: Shard) { } const initShards = (data: ShardSerialized[]) => - data.map(s => { + data.map((s) => { const idMatch = s.id.match(/\[([^\]\[]*?)\]/g) || []; - const ids = idMatch.map(id => { + const ids = idMatch.map((id) => { return id.replace('[', '').replace(']', ''); }); return { diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx index ade547a7d440f..7849f1c2a9d4c 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx @@ -40,7 +40,7 @@ export const ProfileTree = memo(({ data, target, onHighlight, onDataInitError }: gutterSize="none" direction="column" > - {sortedIndices.map(index => ( + {sortedIndices.map((index) => ( { const { relative, time } = shard; const [shardVisibility, setShardVisibility] = useState(() => - hasVisibleOperation(operations.map(op => op.treeRoot ?? op)) + hasVisibleOperation(operations.map((op) => op.treeRoot ?? op)) ); return ( diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/unsafe_utils.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/unsafe_utils.ts index 2201ad42070b7..b023f1b365c06 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/unsafe_utils.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/unsafe_utils.ts @@ -93,7 +93,7 @@ export function normalizeBreakdown(breakdown: Record) { }, 0); Object.keys(breakdown) .sort() - .forEach(key => { + .forEach((key) => { let relative = 0; if (key.indexOf('_count') === -1) { relative = ((breakdown[key] / total) * 100).toFixed(1) as any; @@ -122,10 +122,10 @@ export function normalizeIndices(indices: IndexMap, target: Targets) { let sortQueryComponents; if (target === 'searches') { sortQueryComponents = (a: Shard, b: Shard) => { - const aTime = _.sum(a.searches!, search => { + const aTime = _.sum(a.searches!, (search) => { return search.treeRoot!.time; }); - const bTime = _.sum(b.searches!, search => { + const bTime = _.sum(b.searches!, (search) => { return search.treeRoot!.time; }); @@ -133,10 +133,10 @@ export function normalizeIndices(indices: IndexMap, target: Targets) { }; } else if (target === 'aggregations') { sortQueryComponents = (a: Shard, b: Shard) => { - const aTime = _.sum(a.aggregations!, agg => { + const aTime = _.sum(a.aggregations!, (agg) => { return agg.treeRoot!.time; }); - const bTime = _.sum(b.aggregations!, agg => { + const bTime = _.sum(b.aggregations!, (agg) => { return agg.treeRoot!.time; }); diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/utils.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/utils.ts index d0bedd873a1bd..d6e409b9126a7 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/utils.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/utils.ts @@ -7,5 +7,5 @@ import { Operation } from '../../types'; export const hasVisibleChild = ({ children }: Operation) => { - return Boolean(children && children.some(child => child.visible)); + return Boolean(children && children.some((child) => child.visible)); }; diff --git a/x-pack/plugins/searchprofiler/public/application/containers/main/main.tsx b/x-pack/plugins/searchprofiler/public/application/containers/main/main.tsx index 11dbc6b320531..480f265a80079 100644 --- a/x-pack/plugins/searchprofiler/public/application/containers/main/main.tsx +++ b/x-pack/plugins/searchprofiler/public/application/containers/main/main.tsx @@ -59,7 +59,7 @@ export const Main = () => { [dispatch] ); - const onHighlight = useCallback(value => dispatch({ type: 'setHighlightDetails', value }), [ + const onHighlight = useCallback((value) => dispatch({ type: 'setHighlightDetails', value }), [ dispatch, ]); diff --git a/x-pack/plugins/searchprofiler/public/application/containers/profile_query_editor.tsx b/x-pack/plugins/searchprofiler/public/application/containers/profile_query_editor.tsx index f6377d2b4f906..878189bf13cdd 100644 --- a/x-pack/plugins/searchprofiler/public/application/containers/profile_query_editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/containers/profile_query_editor.tsx @@ -65,7 +65,7 @@ export const ProfileQueryEditor = memo(() => { } }; - const onEditorReady = useCallback(editorInstance => (editorRef.current = editorInstance), []); + const onEditorReady = useCallback((editorInstance) => (editorRef.current = editorInstance), []); const licenseEnabled = getLicenseStatus().valid; return ( @@ -87,7 +87,7 @@ export const ProfileQueryEditor = memo(() => { > { + inputRef={(ref) => { if (ref) { indexInputRef.current = ref; ref.value = DEFAULT_INDEX_VALUE; diff --git a/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx index e56fc79156666..9d38329583ed6 100644 --- a/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx @@ -14,7 +14,7 @@ describe('Editor Component', () => { const props: Props = { initialValue: '', licenseEnabled: true, - onEditorReady: e => {}, + onEditorReady: (e) => {}, }; // Ignore the warning about Worker not existing for now... const init = registerTestBed(Editor); diff --git a/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.test.ts b/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.test.ts index 9dece5a39e96c..df2d74b9a3ecd 100644 --- a/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.test.ts +++ b/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.test.ts @@ -7,14 +7,14 @@ import expect from '@kbn/expect'; import { checkForParseErrors } from '.'; -describe('checkForParseErrors', function() { - it('returns error from bad JSON', function() { +describe('checkForParseErrors', function () { + it('returns error from bad JSON', function () { const json = '{"foo": {"bar": {"baz": "buzz}}}'; const result = checkForParseErrors(json); expect(result.error.message).to.be(`Unexpected end of JSON input`); }); - it('returns parsed value from good JSON', function() { + it('returns parsed value from good JSON', function () { const json = '{"foo": {"bar": {"baz": "buzz"}}}'; const result = checkForParseErrors(json); expect(!!result.parsed).to.be(true); diff --git a/x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.test.ts b/x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.test.ts index 6c0e1124af176..dfb6e93891584 100644 --- a/x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.test.ts +++ b/x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.test.ts @@ -7,43 +7,43 @@ import expect from '@kbn/expect'; import { nsToPretty } from './ns_to_pretty'; -describe('nsToPretty', function() { - it('returns correct time for ns', function() { +describe('nsToPretty', function () { + it('returns correct time for ns', function () { const result = nsToPretty(500, 1); expect(result).to.eql('500.0ns'); }); - it('returns correct time for µs', function() { + it('returns correct time for µs', function () { const result = nsToPretty(5000, 1); expect(result).to.eql('5.0µs'); }); - it('returns correct time for ms', function() { + it('returns correct time for ms', function () { const result = nsToPretty(5000000, 1); expect(result).to.eql('5.0ms'); }); - it('returns correct time for s', function() { + it('returns correct time for s', function () { const result = nsToPretty(5000000000, 1); expect(result).to.eql('5.0s'); }); - it('returns correct time for min', function() { + it('returns correct time for min', function () { const result = nsToPretty(5000000000 * 60, 1); expect(result).to.eql('5.0min'); }); - it('returns correct time for hr', function() { + it('returns correct time for hr', function () { const result = nsToPretty(3.6e12 * 5, 1); expect(result).to.eql('5.0hr'); }); - it('returns correct time for day', function() { + it('returns correct time for day', function () { const result = nsToPretty(3.6e12 * 24 * 5, 1); expect(result).to.eql('5.0d'); }); - it('returns correct time for precision', function() { + it('returns correct time for precision', function () { const result = nsToPretty(500, 5); expect(result).to.eql('500.00000ns'); }); diff --git a/x-pack/plugins/searchprofiler/public/plugin.ts b/x-pack/plugins/searchprofiler/public/plugin.ts index a3c0770a524ab..1089a6e911817 100644 --- a/x-pack/plugins/searchprofiler/public/plugin.ts +++ b/x-pack/plugins/searchprofiler/public/plugin.ts @@ -65,7 +65,7 @@ export class SearchProfilerUIPlugin implements Plugin { + licensing.license$.subscribe((license) => { if (!checkLicenseStatus(license).valid && !devTool.isDisabled()) { devTool.disable(); } else if (devTool.isDisabled()) { diff --git a/x-pack/plugins/searchprofiler/server/plugin.ts b/x-pack/plugins/searchprofiler/server/plugin.ts index 6ca69b69c1123..0dfb65aa6f857 100644 --- a/x-pack/plugins/searchprofiler/server/plugin.ts +++ b/x-pack/plugins/searchprofiler/server/plugin.ts @@ -29,7 +29,7 @@ export class SearchProfilerServerPlugin implements Plugin { log: this.log, }); - licensing.license$.subscribe(license => { + licensing.license$.subscribe((license) => { const { state, message } = license.check(PLUGIN.id, PLUGIN.minimumLicenseType); const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense) { diff --git a/x-pack/plugins/searchprofiler/server/routes/profile.ts b/x-pack/plugins/searchprofiler/server/routes/profile.ts index 4af3f0519cbc0..914c688a080f8 100644 --- a/x-pack/plugins/searchprofiler/server/routes/profile.ts +++ b/x-pack/plugins/searchprofiler/server/routes/profile.ts @@ -46,7 +46,7 @@ export const register = ({ router, getLicenseStatus, log }: RouteDependencies) = body: JSON.stringify(parsed, null, 2), }; try { - const resp = await elasticsearch.dataClient.callAsCurrentUser('search', body); + const resp = await elasticsearch.legacy.client.callAsCurrentUser('search', body); return response.ok({ body: { ok: true, diff --git a/x-pack/plugins/security/common/licensing/license_features.ts b/x-pack/plugins/security/common/licensing/license_features.ts index 571d2630b2b17..8576e4bbc3555 100644 --- a/x-pack/plugins/security/common/licensing/license_features.ts +++ b/x-pack/plugins/security/common/licensing/license_features.ts @@ -38,6 +38,11 @@ export interface SecurityLicenseFeatures { */ readonly allowAccessAgreement: boolean; + /** + * Indicates whether we allow logging of audit events. + */ + readonly allowAuditLogging: boolean; + /** * Indicates whether we allow users to define document level security in roles. */ diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index 9dec665614635..564b71a2e0fac 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -5,10 +5,10 @@ */ import { of, BehaviorSubject } from 'rxjs'; -import { licensingMock } from '../../../licensing/public/mocks'; +import { licenseMock } from '../../../licensing/common/licensing.mock'; import { SecurityLicenseService } from './license_service'; -describe('license features', function() { +describe('license features', function () { it('should display error when ES is unavailable', () => { const serviceSetup = new SecurityLicenseService().setup({ license$: of(undefined as any), @@ -24,11 +24,12 @@ describe('license features', function() { layout: 'error-es-unavailable', allowRbac: false, allowSubFeaturePrivileges: false, + allowAuditLogging: false, }); }); it('should display error when X-Pack is unavailable', () => { - const rawLicenseMock = licensingMock.createLicenseMock(); + const rawLicenseMock = licenseMock.createLicenseMock(); rawLicenseMock.isAvailable = false; const serviceSetup = new SecurityLicenseService().setup({ license$: of(rawLicenseMock), @@ -44,11 +45,12 @@ describe('license features', function() { layout: 'error-xpack-unavailable', allowRbac: false, allowSubFeaturePrivileges: false, + allowAuditLogging: false, }); }); it('should notify consumers of licensed feature changes', () => { - const rawLicenseMock = licensingMock.createLicenseMock(); + const rawLicenseMock = licenseMock.createLicenseMock(); rawLicenseMock.isAvailable = false; const rawLicense$ = new BehaviorSubject(rawLicenseMock); const serviceSetup = new SecurityLicenseService().setup({ @@ -63,6 +65,7 @@ describe('license features', function() { Array [ Object { "allowAccessAgreement": false, + "allowAuditLogging": false, "allowLogin": false, "allowRbac": false, "allowRoleDocumentLevelSecurity": false, @@ -76,12 +79,13 @@ describe('license features', function() { ] `); - rawLicense$.next(licensingMock.createLicenseMock()); + rawLicense$.next(licenseMock.createLicenseMock()); expect(subscriptionHandler).toHaveBeenCalledTimes(2); expect(subscriptionHandler.mock.calls[1]).toMatchInlineSnapshot(` Array [ Object { "allowAccessAgreement": true, + "allowAuditLogging": true, "allowLogin": true, "allowRbac": true, "allowRoleDocumentLevelSecurity": true, @@ -99,7 +103,7 @@ describe('license features', function() { }); it('should show login page and other security elements, allow RBAC but forbid paid features if license is basic.', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ features: { security: { isEnabled: true, isAvailable: true } }, }); @@ -118,13 +122,14 @@ describe('license features', function() { allowRoleFieldLevelSecurity: false, allowRbac: true, allowSubFeaturePrivileges: false, + allowAuditLogging: false, }); expect(getFeatureSpy).toHaveBeenCalledTimes(1); expect(getFeatureSpy).toHaveBeenCalledWith('security'); }); it('should not show login page or other security elements if security is disabled in Elasticsearch.', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ features: { security: { isEnabled: false, isAvailable: true } }, }); @@ -141,11 +146,12 @@ describe('license features', function() { allowRoleFieldLevelSecurity: false, allowRbac: false, allowSubFeaturePrivileges: false, + allowAuditLogging: false, }); }); it('should allow role mappings, access agreement and sub-feature privileges, but not DLS/FLS if license = gold', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ license: { mode: 'gold', type: 'gold' }, features: { security: { isEnabled: true, isAvailable: true } }, }); @@ -163,11 +169,12 @@ describe('license features', function() { allowRoleFieldLevelSecurity: false, allowRbac: true, allowSubFeaturePrivileges: true, + allowAuditLogging: true, }); }); it('should allow to login, allow RBAC, role mappings, access agreement, sub-feature privileges, and DLS if license >= platinum', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ license: { mode: 'platinum', type: 'platinum' }, features: { security: { isEnabled: true, isAvailable: true } }, }); @@ -185,6 +192,30 @@ describe('license features', function() { allowRoleFieldLevelSecurity: true, allowRbac: true, allowSubFeaturePrivileges: true, + allowAuditLogging: true, + }); + }); + + it('should allow all basic features + audit logging for standard license', () => { + const mockRawLicense = licenseMock.createLicense({ + license: { mode: 'standard', type: 'standard' }, + features: { security: { isEnabled: true, isAvailable: true } }, + }); + + const serviceSetup = new SecurityLicenseService().setup({ + license$: of(mockRawLicense), + }); + expect(serviceSetup.license.getFeatures()).toEqual({ + showLogin: true, + allowLogin: true, + showLinks: true, + showRoleMappingsManagement: false, + allowAccessAgreement: false, + allowRoleDocumentLevelSecurity: false, + allowRoleFieldLevelSecurity: false, + allowRbac: true, + allowSubFeaturePrivileges: false, + allowAuditLogging: true, }); }); }); diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 7815798d6a9f3..75c7670f28a67 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -25,7 +25,7 @@ export class SecurityLicenseService { public setup({ license$ }: SetupDeps) { let rawLicense: Readonly | undefined; - this.licenseSubscription = license$.subscribe(nextRawLicense => { + this.licenseSubscription = license$.subscribe((nextRawLicense) => { rawLicense = nextRawLicense; }); @@ -36,7 +36,7 @@ export class SecurityLicenseService { getFeatures: () => this.calculateFeaturesFromRawLicense(rawLicense), features$: license$.pipe( - map(nextRawLicense => this.calculateFeaturesFromRawLicense(nextRawLicense)) + map((nextRawLicense) => this.calculateFeaturesFromRawLicense(nextRawLicense)) ), }), }; @@ -72,6 +72,7 @@ export class SecurityLicenseService { showLinks: false, showRoleMappingsManagement: false, allowAccessAgreement: false, + allowAuditLogging: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, @@ -90,6 +91,7 @@ export class SecurityLicenseService { showLinks: false, showRoleMappingsManagement: false, allowAccessAgreement: false, + allowAuditLogging: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, @@ -97,6 +99,7 @@ export class SecurityLicenseService { }; } + const isLicenseStandardOrBetter = rawLicense.hasAtLeast('standard'); const isLicenseGoldOrBetter = rawLicense.hasAtLeast('gold'); const isLicensePlatinumOrBetter = rawLicense.hasAtLeast('platinum'); return { @@ -105,6 +108,7 @@ export class SecurityLicenseService { showLinks: true, showRoleMappingsManagement: isLicenseGoldOrBetter, allowAccessAgreement: isLicenseGoldOrBetter, + allowAuditLogging: isLicenseStandardOrBetter, allowSubFeaturePrivileges: isLicenseGoldOrBetter, // Only platinum and trial licenses are compliant with field- and document-level security. allowRoleDocumentLevelSecurity: isLicensePlatinumOrBetter, diff --git a/x-pack/plugins/security/common/model/authenticated_user.test.ts b/x-pack/plugins/security/common/model/authenticated_user.test.ts index 602acc840eaff..cdf1423df56df 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.test.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.test.ts @@ -7,7 +7,7 @@ import { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; describe('#canUserChangePassword', () => { - ['reserved', 'native'].forEach(realm => { + ['reserved', 'native'].forEach((realm) => { it(`returns true for users in the ${realm} realm`, () => { expect( canUserChangePassword({ diff --git a/x-pack/plugins/security/common/privilege_calculator_utils.ts b/x-pack/plugins/security/common/privilege_calculator_utils.ts index e767862c46757..728ee61ce9f69 100644 --- a/x-pack/plugins/security/common/privilege_calculator_utils.ts +++ b/x-pack/plugins/security/common/privilege_calculator_utils.ts @@ -52,7 +52,7 @@ function actionToRegExp(action: string) { return new RegExp( action .split('*') - .map(part => _.escapeRegExp(part)) + .map((part) => _.escapeRegExp(part)) .join('.*') ); } diff --git a/x-pack/plugins/security/public/account_management/account_management_app.ts b/x-pack/plugins/security/public/account_management/account_management_app.ts index 41567a04fe030..0bb7785259c0e 100644 --- a/x-pack/plugins/security/public/account_management/account_management_app.ts +++ b/x-pack/plugins/security/public/account_management/account_management_app.ts @@ -5,7 +5,12 @@ */ import { i18n } from '@kbn/i18n'; -import { StartServicesAccessor, ApplicationSetup, AppMountParameters } from 'src/core/public'; +import { + ApplicationSetup, + AppMountParameters, + AppNavLinkStatus, + StartServicesAccessor, +} from '../../../../../src/core/public'; import { AuthenticationServiceSetup } from '../authentication'; interface CreateDeps { @@ -23,8 +28,7 @@ export const accountManagementApp = Object.freeze({ application.register({ id: this.id, title, - // TODO: switch to proper enum once https://github.com/elastic/kibana/issues/58327 is resolved. - navLinkStatus: 3, + navLinkStatus: AppNavLinkStatus.hidden, appRoute: '/security/account', async mount({ element }: AppMountParameters) { const [ diff --git a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx index a34dcb18d2b9c..9f87388661415 100644 --- a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx +++ b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx @@ -38,8 +38,8 @@ export function AccessAgreementPage({ http, fatalErrors, notifications }: Props) useEffect(() => { http .get<{ accessAgreement: string }>('/internal/security/access_agreement/state') - .then(response => setAccessAgreement(response.accessAgreement)) - .catch(err => fatalErrors.add(err)); + .then((response) => setAccessAgreement(response.accessAgreement)) + .catch((err) => fatalErrors.add(err)); }, [http, fatalErrors]); const onAcknowledge = useCallback( diff --git a/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx b/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx index 35be650d127fb..e20381d35e4e5 100644 --- a/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx +++ b/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx @@ -14,7 +14,7 @@ interface Props { title: React.ReactNode; } -export const AuthenticationStatePage: React.FC = props => ( +export const AuthenticationStatePage: React.FC = (props) => (
    diff --git a/x-pack/plugins/security/public/authentication/login/components/disabled_login_form/disabled_login_form.tsx b/x-pack/plugins/security/public/authentication/login/components/disabled_login_form/disabled_login_form.tsx index b539029d834ec..1cc4124c3a957 100644 --- a/x-pack/plugins/security/public/authentication/login/components/disabled_login_form/disabled_login_form.tsx +++ b/x-pack/plugins/security/public/authentication/login/components/disabled_login_form/disabled_login_form.tsx @@ -12,7 +12,7 @@ interface Props { message: ReactNode; } -export const DisabledLoginForm: React.FC = props => ( +export const DisabledLoginForm: React.FC = (props) => (

    {props.title}

    diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss index 6784052ef4337..344cde9c7825c 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss @@ -23,9 +23,10 @@ } &:focus { + @include euiFocusRing; + border-color: transparent; border-radius: $euiBorderRadius; - @include euiFocusRing; .secLoginCard__title { text-decoration: underline; diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx index 05ad50ce15d9d..9675be2a4d6a7 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx @@ -275,7 +275,7 @@ describe('LoginForm', () => { expectPageMode(wrapper, PageMode.Selector); expect( - wrapper.find('.secLoginCard').map(card => { + wrapper.find('.secLoginCard').map((card) => { const hint = card.find('.secLoginCard__hint'); return { title: card.find('p.secLoginCard__title').text(), @@ -316,7 +316,7 @@ describe('LoginForm', () => { expectPageMode(wrapper, PageMode.Selector); expect( - wrapper.find('.secLoginCard').map(card => { + wrapper.find('.secLoginCard').map((card) => { const hint = card.find('.secLoginCard__hint'); return { title: card.find('p.secLoginCard__title').text(), @@ -359,7 +359,7 @@ describe('LoginForm', () => { expectPageMode(wrapper, PageMode.Selector); - wrapper.findWhere(node => node.key() === 'saml1').simulate('click'); + wrapper.findWhere((node) => node.key() === 'saml1').simulate('click'); await act(async () => { await nextTick(); @@ -403,7 +403,7 @@ describe('LoginForm', () => { expectPageMode(wrapper, PageMode.Selector); - wrapper.findWhere(node => node.key() === 'saml1').simulate('click'); + wrapper.findWhere((node) => node.key() === 'saml1').simulate('click'); await act(async () => { await nextTick(); @@ -445,7 +445,7 @@ describe('LoginForm', () => { expectPageMode(wrapper, PageMode.Selector); - wrapper.findWhere(node => node.key() === 'basic').simulate('click'); + wrapper.findWhere((node) => node.key() === 'basic').simulate('click'); wrapper.update(); expectPageMode(wrapper, PageMode.Form); @@ -512,7 +512,7 @@ describe('LoginForm', () => { expectPageMode(wrapper, PageMode.Selector); // Going to login form. - wrapper.findWhere(node => node.key() === 'basic').simulate('click'); + wrapper.findWhere((node) => node.key() === 'basic').simulate('click'); wrapper.update(); expectPageMode(wrapper, PageMode.Form); diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx index b59547aff24db..ec631e8a2b525 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx @@ -259,7 +259,7 @@ export class LoginForm extends Component { private renderSelector = () => { return ( - {this.props.selector.providers.map(provider => ( + {this.props.selector.providers.map((provider) => ( @@ -113,7 +113,7 @@ describe('DeleteProvider', () => { const wrapper = mountWithIntl( - {onDelete => ( + {(onDelete) => ( @@ -189,7 +189,7 @@ describe('DeleteProvider', () => { const wrapper = mountWithIntl( - {onDelete => ( + {(onDelete) => ( @@ -253,7 +253,7 @@ describe('DeleteProvider', () => { const wrapper = mountWithIntl( - {onDelete => ( + {(onDelete) => ( diff --git a/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx index 860fe22cb8032..7abf34700ad7a 100644 --- a/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx @@ -58,7 +58,7 @@ export const DeleteProvider: React.FunctionComponent = ({ setIsDeleteInProgress(true); try { - result = await roleMappingsAPI.deleteRoleMappings(roleMappings.map(rm => rm.name)); + result = await roleMappingsAPI.deleteRoleMappings(roleMappings.map((rm) => rm.name)); } catch (e) { notifications.toasts.addError(e, { title: i18n.translate( @@ -76,8 +76,8 @@ export const DeleteProvider: React.FunctionComponent = ({ closeModal(); - const successfulDeletes = result.filter(res => res.success); - const erroredDeletes = result.filter(res => !res.success); + const successfulDeletes = result.filter((res) => res.success); + const erroredDeletes = result.filter((res) => !res.success); // Surface success notifications if (successfulDeletes.length > 0) { diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx index 149c1271123d2..b4e755507f8c5 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx @@ -12,6 +12,7 @@ import { findTestSubject } from 'test_utils/find_test_subject'; // This is not required for the tests to pass, but it rather suppresses lengthy // warnings in the console which adds unnecessary noise to the test output. import 'test_utils/stub_web_worker'; +import { ScopedHistory } from 'kibana/public'; import { EditRoleMappingPage } from '.'; import { NoCompatibleRealms, SectionLoading, PermissionDenied } from '../components'; @@ -21,13 +22,15 @@ import { RolesAPIClient } from '../../roles'; import { Role } from '../../../../common/model'; import { DocumentationLinksService } from '../documentation_links'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { roleMappingsAPIClientMock } from '../role_mappings_api_client.mock'; import { rolesAPIClientMock } from '../../roles/roles_api_client.mock'; import { RoleComboBox } from '../../role_combo_box'; describe('EditRoleMappingPage', () => { + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; let rolesAPI: PublicMethodsOf; + beforeEach(() => { rolesAPI = rolesAPIClientMock.create(); (rolesAPI as jest.Mocked).getRoles.mockResolvedValue([ @@ -54,6 +57,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -64,10 +68,7 @@ describe('EditRoleMappingPage', () => { target: { value: 'my-role-mapping' }, }); - wrapper - .find(RoleComboBox) - .props() - .onChange(['foo_role']); + wrapper.find(RoleComboBox).props().onChange(['foo_role']); findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); @@ -119,6 +120,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -127,10 +129,7 @@ describe('EditRoleMappingPage', () => { findTestSubject(wrapper, 'switchToRolesButton').simulate('click'); - wrapper - .find(RoleComboBox) - .props() - .onChange(['foo_role']); + wrapper.find(RoleComboBox).props().onChange(['foo_role']); findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); wrapper.find('button[id="addRuleOption"]').simulate('click'); @@ -169,6 +168,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -196,6 +196,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -233,6 +234,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -273,6 +275,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -315,6 +318,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -369,6 +373,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -424,6 +429,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx index 142b53cbb50f2..b4e3627039ecb 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ScopedHistory } from 'src/core/public'; import { RoleMapping } from '../../../../common/model'; import { RuleEditorPanel } from './rule_editor_panel'; import { @@ -29,7 +29,6 @@ import { SectionLoading, } from '../components'; import { RolesAPIClient } from '../../roles'; -import { ROLE_MAPPINGS_PATH } from '../../management_urls'; import { validateRoleMappingForSave } from './services/role_mapping_validation'; import { MappingInfoPanel } from './mapping_info_panel'; import { DocumentationLinksService } from '../documentation_links'; @@ -55,6 +54,7 @@ interface Props { rolesAPIClient: PublicMethodsOf; notifications: NotificationsStart; docLinks: DocumentationLinksService; + history: ScopedHistory; } export class EditRoleMappingPage extends Component { @@ -106,7 +106,7 @@ export class EditRoleMappingPage extends Component { this.setState({ roleMapping })} + onChange={(roleMapping) => this.setState({ roleMapping })} mode={this.editingExistingRoleMapping() ? 'edit' : 'create'} validateForm={this.state.validateForm} canUseInlineScripts={this.state.canUseInlineScripts} @@ -119,7 +119,7 @@ export class EditRoleMappingPage extends Component { rawRules={this.state.roleMapping!.rules} validateForm={this.state.validateForm} onValidityChange={this.onRuleValidityChange} - onChange={rules => + onChange={(rules) => this.setState({ roleMapping: { ...this.state.roleMapping!, @@ -218,7 +218,7 @@ export class EditRoleMappingPage extends Component { roleMappingsAPI={this.props.roleMappingsAPI} notifications={this.props.notifications} > - {deleteRoleMappingsPrompt => { + {(deleteRoleMappingsPrompt) => { return ( @@ -279,7 +279,7 @@ export class EditRoleMappingPage extends Component { }); this.backToRoleMappingsList(); }) - .catch(e => { + .catch((e) => { this.props.notifications.toasts.addError(e, { title: i18n.translate('xpack.security.management.editRoleMapping.saveError', { defaultMessage: `Error saving role mapping`, @@ -342,7 +342,5 @@ export class EditRoleMappingPage extends Component { } } - private backToRoleMappingsList = () => { - window.location.hash = ROLE_MAPPINGS_PATH; - }; + private backToRoleMappingsList = () => this.props.history.push('/'); } diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx index b376a3943ff48..3df9987141fb2 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx @@ -172,7 +172,7 @@ export class MappingInfoPanel extends Component { mode={this.state.rolesMode} canUseInlineScripts={this.props.canUseInlineScripts} canUseStoredScripts={this.props.canUseStoredScripts} - onChange={roleMapping => this.props.onChange(roleMapping)} + onChange={(roleMapping) => this.props.onChange(roleMapping)} /> @@ -240,7 +240,7 @@ export class MappingInfoPanel extends Component { mode={this.state.rolesMode} canUseInlineScripts={this.props.canUseInlineScripts} canUseStoredScripts={this.props.canUseStoredScripts} - onChange={roleMapping => this.props.onChange(roleMapping)} + onChange={(roleMapping) => this.props.onChange(roleMapping)} /> @@ -286,7 +286,7 @@ export class MappingInfoPanel extends Component { showLabel={false} data-test-subj="roleMappingsEnabledSwitch" checked={this.props.roleMapping.enabled} - onChange={e => { + onChange={(e) => { this.props.onChange({ ...this.props.roleMapping, enabled: e.target.checked, diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.tsx index 8e1597cf3d598..b0f558ee71be8 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.tsx @@ -59,7 +59,7 @@ export class RoleSelector extends React.Component { isLoading={this.state.roles.length === 0} availableRoles={this.state.roles} selectedRoleNames={roles} - onChange={selectedRoles => { + onChange={(selectedRoles) => { this.props.onChange({ ...this.props.roleMapping, roles: selectedRoles, @@ -80,7 +80,7 @@ export class RoleSelector extends React.Component { canUseStoredScripts={this.props.canUseStoredScripts} canUseInlineScripts={this.props.canUseInlineScripts} roleTemplate={rt} - onChange={updatedTemplate => { + onChange={(updatedTemplate) => { const templates = [...(this.props.roleMapping.role_templates || [])]; templates.splice(index, 1, updatedTemplate); this.props.onChange({ @@ -103,7 +103,7 @@ export class RoleSelector extends React.Component { { + onClick={(type) => { switch (type) { case 'inline': { const templates = this.props.roleMapping.role_templates || []; @@ -147,8 +147,8 @@ export class RoleSelector extends React.Component { private hasDeprecatedRolesAssigned = () => { return ( - this.props.roleMapping.roles?.some(r => - this.state.roles.some(role => role.name === r && isRoleDeprecated(role)) + this.props.roleMapping.roles?.some((r) => + this.state.roles.some((role) => role.name === r && isRoleDeprecated(role)) ) ?? false ); }; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.tsx index d79651d7b9cd6..0191f7194e8e5 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.tsx @@ -81,7 +81,7 @@ export const RoleTemplateEditor = ({ checked={roleTemplate.format === 'json'} label={returnsJsonLabel} showLabel={false} - onChange={e => { + onChange={(e) => { onChange({ ...roleTemplate, format: e.target.checked ? 'json' : 'string', @@ -164,7 +164,7 @@ export const RoleTemplateEditor = ({ { + onChange={(e) => { onChange({ ...roleTemplate, template: { @@ -213,7 +213,7 @@ export const RoleTemplateEditor = ({ { + onChange={(e) => { onChange({ ...roleTemplate, template: { diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_type_select.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_type_select.tsx index aa65c5c9bcae7..9bbf4b7ff405a 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_type_select.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_type_select.tsx @@ -53,7 +53,7 @@ export const RoleTemplateTypeSelect = (props: Props) => { singleSelection={{ asPlainText: true }} selectedOptions={selectedOptions} data-test-subj="roleMappingsFormTemplateType" - onChange={selected => { + onChange={(selected) => { const [{ id }] = selected; if (id === 'inline') { props.onChange({ diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.tsx index 6022c836c6904..125e751f9a91b 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.tsx @@ -41,7 +41,7 @@ const userFields = [ }, ]; -const fieldOptions = userFields.map(f => ({ label: f.name })); +const fieldOptions = userFields.map((f) => ({ label: f.name })); type ComparisonOption = 'text' | 'number' | 'null' | 'boolean'; const comparisonOptions: Record< @@ -196,7 +196,7 @@ export class FieldRuleEditor extends Component { ]} data-test-subj={`fieldRuleEditorValueType-${valueIndex}`} value={inputType} - onChange={e => + onChange={(e) => this.onComparisonTypeChange(valueIndex, e.target.value as ComparisonOption) } /> diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx index 5946aac4306b1..48eb1380bd08f 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx @@ -48,7 +48,7 @@ describe('RuleGroupEditor', () => { await nextTick(); wrapper.update(); - const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere(menuItem => { + const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere((menuItem) => { return menuItem.text() === anyRule.getDisplayTitle(); }); @@ -76,13 +76,11 @@ describe('RuleGroupEditor', () => { const anyRule = new AnyRule(); - findTestSubject(wrapper, 'ruleGroupTitle') - .first() - .simulate('click'); + findTestSubject(wrapper, 'ruleGroupTitle').first().simulate('click'); await nextTick(); wrapper.update(); - const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere(menuItem => { + const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere((menuItem) => { return menuItem.text() === anyRule.getDisplayTitle(); }); @@ -116,13 +114,11 @@ describe('RuleGroupEditor', () => { const anyRule = new AnyRule(); - findTestSubject(wrapper, 'ruleGroupTitle') - .first() - .simulate('click'); + findTestSubject(wrapper, 'ruleGroupTitle').first().simulate('click'); await nextTick(); wrapper.update(); - const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere(menuItem => { + const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere((menuItem) => { return menuItem.text() === anyRule.getDisplayTitle(); }); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.tsx index b10a6dd8d183f..2e6cfa5f1cde6 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.tsx @@ -90,7 +90,7 @@ export class RuleGroupEditor extends Component { parentRule={this.props.rule} allowAdd={this.props.allowAdd} ruleDepth={this.props.ruleDepth + 1} - onChange={updatedSubRule => { + onChange={(updatedSubRule) => { const updatedRule = this.props.rule.clone() as RuleGroup; updatedRule.replaceRule(subRuleIndex, updatedSubRule); this.props.onChange(updatedRule); @@ -112,7 +112,7 @@ export class RuleGroupEditor extends Component { { + onChange={(updatedSubRule) => { const updatedRule = this.props.rule.clone() as RuleGroup; updatedRule.replaceRule(subRuleIndex, updatedSubRule); this.props.onChange(updatedRule); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx index 6bef9c09eeef3..6f9853041ffbe 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx @@ -42,7 +42,7 @@ export const RuleGroupTitle = (props: Props) => { const areSubRulesValid = newRule.canContainRules(currentSubRules); if (areSubRulesValid) { const clone = newRule.clone() as RuleGroup; - currentSubRules.forEach(subRule => clone.addRule(subRule)); + currentSubRules.forEach((subRule) => clone.addRule(subRule)); props.onChange(clone); setIsMenuOpen(false); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.tsx index 2e3db275788ee..cb50d30b02a1e 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.tsx @@ -127,7 +127,7 @@ export class VisualRuleEditor extends Component { rule={rule as RuleGroup} ruleDepth={0} allowAdd={this.canUseVisualEditor()} - onChange={value => onChange(value)} + onChange={(value) => onChange(value)} onDelete={this.onRuleDelete} /> ); @@ -135,7 +135,7 @@ export class VisualRuleEditor extends Component { return ( onChange(value)} + onChange={(value) => onChange(value)} onDelete={this.onRuleDelete} /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/model/all_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/all_rule.test.ts index ddf3b4499f73b..d046945747a57 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/all_rule.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/all_rule.test.ts @@ -28,7 +28,7 @@ describe('All rule', () => { const rule = new AllRule() as RuleGroup; expect(rule.canContainRules(subRules)).toEqual(true); - subRules.forEach(sr => rule.addRule(sr)); + subRules.forEach((sr) => rule.addRule(sr)); expect(rule.getRules()).toEqual([...subRules]); }); diff --git a/x-pack/plugins/security/public/management/role_mappings/model/all_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/all_rule.ts index ecea27a7fb87f..f948d4c15e611 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/all_rule.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/all_rule.ts @@ -50,13 +50,13 @@ export class AllRule extends RuleGroup { /** {@see RuleGroup.clone} */ public clone() { - return new AllRule(this.rules.map(r => r.clone())); + return new AllRule(this.rules.map((r) => r.clone())); } /** {@see RuleGroup.toRaw} */ public toRaw() { return { - all: [...this.rules.map(rule => rule.toRaw())], + all: [...this.rules.map((rule) => rule.toRaw())], }; } } diff --git a/x-pack/plugins/security/public/management/role_mappings/model/any_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/any_rule.test.ts index 767aa075755af..d56469c4c9622 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/any_rule.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/any_rule.test.ts @@ -22,7 +22,7 @@ describe('Any rule', () => { const rule = new AnyRule() as RuleGroup; expect(rule.canContainRules(subRules)).toEqual(true); - subRules.forEach(sr => rule.addRule(sr)); + subRules.forEach((sr) => rule.addRule(sr)); expect(rule.getRules()).toEqual([...subRules]); }); diff --git a/x-pack/plugins/security/public/management/role_mappings/model/any_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/any_rule.ts index 6a4f2eaf1b362..5f3f190dd0f37 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/any_rule.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/any_rule.ts @@ -49,19 +49,19 @@ export class AnyRule extends RuleGroup { public canContainRules(rules: Rule[]) { const forbiddenRules = [ExceptAllRule, ExceptAnyRule]; return rules.every( - candidate => !forbiddenRules.some(forbidden => candidate instanceof forbidden) + (candidate) => !forbiddenRules.some((forbidden) => candidate instanceof forbidden) ); } /** {@see RuleGroup.clone} */ public clone() { - return new AnyRule(this.rules.map(r => r.clone())); + return new AnyRule(this.rules.map((r) => r.clone())); } /** {@see RuleGroup.toRaw} */ public toRaw() { return { - any: [...this.rules.map(rule => rule.toRaw())], + any: [...this.rules.map((rule) => rule.toRaw())], }; } } diff --git a/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.test.ts index 7a00e5b19638f..73a5451534ec0 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.test.ts @@ -28,7 +28,7 @@ describe('Except All rule', () => { const rule = new ExceptAllRule() as RuleGroup; expect(rule.canContainRules(subRules)).toEqual(true); - subRules.forEach(sr => rule.addRule(sr)); + subRules.forEach((sr) => rule.addRule(sr)); expect(rule.getRules()).toEqual([...subRules]); }); diff --git a/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.ts index a67c2622a2533..af75f08539c1c 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.ts @@ -50,13 +50,13 @@ export class ExceptAllRule extends RuleGroup { /** {@see RuleGroup.clone} */ public clone() { - return new ExceptAllRule(this.rules.map(r => r.clone())); + return new ExceptAllRule(this.rules.map((r) => r.clone())); } /** {@see RuleGroup.toRaw} */ public toRaw() { const rawRule = { - all: [...this.rules.map(rule => rule.toRaw())], + all: [...this.rules.map((rule) => rule.toRaw())], }; return { diff --git a/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.test.ts index e4e182ce88d8d..902c706d75aec 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.test.ts @@ -22,7 +22,7 @@ describe('Except Any rule', () => { const rule = new ExceptAnyRule() as RuleGroup; expect(rule.canContainRules(subRules)).toEqual(true); - subRules.forEach(sr => rule.addRule(sr)); + subRules.forEach((sr) => rule.addRule(sr)); expect(rule.getRules()).toEqual([...subRules]); }); diff --git a/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.ts index 12ec3fe85b80d..e7bc6a7b0ae4e 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.ts @@ -48,19 +48,19 @@ export class ExceptAnyRule extends RuleGroup { public canContainRules(rules: Rule[]) { const forbiddenRules = [ExceptAllRule, ExceptAnyRule]; return rules.every( - candidate => !forbiddenRules.some(forbidden => candidate instanceof forbidden) + (candidate) => !forbiddenRules.some((forbidden) => candidate instanceof forbidden) ); } /** {@see RuleGroup.clone} */ public clone() { - return new ExceptAnyRule(this.rules.map(r => r.clone())); + return new ExceptAnyRule(this.rules.map((r) => r.clone())); } /** {@see RuleGroup.toRaw} */ public toRaw() { const rawRule = { - any: [...this.rules.map(rule => rule.toRaw())], + any: [...this.rules.map((rule) => rule.toRaw())], }; return { diff --git a/x-pack/plugins/security/public/management/role_mappings/model/field_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/field_rule.test.ts index 3447e81707002..b7bb2a4170ff1 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/field_rule.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/field_rule.test.ts @@ -7,7 +7,7 @@ import { FieldRule } from '.'; describe('FieldRule', () => { - ['*', 1, null, true, false].forEach(value => { + ['*', 1, null, true, false].forEach((value) => { it(`can convert itself to raw form with a single value of ${value}`, () => { const rule = new FieldRule('username', value); expect(rule.toRaw()).toEqual({ diff --git a/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.test.ts index ad486a8b314c4..fea110e6beca4 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.test.ts @@ -252,7 +252,7 @@ describe('generateRulesFromRaw', () => { expect((rules as FieldRule).value).toEqual([0, '*', null, 'foo', true, false]); }); - [{}, () => null, undefined, [{}, undefined, [], () => null]].forEach(invalidValue => { + [{}, () => null, undefined, [{}, undefined, [], () => null]].forEach((invalidValue) => { it(`does not support a value of ${invalidValue}`, () => { expect(() => { generateRulesFromRaw({ diff --git a/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.ts b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.ts index a384e61e521ab..44c6ace3bfeac 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.ts @@ -92,7 +92,7 @@ function createRuleForType( const [field, value] = entries[0] as [string, FieldRuleValue]; const values = Array.isArray(value) ? value : [value]; - values.forEach(fieldValue => { + values.forEach((fieldValue) => { const valueType = typeof fieldValue; if (fieldValue !== null && !['string', 'number', 'boolean'].includes(valueType)) { throw new RuleBuilderError( diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts index 0a88ed1da9ac3..8e8bae42d4ad8 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts @@ -47,11 +47,11 @@ export class RoleMappingsAPIClient { public async deleteRoleMappings(names: string[]): Promise { return Promise.all( - names.map(name => + names.map((name) => this.http .delete(`/internal/security/role_mapping/${encodeURIComponent(name)}`) .then(() => ({ success: true, name })) - .catch(error => ({ success: false, name, error })) + .catch((error) => ({ success: false, name, error })) ) ); } diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx index 6fe4bcc7a0bbb..7330dba968162 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx @@ -7,11 +7,21 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getCreateRoleMappingHref } from '../../../management_urls'; +import { ScopedHistory } from 'kibana/public'; +import { EDIT_ROLE_MAPPING_PATH } from '../../../management_urls'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; -export const CreateRoleMappingButton = () => { +interface CreateRoleMappingButtonProps { + history: ScopedHistory; +} + +export const CreateRoleMappingButton = ({ history }: CreateRoleMappingButtonProps) => { return ( - + = () => ( +interface EmptyPromptProps { + history: ScopedHistory; +} + +export const EmptyPrompt: React.FunctionComponent = ({ history }) => ( = () => (

    } - actions={} + actions={} data-test-subj="roleMappingsEmptyPrompt" /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx index 0d343ad33d78e..fb81ddb641e1f 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { CoreStart, ScopedHistory } from 'kibana/public'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { RoleMappingsGridPage } from '.'; import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../components'; @@ -14,11 +15,19 @@ import { EuiLink } from '@elastic/eui'; import { act } from '@testing-library/react'; import { DocumentationLinksService } from '../documentation_links'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { roleMappingsAPIClientMock } from '../role_mappings_api_client.mock'; import { rolesAPIClientMock } from '../../roles/index.mock'; describe('RoleMappingsGridPage', () => { + let history: ScopedHistory; + let coreStart: CoreStart; + + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + coreStart = coreMock.createStart(); + }); + it('renders an empty prompt when no role mappings exist', async () => { const roleMappingsAPI = roleMappingsAPIClientMock.create(); roleMappingsAPI.getRoleMappings.mockResolvedValue([]); @@ -34,6 +43,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -61,6 +72,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -96,6 +109,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -130,6 +145,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -137,9 +154,7 @@ describe('RoleMappingsGridPage', () => { const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink); expect(links).toHaveLength(1); - expect(links.at(0).props()).toMatchObject({ - href: '#/management/security/roles/edit/superuser', - }); + expect(links.at(0).props().onClick).toBeDefined(); }); it('describes the number of mapped role templates', async () => { @@ -164,6 +179,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -202,6 +219,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -263,6 +282,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx index 5802c3444e85f..757e59a4e0583 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx @@ -24,7 +24,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ApplicationStart, ScopedHistory } from 'src/core/public'; import { RoleMapping, Role } from '../../../../common/model'; import { EmptyPrompt } from './empty_prompt'; import { @@ -33,18 +33,21 @@ import { PermissionDenied, SectionLoading, } from '../components'; -import { getCreateRoleMappingHref, getEditRoleMappingHref } from '../../management_urls'; +import { EDIT_ROLE_MAPPING_PATH, getEditRoleMappingHref } from '../../management_urls'; import { DocumentationLinksService } from '../documentation_links'; import { RoleMappingsAPIClient } from '../role_mappings_api_client'; import { RoleTableDisplay } from '../../role_table_display'; import { RolesAPIClient } from '../../roles'; import { EnabledBadge, DisabledBadge } from '../../badges'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { rolesAPIClient: PublicMethodsOf; roleMappingsAPI: PublicMethodsOf; notifications: NotificationsStart; docLinks: DocumentationLinksService; + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -119,7 +122,7 @@ export class RoleMappingsGridPage extends Component { if (loadState === 'finished' && roleMappings && roleMappings.length === 0) { return ( - + ); } @@ -160,7 +163,10 @@ export class RoleMappingsGridPage extends Component {
    - + { id="xpack.security.management.roleMappings.roleMappingTableLoadingMessage" defaultMessage="Loading role mappings…" /> - ) : ( - undefined - ); + ) : undefined; const sorting = { sort: { @@ -222,7 +226,7 @@ export class RoleMappingsGridPage extends Component { roleMappingsAPI={this.props.roleMappingsAPI} notifications={this.props.notifications} > - {deleteRoleMappingsPrompt => { + {(deleteRoleMappingsPrompt) => { return ( deleteRoleMappingsPrompt(selectedItems, this.onRoleMappingsDeleted)} @@ -240,9 +244,7 @@ export class RoleMappingsGridPage extends Component { ); }} - ) : ( - undefined - ), + ) : undefined, toolsRight: ( { render: (roleMappingName: string) => { return ( {roleMappingName} @@ -325,9 +327,15 @@ export class RoleMappingsGridPage extends Component { } const roleLinks = assignedRoleNames.map((rolename, index) => { const role: Role | string = - this.state.roles?.find(r => r.name === rolename) ?? rolename; + this.state.roles?.find((r) => r.name === rolename) ?? rolename; - return ; + return ( + + ); }); return
    {roleLinks}
    ; }, @@ -371,7 +379,10 @@ export class RoleMappingsGridPage extends Component { iconType="pencil" color="primary" data-test-subj={`editRoleMappingButton-${record.name}`} - href={getEditRoleMappingHref(record.name)} + {...reactRouterNavigate( + this.props.history, + getEditRoleMappingHref(record.name) + )} /> ); @@ -386,7 +397,7 @@ export class RoleMappingsGridPage extends Component { roleMappingsAPI={this.props.roleMappingsAPI} notifications={this.props.notifications} > - {deleteRoleMappingPrompt => { + {(deleteRoleMappingPrompt) => { return ( ({ EditRoleMappingPage: (props: any) => `Role Mapping Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; import { roleMappingsManagementApp } from './role_mappings_management_app'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; -import { coreMock } from '../../../../../../src/core/public/mocks'; - -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); const unmount = await roleMappingsManagementApp .create({ getStartServices: coreMock.createSetup().getStartServices as any }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -44,16 +49,13 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/role_mappings'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Role Mappings' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Role Mappings' }]); expect(container).toMatchInlineSnapshot(`
    - Role Mappings Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mappings Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
    `); @@ -63,19 +65,16 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `create role mapping` page', async () => { - const basePath = '/some-base-path/role_mappings'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, + { href: `/`, text: 'Role Mappings' }, { text: 'Create' }, ]); expect(container).toMatchInlineSnapshot(`
    - Role Mapping Edit Page: {"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mapping Edit Page: {"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
    `); @@ -85,20 +84,18 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `edit role mapping` page', async () => { - const basePath = '/some-base-path/role_mappings'; const roleMappingName = 'someRoleMappingName'; - window.location.hash = `${basePath}/edit/${roleMappingName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${roleMappingName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, - { href: `#/some-base-path/role_mappings/edit/${roleMappingName}`, text: roleMappingName }, + { href: `/`, text: 'Role Mappings' }, + { href: `/edit/${roleMappingName}`, text: roleMappingName }, ]); expect(container).toMatchInlineSnapshot(`
    - Role Mapping Edit Page: {"name":"someRoleMappingName","roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mapping Edit Page: {"name":"someRoleMappingName","roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someRoleMappingName","search":"","hash":""}}}
    `); @@ -108,18 +105,15 @@ describe('roleMappingsManagementApp', () => { }); it('mount() properly encodes role mapping name in `edit role mapping` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/role_mappings'; const roleMappingName = 'some 安全性 role mapping'; - window.location.hash = `${basePath}/edit/${roleMappingName}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${roleMappingName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, + { href: `/`, text: 'Role Mappings' }, { - href: - '#/some-base-path/role_mappings/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', text: roleMappingName, }, ]); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx index ffb6d6d98f180..bca3a070e64f9 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -26,13 +26,14 @@ export const roleMappingsManagementApp = Object.freeze({ title: i18n.translate('xpack.security.management.roleMappingsTitle', { defaultMessage: 'Role Mappings', }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { + const [coreStart] = await getStartServices(); const roleMappingsBreadcrumbs = [ { text: i18n.translate('xpack.security.roleMapping.breadcrumb', { defaultMessage: 'Role Mappings', }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -60,6 +61,8 @@ export const roleMappingsManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} roleMappingsAPI={roleMappingsAPIClient} docLinks={dockLinksService} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); }; @@ -70,7 +73,7 @@ export const roleMappingsManagementApp = Object.freeze({ setBreadcrumbs([ ...roleMappingsBreadcrumbs, name - ? { text: name, href: `#${basePath}/edit/${encodeURIComponent(name)}` } + ? { text: name, href: `/edit/${encodeURIComponent(name)}` } : { text: i18n.translate('xpack.security.roleMappings.createBreadcrumb', { defaultMessage: 'Create', @@ -85,15 +88,16 @@ export const roleMappingsManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} notifications={notifications} docLinks={dockLinksService} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx b/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx index 28978f0090011..c1349eba9cddc 100644 --- a/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx +++ b/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx @@ -6,19 +6,20 @@ import React from 'react'; import { EuiLink, EuiToolTip, EuiIcon } from '@elastic/eui'; +import { ApplicationStart } from 'kibana/public'; import { Role, isRoleDeprecated, getExtendedRoleDeprecationNotice } from '../../../common/model'; -import { getEditRoleHref } from '../management_urls'; interface Props { role: Role | string; + navigateToApp: ApplicationStart['navigateToApp']; } -export const RoleTableDisplay = ({ role }: Props) => { +export const RoleTableDisplay = ({ role, navigateToApp }: Props) => { let content; - let href; + let path: string; if (typeof role === 'string') { content =
    {role}
    ; - href = getEditRoleHref(role); + path = `security/roles/edit/${encodeURIComponent(role)}`; } else if (isRoleDeprecated(role)) { content = ( {
    ); - href = getEditRoleHref(role.name); + path = `security/roles/edit/${encodeURIComponent(role.name)}`; } else { content =
    {role.name}
    ; - href = getEditRoleHref(role.name); + path = `security/roles/edit/${encodeURIComponent(role.name)}`; } - return {content}; + + return navigateToApp('management', { path })}>{content}; }; diff --git a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts index 98110a83103aa..6821c163d817d 100644 --- a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts +++ b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts @@ -11,13 +11,15 @@ import { Feature } from '../../../../../features/public'; import { KibanaPrivileges } from '../model'; import { SecurityLicenseFeatures } from '../../..'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { featuresPluginMock } from '../../../../../features/server/mocks'; + export const createRawKibanaPrivileges = ( features: Feature[], { allowSubFeaturePrivileges = true } = {} ) => { - const featuresService = { - getFeatures: () => features, - }; + const featuresService = featuresPluginMock.createSetup(); + featuresService.getFeatures.mockReturnValue(features); const licensingService = { getFeatures: () => ({ allowSubFeaturePrivileges } as SecurityLicenseFeatures), diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index f1ee681331005..43387d913e6fc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -8,7 +8,7 @@ import { ReactWrapper } from 'enzyme'; import React from 'react'; import { act } from '@testing-library/react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { Capabilities } from 'src/core/public'; +import { Capabilities, ScopedHistory } from 'src/core/public'; import { Feature } from '../../../../../features/public'; import { Role } from '../../../../common/model'; import { DocumentationLinksService } from '../documentation_links'; @@ -16,7 +16,7 @@ import { EditRolePage } from './edit_role_page'; import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section'; import { TransformErrorSection } from './privileges/kibana/transform_error_section'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { licenseMock } from '../../../../common/licensing/index.mock'; import { userAPIClientMock } from '../../users/index.mock'; @@ -163,7 +163,12 @@ function getProps({ const { http, docLinks, notifications } = coreMock.createStart(); http.get.mockImplementation(async (path: any) => { if (path === '/api/spaces/space') { - return buildSpaces(); + if (spacesEnabled) { + return buildSpaces(); + } + + const notFoundError = { response: { status: 404 } }; + throw notFoundError; } }); @@ -181,8 +186,8 @@ function getProps({ notifications, docLinks: new DocumentationLinksService(docLinks), fatalErrors, - spacesEnabled, uiCapabilities: buildUICapabilities(canManageSpaces), + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, }; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index f0d5abf89dd2e..15888733ec424 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -26,6 +26,7 @@ import React, { Fragment, FunctionComponent, HTMLProps, + useCallback, useEffect, useRef, useState, @@ -37,6 +38,7 @@ import { IHttpFetchError, NotificationsStart, } from 'src/core/public'; +import { ScopedHistory } from 'kibana/public'; import { FeaturesPluginStart } from '../../../../../features/public'; import { Feature } from '../../../../../features/common'; import { IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; @@ -53,7 +55,6 @@ import { RoleIndexPrivilege, getExtendedRoleDeprecationNotice, } from '../../../../common/model'; -import { ROLES_PATH } from '../../management_urls'; import { RoleValidationResult, RoleValidator } from './validate_role'; import { DeleteRoleButton } from './delete_role_button'; import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges'; @@ -65,6 +66,7 @@ import { IndicesAPIClient } from '../indices_api_client'; import { RolesAPIClient } from '../roles_api_client'; import { PrivilegesAPIClient } from '../privileges_api_client'; import { KibanaPrivileges } from '../model'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { action: 'edit' | 'clone'; @@ -78,10 +80,10 @@ interface Props { docLinks: DocumentationLinksService; http: HttpStart; license: SecurityLicense; - spacesEnabled: boolean; uiCapabilities: Capabilities; notifications: NotificationsStart; fatalErrors: FatalErrorsSetup; + history: ScopedHistory; } function useRunAsUsers( @@ -91,8 +93,8 @@ function useRunAsUsers( const [userNames, setUserNames] = useState(null); useEffect(() => { userAPIClient.getUsers().then( - users => setUserNames(users.map(user => user.username)), - err => fatalErrors.add(err) + (users) => setUserNames(users.map((user) => user.username)), + (err) => fatalErrors.add(err) ); }, [fatalErrors, userAPIClient]); @@ -143,7 +145,7 @@ function usePrivileges( ]).then( ([kibanaPrivileges, builtInESPrivileges]) => setPrivileges([kibanaPrivileges, builtInESPrivileges]), - err => fatalErrors.add(err) + (err) => fatalErrors.add(err) ); }, [privilegesAPIClient, fatalErrors]); @@ -156,6 +158,7 @@ function useRole( notifications: NotificationsStart, license: SecurityLicense, action: string, + backToRoleList: () => void, roleName?: string ) { const [role, setRole] = useState(null); @@ -170,7 +173,7 @@ function useRole( } as Role); rolePromise - .then(fetchedRole => { + .then((fetchedRole) => { if (action === 'clone' && checkIfRoleReserved(fetchedRole)) { backToRoleList(); return; @@ -216,19 +219,26 @@ function useRole( fatalErrors.add(err); } }); - }, [roleName, action, fatalErrors, rolesAPIClient, notifications, license]); + }, [roleName, action, fatalErrors, rolesAPIClient, notifications, license, backToRoleList]); return [role, setRole] as [Role | null, typeof setRole]; } -function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup, spacesEnabled: boolean) { - const [spaces, setSpaces] = useState(null); +function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup) { + const [spaces, setSpaces] = useState<{ enabled: boolean; list: Space[] } | null>(null); useEffect(() => { - (spacesEnabled ? http.get('/api/spaces/space') : Promise.resolve([])).then( - fetchedSpaces => setSpaces(fetchedSpaces), - err => fatalErrors.add(err) + http.get('/api/spaces/space').then( + (fetchedSpaces) => setSpaces({ enabled: true, list: fetchedSpaces }), + (err: IHttpFetchError) => { + // Spaces plugin can be disabled and hence this endpoint can be unavailable. + if (err.response?.status === 404) { + setSpaces({ enabled: false, list: [] }); + } else { + fatalErrors.add(err); + } + } ); - }, [http, fatalErrors, spacesEnabled]); + }, [http, fatalErrors]); return spaces; } @@ -255,7 +265,7 @@ function useFeatures( fatalErrors.add(err); }) - .then(retrievedFeatures => { + .then((retrievedFeatures) => { setFeatures(retrievedFeatures); }); }, [fatalErrors, getFeatures]); @@ -263,10 +273,6 @@ function useFeatures( return features; } -function backToRoleList() { - window.location.hash = ROLES_PATH; -} - export const EditRolePage: FunctionComponent = ({ userAPIClient, indexPatterns, @@ -278,12 +284,14 @@ export const EditRolePage: FunctionComponent = ({ roleName, action, fatalErrors, - spacesEnabled, license, docLinks, uiCapabilities, notifications, + history, }) => { + const backToRoleList = useCallback(() => history.push('/'), [history]); + // We should keep the same mutable instance of Validator for every re-render since we'll // eventually enable validation after the first time user tries to save a role. const { current: validator } = useRef(new RoleValidator({ shouldValidate: false })); @@ -292,7 +300,7 @@ export const EditRolePage: FunctionComponent = ({ const runAsUsers = useRunAsUsers(userAPIClient, fatalErrors); const indexPatternsTitles = useIndexPatternsTitles(indexPatterns, fatalErrors, notifications); const privileges = usePrivileges(privilegesAPIClient, fatalErrors); - const spaces = useSpaces(http, fatalErrors, spacesEnabled); + const spaces = useSpaces(http, fatalErrors); const features = useFeatures(getFeatures, fatalErrors); const [role, setRole] = useRole( rolesAPIClient, @@ -300,6 +308,7 @@ export const EditRolePage: FunctionComponent = ({ notifications, license, action, + backToRoleList, roleName ); @@ -380,9 +389,7 @@ export const EditRolePage: FunctionComponent = ({ id="xpack.security.management.editRole.roleNameFormRowHelpText" defaultMessage="A role's name cannot be changed once it has been created." /> - ) : ( - undefined - ) + ) : undefined } {...validator.validateRoleName(role)} > @@ -432,8 +439,8 @@ export const EditRolePage: FunctionComponent = ({ = ({ const getReturnToRoleListButton = () => { return ( - + = ({ setFormError(null); try { - await rolesAPIClient.saveRole({ role, spacesEnabled }); + await rolesAPIClient.saveRole({ role, spacesEnabled: spaces.enabled }); } catch (error) { notifications.toasts.addDanger(get(error, 'data.message')); return; @@ -552,7 +559,7 @@ export const EditRolePage: FunctionComponent = ({ backToRoleList(); }; - const description = spacesEnabled ? ( + const description = spaces.enabled ? ( = ({
    {getFormTitle()} - - {description} - {isRoleReserved && ( @@ -586,7 +590,6 @@ export const EditRolePage: FunctionComponent = ({ )} - {isDeprecatedRole && ( @@ -597,17 +600,11 @@ export const EditRolePage: FunctionComponent = ({ /> )} - - {getRoleName()} - {getElasticsearchPrivileges()} - {getKibanaPrivileges()} - - {getFormButtons()}
    diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.tsx index 54be04ade370e..0041e63da1fba 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.tsx @@ -24,11 +24,11 @@ export class ClusterPrivileges extends Component { public buildComboBox = (items: string[]) => { const role = this.props.role; - const options = items.map(i => ({ + const options = items.map((i) => ({ label: i, })); - const selectedOptions = (role.elasticsearch.cluster || []).map(k => ({ label: k })); + const selectedOptions = (role.elasticsearch.cluster || []).map((k) => ({ label: k })); return ( diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx index 96249ccf3ff87..46e0183b2f38c 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx @@ -133,12 +133,12 @@ export class ElasticsearchPrivileges extends Component { ) : undefined } - options={this.props.runAsUsers.map(username => ({ + options={this.props.runAsUsers.map((username) => ({ id: username, label: username, isGroupLabelOption: false, }))} - selectedOptions={this.props.role.elasticsearch.run_as.map(u => ({ label: u }))} + selectedOptions={this.props.role.elasticsearch.run_as.map((u) => ({ label: u }))} onCreateOption={this.onCreateRunAsOption} onChange={this.onRunAsUserChange} isDisabled={!editable} diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx index 32e8a558ecbc6..ed5ade0d23bf3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx @@ -198,9 +198,7 @@ export class IndexPrivilegeForm extends Component { id="xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowHelpText" defaultMessage="If no fields are granted, then users assigned to this role will not be able to see any data for this index." /> - ) : ( - undefined - ) + ) : undefined } > diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.tsx index 1157640ca57a7..e4a2bbd260deb 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.tsx @@ -147,14 +147,14 @@ export class IndexPrivileges extends Component { return; } - const patterns = privileges.map(index => index.names.join(',')); + const patterns = privileges.map((index) => index.names.join(',')); const cachedPatterns = Object.keys(this.state.availableFields); const patternsToFetch = _.difference(patterns, cachedPatterns); const fetchRequests = patternsToFetch.map(this.loadFieldsForPattern); - Promise.all(fetchRequests).then(response => { + Promise.all(fetchRequests).then((response) => { this.setState({ availableFields: { ...this.state.availableFields, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts index 440ed78f944ba..9df50b198bde0 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts @@ -19,7 +19,7 @@ import { SubFeatureForm } from '../sub_feature_form'; export function getDisplayedFeaturePrivileges(wrapper: ReactWrapper) { const allExpanderButtons = findTestSubject(wrapper, 'expandFeaturePrivilegeRow'); - allExpanderButtons.forEach(button => button.simulate('click')); + allExpanderButtons.forEach((button) => button.simulate('click')); // each expanded row renders its own `EuiTableRow`, so there are 2 rows // for each feature: one for the primary feature privilege, and one for the sub privilege form diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/change_all_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/change_all_privileges.tsx index 5d7b13acf79da..10aa59083dff6 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/change_all_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/change_all_privileges.tsx @@ -41,7 +41,7 @@ export class ChangeAllPrivilegesControl extends Component { ); - const items = this.props.privileges.map(privilege => { + const items = this.props.privileges.map((privilege) => { return ( { }; describe('FeatureTable', () => { - [true, false].forEach(canCustomizeSubFeaturePrivileges => { + [true, false].forEach((canCustomizeSubFeaturePrivileges) => { describe(`with sub feature privileges ${ canCustomizeSubFeaturePrivileges ? 'allowed' : 'disallowed' }`, () => { @@ -312,7 +312,7 @@ describe('FeatureTable', () => { canCustomizeSubFeaturePrivileges: true, }); - kibanaFeatures.forEach(feature => { + kibanaFeatures.forEach((feature) => { const rowExpander = findTestSubject(wrapper, `expandFeaturePrivilegeRow-${feature.id}`); if (!feature.subFeatures || feature.subFeatures.length === 0) { expect(rowExpander).toHaveLength(0); @@ -345,9 +345,7 @@ describe('FeatureTable', () => { expect(wrapper.find(FeatureTableExpandedRow)).toHaveLength(0); - findTestSubject(wrapper, 'expandFeaturePrivilegeRow') - .first() - .simulate('click'); + findTestSubject(wrapper, 'expandFeaturePrivilegeRow').first().simulate('click'); expect(wrapper.find(FeatureTableExpandedRow)).toHaveLength(1); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx index 4610da95e9649..0776f2af2ddd7 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx @@ -78,7 +78,7 @@ export class FeatureTable extends Component { return 0; }) - .map(feature => { + .map((feature) => { return { featureId: feature.id, feature, @@ -98,7 +98,7 @@ export class FeatureTable extends Component { ...acc, [featureId]: ( f.id === featureId)!} + feature={featurePrivileges.find((f) => f.id === featureId)!} privilegeIndex={this.props.privilegeIndex} onChange={this.props.onChange} privilegeCalculator={this.props.privilegeCalculator} @@ -208,7 +208,7 @@ export class FeatureTable extends Component { this.props.privilegeIndex ); - const options = primaryFeaturePrivileges.map(privilege => { + const options = primaryFeaturePrivileges.map((privilege) => { return { id: `${feature.id}_${privilege.id}`, label: privilege.name, @@ -268,7 +268,7 @@ export class FeatureTable extends Component { private toggleExpandedFeature = (featureId: string) => { if (this.state.expandedFeatures.includes(featureId)) { this.setState({ - expandedFeatures: this.state.expandedFeatures.filter(ef => ef !== featureId), + expandedFeatures: this.state.expandedFeatures.filter((ef) => ef !== featureId), }); } else { this.setState({ diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx index fb302c2269485..ca73c4d183f23 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx @@ -31,13 +31,13 @@ export const FeatureTableExpandedRow = ({ const [isCustomizing, setIsCustomizing] = useState(() => { return feature .getMinimalFeaturePrivileges() - .some(p => selectedFeaturePrivileges.includes(p.id)); + .some((p) => selectedFeaturePrivileges.includes(p.id)); }); useEffect(() => { const hasMinimalFeaturePrivilegeSelected = feature .getMinimalFeaturePrivileges() - .some(p => selectedFeaturePrivileges.includes(p.id)); + .some((p) => selectedFeaturePrivileges.includes(p.id)); if (!hasMinimalFeaturePrivilegeSelected && isCustomizing) { setIsCustomizing(false); @@ -75,7 +75,7 @@ export const FeatureTableExpandedRow = ({ } /> - {feature.getSubFeatures().map(subFeature => { + {feature.getSubFeatures().map((subFeature) => { return ( onChange(feature.id, updatedPrivileges)} + onChange={(updatedPrivileges) => onChange(feature.id, updatedPrivileges)} selectedFeaturePrivileges={selectedFeaturePrivileges} disabled={disabled || !isCustomizing} /> diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx index ba7eff601f4c1..ced90e24b9c72 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx @@ -27,7 +27,7 @@ const createRole = (kibana: Role['kibana'] = []): Role => { }; const featureId = 'with_sub_features'; -const subFeature = kibanaFeatures.find(kf => kf.id === featureId)!.subFeatures[0]; +const subFeature = kibanaFeatures.find((kf) => kf.id === featureId)!.subFeatures[0]; const securedSubFeature = new SecuredSubFeature(subFeature.toRaw()); describe('SubFeatureForm', () => { @@ -57,8 +57,8 @@ describe('SubFeatureForm', () => { const checkboxes = wrapper.find(EuiCheckbox); const buttonGroups = wrapper.find(EuiButtonGroup); - expect(checkboxes.everyWhere(checkbox => checkbox.props().disabled === true)).toBe(true); - expect(buttonGroups.everyWhere(checkbox => checkbox.props().isDisabled === true)).toBe(true); + expect(checkboxes.everyWhere((checkbox) => checkbox.props().disabled === true)).toBe(true); + expect(buttonGroups.everyWhere((checkbox) => checkbox.props().isDisabled === true)).toBe(true); }); it('fires onChange when an independent privilege is selected', () => { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx index d4b6721ddad05..5432a50c1f0df 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx @@ -64,12 +64,14 @@ export const SubFeatureForm = (props: Props) => { id={`${props.featureId}_${privilege.id}`} label={privilege.name} data-test-subj="independentSubFeaturePrivilegeControl" - onChange={e => { + onChange={(e) => { const { checked } = e.target; if (checked) { props.onChange([...props.selectedFeaturePrivileges, privilege.id]); } else { - props.onChange(props.selectedFeaturePrivileges.filter(sp => sp !== privilege.id)); + props.onChange( + props.selectedFeaturePrivileges.filter((sp) => sp !== privilege.id) + ); } }} checked={isGranted} @@ -116,10 +118,10 @@ export const SubFeatureForm = (props: Props) => { options={options} idSelected={firstSelectedPrivilege?.id ?? NO_PRIVILEGE_VALUE} isDisabled={props.disabled} - onChange={selectedPrivilegeId => { + onChange={(selectedPrivilegeId) => { // Deselect all privileges which belong to this mutually-exclusive group const privilegesWithoutGroupEntries = props.selectedFeaturePrivileges.filter( - sp => !privilegeGroup.privileges.some(privilege => privilege.id === sp) + (sp) => !privilegeGroup.privileges.some((privilege) => privilege.id === sp) ); // fire on-change with the newly selected privilege if (selectedPrivilegeId === NO_PRIVILEGE_VALUE) { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx index 316818e4deed3..155e41baeba1e 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx @@ -41,12 +41,7 @@ describe('FeatureTableCell', () => { ); expect(wrapper.text()).toMatchInlineSnapshot(`"Test Feature "`); - expect( - wrapper - .find(EuiIcon) - .first() - .props() - ).toMatchObject({ + expect(wrapper.find(EuiIcon).first().props()).toMatchObject({ type: feature.icon, }); expect(wrapper.find(EuiIconTip).props().content).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.test.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.test.ts index edf2af918fd04..53bebf6f5bb93 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.test.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.test.ts @@ -515,10 +515,10 @@ describe('PrivilegeFormCalculator', () => { ]); const feature = kibanaPrivileges.getSecuredFeature('with_sub_features'); - const coolSubFeature = feature.getSubFeatures().find(sf => sf.name === 'Cool Sub Feature')!; + const coolSubFeature = feature.getSubFeatures().find((sf) => sf.name === 'Cool Sub Feature')!; const subFeatureGroup = coolSubFeature .getPrivilegeGroups() - .find(pg => pg.groupType === 'mutually_exclusive')!; + .find((pg) => pg.groupType === 'mutually_exclusive')!; const calculator = new PrivilegeFormCalculator(kibanaPrivileges, role); expect( @@ -543,10 +543,10 @@ describe('PrivilegeFormCalculator', () => { ]); const feature = kibanaPrivileges.getSecuredFeature('with_sub_features'); - const coolSubFeature = feature.getSubFeatures().find(sf => sf.name === 'Cool Sub Feature')!; + const coolSubFeature = feature.getSubFeatures().find((sf) => sf.name === 'Cool Sub Feature')!; const subFeatureGroup = coolSubFeature .getPrivilegeGroups() - .find(pg => pg.groupType === 'mutually_exclusive')!; + .find((pg) => pg.groupType === 'mutually_exclusive')!; const calculator = new PrivilegeFormCalculator(kibanaPrivileges, role); expect( @@ -573,10 +573,10 @@ describe('PrivilegeFormCalculator', () => { ]); const feature = kibanaPrivileges.getSecuredFeature('with_sub_features'); - const coolSubFeature = feature.getSubFeatures().find(sf => sf.name === 'Cool Sub Feature')!; + const coolSubFeature = feature.getSubFeatures().find((sf) => sf.name === 'Cool Sub Feature')!; const subFeatureGroup = coolSubFeature .getPrivilegeGroups() - .find(pg => pg.groupType === 'mutually_exclusive')!; + .find((pg) => pg.groupType === 'mutually_exclusive')!; const calculator = new PrivilegeFormCalculator(kibanaPrivileges, role); expect( diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.ts index 8cff37f4bd4b0..87aabcb6fbf63 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_form_calculator/privilege_form_calculator.ts @@ -27,7 +27,7 @@ export class PrivilegeFormCalculator { const entry = this.role.kibana[privilegeIndex]; const basePrivileges = this.kibanaPrivileges.getBasePrivileges(entry); - return basePrivileges.find(bp => entry.base.includes(bp.id)); + return basePrivileges.find((bp) => entry.base.includes(bp.id)); } /** @@ -67,7 +67,7 @@ export class PrivilegeFormCalculator { this.role.kibana[privilegeIndex], ]); - return feature.getSubFeaturePrivileges().some(sfp => { + return feature.getSubFeaturePrivileges().some((sfp) => { const isGranted = formPrivileges.grantsPrivilege(sfp); const isGrantedByDisplayedPrimary = displayedPrimary?.grantsPrivilege(sfp) ?? isGranted; @@ -90,7 +90,7 @@ export class PrivilegeFormCalculator { return feature .getPrimaryFeaturePrivileges({ includeMinimalFeaturePrivileges: true }) - .find(fp => { + .find((fp) => { return selectedFeaturePrivileges.includes(fp.id) || basePrivilege?.grantsPrivilege(fp); }); } @@ -112,7 +112,7 @@ export class PrivilegeFormCalculator { const feature = this.kibanaPrivileges.getSecuredFeature(featureId); const subFeaturePrivilege = feature .getSubFeaturePrivileges() - .find(ap => ap.id === privilegeId)!; + .find((ap) => ap.id === privilegeId)!; const assignedPrivileges = this.kibanaPrivileges.createCollectionFromRoleKibanaPrivileges([ kibanaPrivilege, @@ -138,7 +138,7 @@ export class PrivilegeFormCalculator { kibanaPrivilege, ]); - return subFeatureGroup.privileges.find(p => { + return subFeatureGroup.privileges.find((p) => { return assignedPrivileges.grantsPrivilege(p); }); } @@ -155,7 +155,7 @@ export class PrivilegeFormCalculator { return feature .getPrimaryFeaturePrivileges({ includeMinimalFeaturePrivileges: true }) - .some(apfp => selectedFeaturePrivileges.includes(apfp.id)); + .some((apfp) => selectedFeaturePrivileges.includes(apfp.id)); } /** @@ -184,8 +184,8 @@ export class PrivilegeFormCalculator { const startingPrivileges = feature .getSubFeaturePrivileges() - .filter(ap => primary.grantsPrivilege(ap)) - .map(p => p.id); + .filter((ap) => primary.grantsPrivilege(ap)) + .map((p) => p.id); nextPrivileges.push(primary.getMinimalPrivilegeId(), ...startingPrivileges); } else { @@ -216,14 +216,14 @@ export class PrivilegeFormCalculator { const hasAssignedBasePrivileges = this.kibanaPrivileges .getBasePrivileges(entry) - .some(base => entry.base.includes(base.id)); + .some((base) => entry.base.includes(base.id)); const featuresWithDirectlyAssignedPrivileges = this.kibanaPrivileges .getSecuredFeatures() - .filter(feature => + .filter((feature) => feature .getAllPrivileges() - .some(privilege => entry.feature[feature.id]?.includes(privilege.id)) + .some((privilege) => entry.feature[feature.id]?.includes(privilege.id)) ); const hasSupersededBasePrivileges = @@ -231,15 +231,15 @@ export class PrivilegeFormCalculator { this.kibanaPrivileges .getBasePrivileges(entry) .some( - privilege => + (privilege) => globalPrivileges.grantsPrivilege(privilege) && !formPrivileges.grantsPrivilege(privilege) ); - const hasSupersededFeaturePrivileges = featuresWithDirectlyAssignedPrivileges.some(feature => + const hasSupersededFeaturePrivileges = featuresWithDirectlyAssignedPrivileges.some((feature) => feature .getAllPrivileges() - .some(fp => globalPrivileges.grantsPrivilege(fp) && !formPrivileges.grantsPrivilege(fp)) + .some((fp) => globalPrivileges.grantsPrivilege(fp) && !formPrivileges.grantsPrivilege(fp)) ); return hasSupersededBasePrivileges || hasSupersededFeaturePrivileges; @@ -270,12 +270,12 @@ export class PrivilegeFormCalculator { const selectedFeaturePrivileges = this.getSelectedFeaturePrivileges(featureId, privilegeIndex); - return feature.getPrimaryFeaturePrivileges().find(fp => { + return feature.getPrimaryFeaturePrivileges().find((fp) => { const correspondingMinimalPrivilegeId = fp.getMinimalPrivilegeId(); const correspendingMinimalPrivilege = feature .getMinimalFeaturePrivileges() - .find(mp => mp.id === correspondingMinimalPrivilegeId)!; + .find((mp) => mp.id === correspondingMinimalPrivilegeId)!; // There are two cases where the minimal privileges aren't available: // 1. The feature has no registered sub-features @@ -298,6 +298,6 @@ export class PrivilegeFormCalculator { } private locateGlobalPrivilege(role: Role) { - return role.kibana.find(entry => isGlobalPrivilegeDefinition(entry)); + return role.kibana.find((entry) => isGlobalPrivilegeDefinition(entry)); } } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts index 63b38b6967575..b920e7b696375 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts @@ -32,7 +32,7 @@ export function getDisplayedFeaturePrivileges( role: Role ): DisplayedFeaturePrivileges { const allExpanderButtons = findTestSubject(wrapper, 'expandPrivilegeSummaryRow'); - allExpanderButtons.forEach(button => button.simulate('click')); + allExpanderButtons.forEach((button) => button.simulate('click')); // each expanded row renders its own `EuiTableRow`, so there are 2 rows // for each feature: one for the primary feature privilege, and one for the sub privilege form @@ -81,7 +81,7 @@ function getDisplayedSubFeaturePrivileges( displayedFeatures[feature.id] = displayedFeatures[feature.id] ?? {}; - subFeatureEntries.forEach(subFeatureEntry => { + subFeatureEntries.forEach((subFeatureEntry) => { const subFeatureName = findTestSubject(subFeatureEntry, 'subFeatureName').text(); const entryElements = findTestSubject(subFeatureEntry as ReactWrapper, 'entry', '|='); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.ts index 27ed8c443045a..5ad0f6752b1cd 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.ts @@ -32,7 +32,7 @@ export class PrivilegeSummaryCalculator { const effectiveSubPrivileges = feature .getSubFeaturePrivileges() - .filter(ap => assignedPrivileges.grantsPrivilege(ap)); + .filter((ap) => assignedPrivileges.grantsPrivilege(ap)); const hasCustomizedSubFeaturePrivileges = this.hasCustomizedSubFeaturePrivileges( feature, @@ -45,7 +45,7 @@ export class PrivilegeSummaryCalculator { [feature.id]: { primary: displayedPrimaryFeaturePrivilege, hasCustomizedSubFeaturePrivileges, - subFeature: effectiveSubPrivileges.map(p => p.id), + subFeature: effectiveSubPrivileges.map((p) => p.id), }, }; }, {} as EffectiveFeaturePrivileges); @@ -58,7 +58,7 @@ export class PrivilegeSummaryCalculator { ) { const formPrivileges = this.collectAssignedPrivileges(entry); - return feature.getSubFeaturePrivileges().some(sfp => { + return feature.getSubFeaturePrivileges().some((sfp) => { const isGranted = formPrivileges.grantsPrivilege(sfp); const isGrantedByDisplayedPrimary = displayedPrimaryFeaturePrivilege?.grantsPrivilege(sfp) ?? isGranted; @@ -77,11 +77,11 @@ export class PrivilegeSummaryCalculator { const hasMinimalPrivileges = feature.subFeatures.length > 0; - const effectivePrivilege = primaryFeaturePrivileges.find(pfp => { + const effectivePrivilege = primaryFeaturePrivileges.find((pfp) => { const isPrimaryGranted = assignedPrivileges.grantsPrivilege(pfp); if (!isPrimaryGranted && hasMinimalPrivileges) { const correspondingMinimal = minimalPrimaryFeaturePrivileges.find( - mpfp => mpfp.id === pfp.getMinimalPrivilegeId() + (mpfp) => mpfp.id === pfp.getMinimalPrivilegeId() )!; return assignedPrivileges.grantsPrivilege(correspondingMinimal); @@ -104,6 +104,6 @@ export class PrivilegeSummaryCalculator { } private locateGlobalPrivilege(role: Role) { - return role.kibana.find(entry => isGlobalPrivilegeDefinition(entry)); + return role.kibana.find((entry) => isGlobalPrivilegeDefinition(entry)); } } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_expanded_row.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_expanded_row.tsx index 3283f7a58a27c..d7483e840ec7e 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_expanded_row.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_expanded_row.tsx @@ -18,7 +18,7 @@ interface Props { export const PrivilegeSummaryExpandedRow = (props: Props) => { return ( - {props.feature.getSubFeatures().map(subFeature => { + {props.feature.getSubFeatures().map((subFeature) => { return ( @@ -107,7 +107,7 @@ export const PrivilegeSummaryExpandedRow = (props: Props) => { privilegeGroup: SubFeaturePrivilegeGroup, index: number ) { - const firstSelectedPrivilege = privilegeGroup.privileges.find(p => + const firstSelectedPrivilege = privilegeGroup.privileges.find((p) => effectiveSubFeaturePrivileges.includes(p.id) )?.name; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx index 0498f099b536b..502868a509f91 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx @@ -83,7 +83,7 @@ const expectNoPrivileges = (displayedPrivileges: any, expectSubFeatures: boolean }; describe('PrivilegeSummaryTable', () => { - [true, false].forEach(allowSubFeaturePrivileges => { + [true, false].forEach((allowSubFeaturePrivileges) => { describe(`when sub feature privileges are ${ allowSubFeaturePrivileges ? 'allowed' : 'disallowed' }`, () => { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.tsx index e04ca36b6d193..4b5169de3dfc3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.tsx @@ -43,7 +43,7 @@ export const PrivilegeSummaryTable = (props: Props) => { const toggleExpandedFeature = (featureId: string) => { if (expandedFeatures.includes(featureId)) { - setExpandedFeatures(expandedFeatures.filter(ef => ef !== featureId)); + setExpandedFeatures(expandedFeatures.filter((ef) => ef !== featureId)); } else { setExpandedFeatures([...expandedFeatures, featureId]); } @@ -88,7 +88,7 @@ export const PrivilegeSummaryTable = (props: Props) => { } return 0; }); - const privilegeColumns = rawKibanaPrivileges.map(entry => { + const privilegeColumns = rawKibanaPrivileges.map((entry) => { const key = getColumnKey(entry); return { name: , @@ -140,7 +140,7 @@ export const PrivilegeSummaryTable = (props: Props) => { }; }, {} as Record); - const items = props.kibanaPrivileges.getSecuredFeatures().map(feature => { + const items = props.kibanaPrivileges.getSecuredFeatures().map((feature) => { return { feature, featureId: feature.id, @@ -153,7 +153,7 @@ export const PrivilegeSummaryTable = (props: Props) => { columns={columns} items={items} itemId="featureId" - rowProps={record => { + rowProps={(record) => { return { 'data-test-subj': `summaryTableRow-${record.featureId}`, }; @@ -164,7 +164,7 @@ export const PrivilegeSummaryTable = (props: Props) => { [featureId]: ( p[featureId])} + effectiveFeaturePrivileges={Object.values(privileges).map((p) => p[featureId])} /> ), }; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.tsx index 8ed9bb449b595..24ac0022b12af 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.tsx @@ -20,9 +20,9 @@ const SPACES_DISPLAY_COUNT = 4; export const SpaceColumnHeader = (props: Props) => { const isGlobal = isGlobalPrivilegeDefinition(props.entry); - const entrySpaces = props.entry.spaces.map(spaceId => { + const entrySpaces = props.entry.spaces.map((spaceId) => { return ( - props.spaces.find(s => s.id === spaceId) ?? { + props.spaces.find((s) => s.id === spaceId) ?? { id: spaceId, name: spaceId, disabledFeatures: [], @@ -31,7 +31,7 @@ export const SpaceColumnHeader = (props: Props) => { }); return (
    - {entrySpaces.slice(0, SPACES_DISPLAY_COUNT).map(space => { + {entrySpaces.slice(0, SPACES_DISPLAY_COUNT).map((space) => { return ( {' '} @@ -43,7 +43,7 @@ export const SpaceColumnHeader = (props: Props) => { />
    s.id !== '*')} + spaces={props.spaces.filter((s) => s.id !== '*')} buttonText={i18n.translate( 'xpack.security.management.editRole.spacePrivilegeMatrix.showAllSpacesLink', { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx index bda0227372c09..493ae290cac5e 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx @@ -31,7 +31,7 @@ export class PrivilegeSelector extends Component { } options.push( - ...availablePrivileges.map(p => ({ + ...availablePrivileges.map((p) => ({ value: p, text: p, })) diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx index d68d43e8089c7..34b4db89623bb 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -83,7 +83,7 @@ export class SimplePrivilegeSection extends Component { {reservedPrivileges.length > 0 ? ( ({ label: rp }))} + selectedOptions={reservedPrivileges.map((rp) => ({ label: rp }))} isDisabled /> ) : ( @@ -215,7 +215,7 @@ export class SimplePrivilegeSection extends Component { } onChange={this.onFeaturePrivilegeChange} onChangeAll={this.onChangeAllFeaturePrivileges} - privilegeIndex={this.props.role.kibana.findIndex(k => + privilegeIndex={this.props.role.kibana.findIndex((k) => isGlobalPrivilegeDefinition(k) )} canCustomizeSubFeaturePrivileges={this.props.canCustomizeSubFeaturePrivileges} @@ -248,7 +248,7 @@ export class SimplePrivilegeSection extends Component { if (privilege === NO_PRIVILEGE_VALUE) { // Remove global entry if no privilege value - role.kibana = role.kibana.filter(entry => !isGlobalPrivilegeDefinition(entry)); + role.kibana = role.kibana.filter((entry) => !isGlobalPrivilegeDefinition(entry)); } else if (privilege === CUSTOM_PRIVILEGE_VALUE) { // Remove base privilege if customizing feature privileges form.base = []; @@ -280,7 +280,7 @@ export class SimplePrivilegeSection extends Component { const form = this.locateGlobalPrivilege(role) || this.createGlobalPrivilegeEntry(role); if (privileges.length > 0) { - this.props.kibanaPrivileges.getSecuredFeatures().forEach(feature => { + this.props.kibanaPrivileges.getSecuredFeatures().forEach((feature) => { form.feature[feature.id] = [...privileges]; }); } else { @@ -292,7 +292,7 @@ export class SimplePrivilegeSection extends Component { private maybeRenderSpacePrivilegeWarning = () => { const kibanaPrivileges = this.props.role.kibana; const hasSpacePrivileges = kibanaPrivileges.some( - privilege => !isGlobalPrivilegeDefinition(privilege) + (privilege) => !isGlobalPrivilegeDefinition(privilege) ); if (hasSpacePrivileges) { @@ -306,12 +306,12 @@ export class SimplePrivilegeSection extends Component { }; private locateGlobalPrivilegeIndex = (role: Role) => { - return role.kibana.findIndex(privileges => isGlobalPrivilegeDefinition(privileges)); + return role.kibana.findIndex((privileges) => isGlobalPrivilegeDefinition(privileges)); }; private locateGlobalPrivilege = (role: Role) => { const spacePrivileges = role.kibana; - return spacePrivileges.find(privileges => isGlobalPrivilegeDefinition(privileges)); + return spacePrivileges.find((privileges) => isGlobalPrivilegeDefinition(privileges)); }; private createGlobalPrivilegeEntry(role: Role): RoleKibanaPrivilege { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.tsx index 93f1d9bba460d..7b5d8d8c1ed27 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.tsx @@ -40,7 +40,7 @@ function getDisplayValue(privilege: string | string[] | undefined) { if (isPrivilegeMissing) { displayValue = ; } else { - displayValue = privileges.map(p => _.capitalize(p)).join(', '); + displayValue = privileges.map((p) => _.capitalize(p)).join(', '); } return displayValue; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index 968730181fe10..32eed6c878016 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -218,10 +218,7 @@ describe('PrivilegeSpaceForm', () => { /> ); - wrapper - .find(SpaceSelector) - .props() - .onChange(['*']); + wrapper.find(SpaceSelector).props().onChange(['*']); wrapper.update(); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index 4e9e02bb531f1..c457401196ba1 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -452,8 +452,8 @@ export class PrivilegeSpaceForm extends Component { // remove any spaces that no longer exist if (!this.isDefiningGlobalPrivilege()) { - form.spaces = form.spaces.filter(spaceId => - this.props.spaces.find(space => space.id === spaceId) + form.spaces = form.spaces.filter((spaceId) => + this.props.spaces.find((space) => space.id === spaceId) ); } @@ -532,10 +532,10 @@ export class PrivilegeSpaceForm extends Component { if (privileges.length === 0) { entry.feature = {}; } else { - this.props.kibanaPrivileges.getSecuredFeatures().forEach(feature => { + this.props.kibanaPrivileges.getSecuredFeatures().forEach((feature) => { const nextFeaturePrivilege = feature .getPrimaryFeaturePrivileges() - .find(pfp => privileges.includes(pfp.id)); + .find((pfp) => privileges.includes(pfp.id)); if (nextFeaturePrivilege) { entry.feature[feature.id] = [nextFeaturePrivilege.id]; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx index b1c7cb4b631e6..5530d9964f8cd 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx @@ -176,7 +176,7 @@ const getTableFromComponent = ( return [ ...acc, { - spaces: spacesBadge.map(badge => badge.text().trim()), + spaces: spacesBadge.map((badge) => badge.text().trim()), privileges: { summary: privilegesDisplay.text().trim(), overridden: diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx index 30a275876fdc7..585c07c2e834f 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx @@ -78,8 +78,8 @@ export class PrivilegeSpaceTable extends Component { const rows: TableRow[] = spacePrivileges.map((spacePrivs, privilegeIndex) => { const spaces = spacePrivs.spaces.map( - spaceId => - displaySpaces.find(space => space.id === spaceId) || { + (spaceId) => + displaySpaces.find((space) => space.id === spaceId) || { id: spaceId, name: spaceId, disabledFeatures: [], @@ -122,7 +122,7 @@ export class PrivilegeSpaceTable extends Component { if (record.isGlobal) { button = ( s.id !== '*')} + spaces={this.props.displaySpaces.filter((s) => s.id !== '*')} buttonText={i18n.translate( 'xpack.security.management.editRole.spacePrivilegeTable.showAllSpacesLink', { @@ -238,7 +238,7 @@ export class PrivilegeSpaceTable extends Component { 'xpack.security.management.editRole.spacePrivilegeTable.editPrivilegesLabel', { defaultMessage: `Edit privileges for the following spaces: {spaceNames}.`, - values: { spaceNames: record.spaces.map(s => s.name).join(', ') }, + values: { spaceNames: record.spaces.map((s) => s.name).join(', ') }, } )} color={'primary'} @@ -256,7 +256,7 @@ export class PrivilegeSpaceTable extends Component { 'xpack.security.management.editRole.spacePrivilegeTable.deletePrivilegesLabel', { defaultMessage: `Delete privileges for the following spaces: {spaceNames}.`, - values: { spaceNames: record.spaces.map(s => s.name).join(', ') }, + values: { spaceNames: record.spaces.map((s) => s.name).join(', ') }, } )} color={'danger'} @@ -296,7 +296,7 @@ export class PrivilegeSpaceTable extends Component { private toggleExpandSpacesGroup = (privilegeIndex: number) => { if (this.state.expandedSpacesGroups.includes(privilegeIndex)) { this.setState({ - expandedSpacesGroups: this.state.expandedSpacesGroups.filter(i => i !== privilegeIndex), + expandedSpacesGroups: this.state.expandedSpacesGroups.filter((i) => i !== privilegeIndex), }); } else { this.setState({ @@ -312,7 +312,9 @@ export class PrivilegeSpaceTable extends Component { this.props.onChange(roleCopy); this.setState({ - expandedSpacesGroups: this.state.expandedSpacesGroups.filter(i => i !== item.privilegeIndex), + expandedSpacesGroups: this.state.expandedSpacesGroups.filter( + (i) => i !== item.privilegeIndex + ), }); }; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index 86b09e5332792..734f7b1826723 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -243,7 +243,7 @@ export class SpaceAwarePrivilegeSection extends Component { ); return this.getDisplaySpaces().filter( - displaySpace => !spacesToExclude.includes(displaySpace.id) + (displaySpace) => !spacesToExclude.includes(displaySpace.id) ); }; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx index 70790f785ad58..4344cd4b0784e 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx @@ -26,7 +26,7 @@ const spaceToOption = (space?: Space, currentSelection?: 'global' | 'spaces') => }; const spaceIdToOption = (spaces: Space[]) => (s: string) => - spaceToOption(spaces.find(space => space.id === s)); + spaceToOption(spaces.find((space) => space.id === s)); interface Props { spaces: Space[]; @@ -65,11 +65,11 @@ export class SpaceSelector extends Component { } private onChange = (selectedSpaces: EuiComboBoxOptionOption[]) => { - this.props.onChange(selectedSpaces.map(s => (s.id as string).split('spaceOption_')[1])); + this.props.onChange(selectedSpaces.map((s) => (s.id as string).split('spaceOption_')[1])); }; private getOptions = () => { - const options = this.props.spaces.map(space => + const options = this.props.spaces.map((space) => spaceToOption( space, this.props.selectedSpaceIds.includes('*') diff --git a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx index 20882864645d3..9c68a097a890e 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx @@ -62,7 +62,7 @@ describe('SpacesPopoverList', () => { }); it('renders a search box when there are 8 or more spaces', () => { - const lotsOfSpaces = [1, 2, 3, 4, 5, 6, 7, 8].map(num => ({ + const lotsOfSpaces = [1, 2, 3, 4, 5, 6, 7, 8].map((num) => ({ id: `space-${num}`, name: `Space ${num}`, disabledFeatures: [], @@ -100,10 +100,7 @@ describe('SpacesPopoverList', () => { expect(wrapper.find(EuiPopover).props().isOpen).toEqual(true); - wrapper - .find(EuiPopover) - .props() - .closePopover(); + wrapper.find(EuiPopover).props().closePopover(); wrapper.update(); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx index 63ee311f3155e..9c1a94d3d06f3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx @@ -104,7 +104,7 @@ export class SpacesPopoverList extends Component { let filteredSpaces = spaces; if (searchTerm) { - filteredSpaces = spaces.filter(space => { + filteredSpaces = spaces.filter((space) => { const { name, description = '' } = space; return ( name.toLowerCase().indexOf(searchTerm) >= 0 || diff --git a/x-pack/plugins/security/public/management/roles/edit_role/validate_role.test.ts b/x-pack/plugins/security/public/management/roles/edit_role/validate_role.test.ts index e9be52557bd7d..868674aec6f86 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/validate_role.test.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/validate_role.test.ts @@ -62,7 +62,7 @@ describe('validateRoleName', () => { }); const charList = `!#%^&*()+=[]{}\|';:"/,<>?`.split(''); - charList.forEach(element => { + charList.forEach((element) => { test(`it cannot support the "${element}" character`, () => { const role = { name: `role-${element}`, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/validate_role.ts b/x-pack/plugins/security/public/management/roles/edit_role/validate_role.ts index 02d3061b82b96..89b16b1467776 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/validate_role.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/validate_role.ts @@ -85,7 +85,7 @@ export class RoleValidator { const areIndicesValid = role.elasticsearch.indices - .map(indexPriv => this.validateIndexPrivilege(indexPriv)) + .map((indexPriv) => this.validateIndexPrivilege(indexPriv)) .find((result: RoleValidationResult) => result.isInvalid) == null; if (areIndicesValid) { @@ -171,7 +171,7 @@ export class RoleValidator { const privileges = role.kibana || []; - const arePrivilegesValid = privileges.every(assignedPrivilege => { + const arePrivilegesValid = privileges.every((assignedPrivilege) => { return assignedPrivilege.base.length > 0 || Object.keys(assignedPrivilege.feature).length > 0; }); diff --git a/x-pack/plugins/security/public/management/roles/model/kibana_privilege.ts b/x-pack/plugins/security/public/management/roles/model/kibana_privilege.ts index 7c21a4b5c0ee6..f5c85d3d92be2 100644 --- a/x-pack/plugins/security/public/management/roles/model/kibana_privilege.ts +++ b/x-pack/plugins/security/public/management/roles/model/kibana_privilege.ts @@ -18,7 +18,7 @@ export class KibanaPrivilege { } private checkActions(knownActions: string[], candidateActions: string[]) { - const missing = candidateActions.filter(action => !knownActions.includes(action)); + const missing = candidateActions.filter((action) => !knownActions.includes(action)); const hasAllRequested = knownActions.length > 0 && candidateActions.length > 0 && missing.length === 0; diff --git a/x-pack/plugins/security/public/management/roles/model/kibana_privileges.ts b/x-pack/plugins/security/public/management/roles/model/kibana_privileges.ts index d8d75e90847e3..a1617f71a0df8 100644 --- a/x-pack/plugins/security/public/management/roles/model/kibana_privileges.ts +++ b/x-pack/plugins/security/public/management/roles/model/kibana_privileges.ts @@ -19,7 +19,7 @@ function toBasePrivilege(entry: [string, string[]]): [string, KibanaPrivilege] { function recordsToBasePrivilegeMap( record: Record ): ReadonlyMap { - return new Map(Object.entries(record).map(entry => toBasePrivilege(entry))); + return new Map(Object.entries(record).map((entry) => toBasePrivilege(entry))); } export class KibanaPrivileges { @@ -33,7 +33,7 @@ export class KibanaPrivileges { this.global = recordsToBasePrivilegeMap(rawKibanaPrivileges.global); this.spaces = recordsToBasePrivilegeMap(rawKibanaPrivileges.space); this.feature = new Map( - features.map(feature => { + features.map((feature) => { const rawPrivs = rawKibanaPrivileges.features[feature.id]; return [feature.id, new SecuredFeature(feature.toRaw(), rawPrivs)]; }) @@ -60,7 +60,7 @@ export class KibanaPrivileges { assignedPrivileges.includes(privilege.id); const privileges: KibanaPrivilege[] = roleKibanaPrivileges - .map(entry => { + .map((entry) => { const assignedBasePrivileges = this.getBasePrivileges(entry).filter( filterAssigned(entry.base) ); diff --git a/x-pack/plugins/security/public/management/roles/model/privilege_collection.ts b/x-pack/plugins/security/public/management/roles/model/privilege_collection.ts index cbbd22857666e..d023f18d39279 100644 --- a/x-pack/plugins/security/public/management/roles/model/privilege_collection.ts +++ b/x-pack/plugins/security/public/management/roles/model/privilege_collection.ts @@ -20,7 +20,7 @@ export class PrivilegeCollection { } private checkActions(knownActions: ReadonlySet, candidateActions: string[]) { - const missing = candidateActions.filter(action => !knownActions.has(action)); + const missing = candidateActions.filter((action) => !knownActions.has(action)); const hasAllRequested = knownActions.size > 0 && candidateActions.length > 0 && missing.length === 0; diff --git a/x-pack/plugins/security/public/management/roles/model/secured_feature.ts b/x-pack/plugins/security/public/management/roles/model/secured_feature.ts index 7fc466a70b984..284a85583c33c 100644 --- a/x-pack/plugins/security/public/management/roles/model/secured_feature.ts +++ b/x-pack/plugins/security/public/management/roles/model/secured_feature.ts @@ -34,7 +34,7 @@ export class SecuredFeature extends Feature { } this.securedSubFeatures = - this.config.subFeatures?.map(sf => new SecuredSubFeature(sf, actionMapping)) ?? []; + this.config.subFeatures?.map((sf) => new SecuredSubFeature(sf, actionMapping)) ?? []; this.subFeaturePrivileges = this.securedSubFeatures.reduce((acc, subFeature) => { return [...acc, ...subFeature.privilegeIterator()]; diff --git a/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts b/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts index 3d69e5e709bb0..f7bdd9ba520ad 100644 --- a/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts +++ b/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts @@ -24,7 +24,7 @@ export class SecuredSubFeature extends SubFeature { } public getPrivilegeGroups() { - return this.privilegeGroups.map(pg => new SubFeaturePrivilegeGroup(pg, this.actionMapping)); + return this.privilegeGroups.map((pg) => new SubFeaturePrivilegeGroup(pg, this.actionMapping)); } public *privilegeIterator({ @@ -34,8 +34,8 @@ export class SecuredSubFeature extends SubFeature { } = {}): IterableIterator { for (const group of this.privilegeGroups) { yield* group.privileges - .map(gp => new SubFeaturePrivilege(gp, this.actionMapping[gp.id])) - .filter(privilege => predicate(privilege, this)); + .map((gp) => new SubFeaturePrivilege(gp, this.actionMapping[gp.id])) + .filter((privilege) => predicate(privilege, this)); } } } diff --git a/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege_group.ts b/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege_group.ts index b437649236e27..17d899874d8ff 100644 --- a/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege_group.ts +++ b/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege_group.ts @@ -19,7 +19,7 @@ export class SubFeaturePrivilegeGroup { public get privileges() { return this.config.privileges.map( - p => new SubFeaturePrivilege(p, this.actionMapping[p.id] || []) + (p) => new SubFeaturePrivilege(p, this.actionMapping[p.id] || []) ); } } diff --git a/x-pack/plugins/security/public/management/roles/roles_api_client.ts b/x-pack/plugins/security/public/management/roles/roles_api_client.ts index d7e98e03a965b..50c490d2924ba 100644 --- a/x-pack/plugins/security/public/management/roles/roles_api_client.ts +++ b/x-pack/plugins/security/public/management/roles/roles_api_client.ts @@ -34,18 +34,18 @@ export class RolesAPIClient { const isPlaceholderPrivilege = (indexPrivilege: RoleIndexPrivilege) => indexPrivilege.names.length === 0; role.elasticsearch.indices = role.elasticsearch.indices.filter( - indexPrivilege => !isPlaceholderPrivilege(indexPrivilege) + (indexPrivilege) => !isPlaceholderPrivilege(indexPrivilege) ); // Remove any placeholder query entries - role.elasticsearch.indices.forEach(index => index.query || delete index.query); + role.elasticsearch.indices.forEach((index) => index.query || delete index.query); // If spaces are disabled, then do not persist any space privileges if (!spacesEnabled) { role.kibana = role.kibana.filter(isGlobalPrivilegeDefinition); } - role.kibana.forEach(kibanaPrivilege => { + role.kibana.forEach((kibanaPrivilege) => { // If a base privilege is defined, then do not persist feature privileges if (kibanaPrivilege.base.length > 0) { kibanaPrivilege.feature = {}; diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx index 37eed3357241d..d1b266d2a68fe 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx @@ -71,7 +71,7 @@ export class ConfirmDelete extends Component { />

      - {rolesToDelete.map(roleName => ( + {rolesToDelete.map((roleName) => (
    • {roleName}
    • ))}
    @@ -129,7 +129,7 @@ export class ConfirmDelete extends Component { private deleteRoles = async () => { const { rolesToDelete, callback, rolesAPIClient, notifications } = this.props; const errors: string[] = []; - const deleteOperations = rolesToDelete.map(roleName => { + const deleteOperations = rolesToDelete.map((roleName) => { const deleteRoleTask = async () => { try { await rolesAPIClient.deleteRole(roleName); diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx index 410d5bc9f7643..743510d45107e 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx @@ -12,10 +12,11 @@ import { RolesAPIClient } from '../roles_api_client'; import { PermissionDenied } from './permission_denied'; import { RolesGridPage } from './roles_grid_page'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { rolesAPIClientMock } from '../index.mock'; import { ReservedBadge, DisabledBadge } from '../../badges'; import { findTestSubject } from 'test_utils/find_test_subject'; +import { ScopedHistory } from 'kibana/public'; const mock403 = () => ({ body: { statusCode: 403 } }); @@ -41,7 +42,10 @@ const waitForRender = async ( describe('', () => { let apiClientMock: jest.Mocked>; + let history: ScopedHistory; + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; apiClientMock = rolesAPIClientMock.create(); apiClientMock.getRoles.mockResolvedValue([ { @@ -68,12 +72,13 @@ describe('', () => { const wrapper = mountWithIntl( ); const initialIconCount = wrapper.find(EuiIcon).length; - await waitForRender(wrapper, updatedWrapper => { + await waitForRender(wrapper, (updatedWrapper) => { return updatedWrapper.find(EuiIcon).length > initialIconCount; }); @@ -85,12 +90,13 @@ describe('', () => { const wrapper = mountWithIntl( ); const initialIconCount = wrapper.find(EuiIcon).length; - await waitForRender(wrapper, updatedWrapper => { + await waitForRender(wrapper, (updatedWrapper) => { return updatedWrapper.find(EuiIcon).length > initialIconCount; }); @@ -104,10 +110,11 @@ describe('', () => { const wrapper = mountWithIntl( ); - await waitForRender(wrapper, updatedWrapper => { + await waitForRender(wrapper, (updatedWrapper) => { return updatedWrapper.find(PermissionDenied).length > 0; }); expect(wrapper.find(PermissionDenied)).toMatchSnapshot(); @@ -117,12 +124,13 @@ describe('', () => { const wrapper = mountWithIntl( ); const initialIconCount = wrapper.find(EuiIcon).length; - await waitForRender(wrapper, updatedWrapper => { + await waitForRender(wrapper, (updatedWrapper) => { return updatedWrapper.find(EuiIcon).length > initialIconCount; }); @@ -146,12 +154,13 @@ describe('', () => { const wrapper = mountWithIntl( ); const initialIconCount = wrapper.find(EuiIcon).length; - await waitForRender(wrapper, updatedWrapper => { + await waitForRender(wrapper, (updatedWrapper) => { return updatedWrapper.find(EuiIcon).length > initialIconCount; }); diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx index 04a74a1a9b99a..051c16f03d342 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx @@ -26,6 +26,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { NotificationsStart } from 'src/core/public'; +import { ScopedHistory } from 'kibana/public'; import { Role, isRoleEnabled, @@ -38,10 +39,12 @@ import { RolesAPIClient } from '../roles_api_client'; import { ConfirmDelete } from './confirm_delete'; import { PermissionDenied } from './permission_denied'; import { DisabledBadge, DeprecatedBadge, ReservedBadge } from '../../badges'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { notifications: NotificationsStart; rolesAPIClient: PublicMethodsOf; + history: ScopedHistory; } interface State { @@ -55,7 +58,7 @@ interface State { } const getRoleManagementHref = (action: 'edit' | 'clone', roleName?: string) => { - return `#/management/security/roles/${action}${roleName ? `/${roleName}` : ''}`; + return `/${action}${roleName ? `/${roleName}` : ''}`; }; export class RolesGridPage extends Component { @@ -106,7 +109,10 @@ export class RolesGridPage extends Component { - + { {this.state.showDeleteConfirmation ? ( role.name)} + rolesToDelete={this.state.selection.map((role) => role.name)} callback={this.handleDelete} notifications={this.props.notifications} rolesAPIClient={this.props.rolesAPIClient} @@ -190,7 +196,10 @@ export class RolesGridPage extends Component { render: (name: string, record: Role) => { return ( - + {name} @@ -228,7 +237,10 @@ export class RolesGridPage extends Component { title={title} color={'primary'} iconType={'pencil'} - href={getRoleManagementHref('edit', role.name)} + {...reactRouterNavigate( + this.props.history, + getRoleManagementHref('edit', role.name) + )} /> ); }, @@ -248,7 +260,10 @@ export class RolesGridPage extends Component { title={title} color={'primary'} iconType={'copy'} - href={getRoleManagementHref('clone', role.name)} + {...reactRouterNavigate( + this.props.history, + getRoleManagementHref('edit', role.name) + )} /> ); }, @@ -259,7 +274,7 @@ export class RolesGridPage extends Component { }; private getVisibleRoles = (roles: Role[], filter: string, includeReservedRoles: boolean) => { - return roles.filter(role => { + return roles.filter((role) => { const normalized = `${role.name}`.toLowerCase(); const normalizedQuery = filter.toLowerCase(); return ( diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx index 96051dbd7fa56..e7f38c86b045e 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx @@ -14,12 +14,14 @@ jest.mock('./edit_role', () => ({ EditRolePage: (props: any) => `Role Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; + import { rolesManagementApp } from './roles_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; import { featuresPluginMock } from '../../../../features/public/mocks'; -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const { fatalErrors } = coreMock.createSetup(); const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -34,7 +36,12 @@ async function mountApp(basePath: string) { .fn() .mockResolvedValue([coreMock.createStart(), { data: {}, features: featuresStart }]), }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -60,16 +67,13 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/roles'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Roles' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }]); expect(container).toMatchInlineSnapshot(`
    - Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} + Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
    `); @@ -79,19 +83,13 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `create role` page', async () => { - const basePath = '/some-base-path/roles'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
    - Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
    `); @@ -101,20 +99,18 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `edit role` page', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'someRoleName'; - window.location.hash = `${basePath}/edit/${roleName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { href: `#/some-base-path/roles/edit/${roleName}`, text: roleName }, + { href: `/`, text: 'Roles' }, + { href: `/edit/${roleName}`, text: roleName }, ]); expect(container).toMatchInlineSnapshot(`
    - Role Edit Page: {"action":"edit","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"edit","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someRoleName","search":"","hash":""}}}
    `); @@ -124,20 +120,15 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `clone role` page', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'someRoleName'; - window.location.hash = `${basePath}/clone/${roleName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/clone/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
    - Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}}}
    `); @@ -147,17 +138,15 @@ describe('rolesManagementApp', () => { }); it('mount() properly encodes role name in `edit role` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'some 安全性 role'; - window.location.hash = `${basePath}/edit/${roleName}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, + { href: `/`, text: 'Roles' }, { - href: '#/some-base-path/roles/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', text: roleName, }, ]); diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx index 9aaa3b47f3b19..88aeb1d232fc7 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor, FatalErrorsSetup } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -27,19 +27,16 @@ export const rolesManagementApp = Object.freeze({ id: this.id, order: 20, title: i18n.translate('xpack.security.management.rolesTitle', { defaultMessage: 'Roles' }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { const rolesBreadcrumbs = [ { text: i18n.translate('xpack.security.roles.breadcrumb', { defaultMessage: 'Roles' }), - href: `#${basePath}`, + href: `/`, }, ]; const [ - [ - { application, docLinks, http, i18n: i18nStart, injectedMetadata, notifications }, - { data, features }, - ], + [{ application, docLinks, http, i18n: i18nStart, notifications }, { data, features }], { RolesGridPage }, { EditRolePage }, { RolesAPIClient }, @@ -59,7 +56,13 @@ export const rolesManagementApp = Object.freeze({ const rolesAPIClient = new RolesAPIClient(http); const RolesGridPageWithBreadcrumbs = () => { setBreadcrumbs(rolesBreadcrumbs); - return ; + return ( + + ); }; const EditRolePageWithBreadcrumbs = ({ action }: { action: 'edit' | 'clone' }) => { @@ -68,7 +71,7 @@ export const rolesManagementApp = Object.freeze({ setBreadcrumbs([ ...rolesBreadcrumbs, action === 'edit' && roleName - ? { text: roleName, href: `#${basePath}/edit/${encodeURIComponent(roleName)}` } + ? { text: roleName, href: `/edit/${encodeURIComponent(roleName)}` } : { text: i18n.translate('xpack.security.roles.createBreadcrumb', { defaultMessage: 'Create', @@ -80,9 +83,6 @@ export const rolesManagementApp = Object.freeze({ ); }; render( - + - + diff --git a/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.tsx b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.tsx index 047cad7bead81..7a97794303558 100644 --- a/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.tsx +++ b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.tsx @@ -267,7 +267,7 @@ export class ChangePasswordForm extends Component { this.validateConfirmPassword(true), ]; - const firstFailure = validation.find(result => result.isInvalid); + const firstFailure = validation.find((result) => result.isInvalid); if (firstFailure) { return firstFailure; } diff --git a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx index 9c5a8b0b75ead..e39a4acdaaff9 100644 --- a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx +++ b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx @@ -80,7 +80,7 @@ describe('ConfirmDeleteUsers', () => { const onCancel = jest.fn(); const apiClientMock = userAPIClientMock.create(); - apiClientMock.deleteUser.mockImplementation(user => { + apiClientMock.deleteUser.mockImplementation((user) => { if (user === 'foo') { return Promise.reject('something terrible happened'); } diff --git a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx index 53acbf42273e8..a6fbc6be945c9 100644 --- a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx +++ b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx @@ -58,7 +58,7 @@ export class ConfirmDeleteUsers extends Component { />

      - {usersToDelete.map(username => ( + {usersToDelete.map((username) => (
    • {username}
    • ))}
    @@ -79,7 +79,7 @@ export class ConfirmDeleteUsers extends Component { private deleteUsers = () => { const { usersToDelete, callback, userAPIClient, notifications } = this.props; const errors: string[] = []; - usersToDelete.forEach(async username => { + usersToDelete.forEach(async (username) => { try { await userAPIClient.deleteUser(username); notifications.toasts.addSuccess( diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx index a97781ba25ea6..7ee33357b9af4 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx @@ -5,12 +5,13 @@ */ import { act } from '@testing-library/react'; +import { ScopedHistory } from 'kibana/public'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { EditUserPage } from './edit_user_page'; import React from 'react'; import { User, Role } from '../../../../common/model'; import { ReactWrapper } from 'enzyme'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock'; import { securityMock } from '../../../mocks'; import { rolesAPIClientMock } from '../../roles/index.mock'; @@ -103,6 +104,8 @@ function expectMissingSaveButton(wrapper: ReactWrapper) { } describe('EditUserPage', () => { + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + it('allows reserved users to be viewed', async () => { const user = createUser('reserved_user'); const { apiClient, rolesAPIClient } = buildClients(user); @@ -114,6 +117,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -136,6 +140,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -158,6 +163,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -182,6 +188,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -204,6 +211,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx index 7172ff178eb6b..eea7edd62fbfa 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx @@ -30,10 +30,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ScopedHistory } from 'src/core/public'; import { User, EditUser, Role, isRoleDeprecated } from '../../../../common/model'; import { AuthenticationServiceSetup } from '../../../authentication'; -import { USERS_PATH } from '../../management_urls'; import { RolesAPIClient } from '../../roles'; import { ConfirmDeleteUsers, ChangePasswordForm } from '../components'; import { UserValidator, UserValidationResult } from './validate_user'; @@ -47,6 +46,7 @@ interface Props { rolesAPIClient: PublicMethodsOf; authc: AuthenticationServiceSetup; notifications: NotificationsStart; + history: ScopedHistory; } interface State { @@ -61,10 +61,6 @@ interface State { formError: UserValidationResult | null; } -function backToUserList() { - window.location.hash = USERS_PATH; -} - export class EditUserPage extends Component { private validator: UserValidator; @@ -102,6 +98,10 @@ export class EditUserPage extends Component { } } + private backToUserList() { + this.props.history.push('/'); + } + private async setCurrentUser() { const { username, userAPIClient, rolesAPIClient, notifications, authc } = this.props; let { user, currentUser } = this.state; @@ -120,7 +120,7 @@ export class EditUserPage extends Component { }), text: get(err, 'body.message') || err.message, }); - return backToUserList(); + return this.backToUserList(); } } @@ -148,7 +148,7 @@ export class EditUserPage extends Component { private handleDelete = (usernames: string[], errors: string[]) => { if (errors.length === 0) { - backToUserList(); + this.backToUserList(); } }; @@ -184,7 +184,7 @@ export class EditUserPage extends Component { ) ); - backToUserList(); + this.backToUserList(); } catch (e) { this.props.notifications.toasts.addDanger( i18n.translate('xpack.security.management.users.editUser.savingUserErrorMessage', { @@ -382,8 +382,8 @@ export class EditUserPage extends Component { return null; } - const hasAnyDeprecatedRolesAssigned = selectedRoles.some(selected => { - const role = roles.find(r => r.name === selected); + const hasAnyDeprecatedRolesAssigned = selectedRoles.some((selected) => { + const role = roles.find((r) => r.name === selected); return role && isRoleDeprecated(role); }); @@ -394,9 +394,7 @@ export class EditUserPage extends Component { defaultMessage="This user is assigned a deprecated role. Please migrate to a supported role." />
    - ) : ( - undefined - ); + ) : undefined; return (
    @@ -551,7 +549,7 @@ export class EditUserPage extends Component { {reserved && ( - + this.backToUserList()}> { - + this.backToUserList()} + > { + let history: ScopedHistory; + let coreStart: CoreStart; + + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + history.createHref = (location: LocationDescriptorObject) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; + }; + coreStart = coreMock.createStart(); + }); + it('renders the list of users', async () => { const apiClientMock = userAPIClientMock.create(); apiClientMock.getUsers.mockImplementation(() => { @@ -44,7 +57,9 @@ describe('UsersGridPage', () => { ); @@ -64,7 +79,9 @@ describe('UsersGridPage', () => { ); @@ -93,7 +110,9 @@ describe('UsersGridPage', () => { ); @@ -125,7 +144,9 @@ describe('UsersGridPage', () => { ); @@ -173,7 +194,9 @@ describe('UsersGridPage', () => { ); @@ -231,7 +254,9 @@ describe('UsersGridPage', () => { ); diff --git a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx index f8882129772f7..50815808c4859 100644 --- a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx +++ b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx @@ -23,19 +23,22 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ApplicationStart, ScopedHistory } from 'src/core/public'; import { User, Role } from '../../../../common/model'; import { ConfirmDeleteUsers } from '../components'; import { isUserReserved, getExtendedUserDeprecationNotice, isUserDeprecated } from '../user_utils'; import { DisabledBadge, ReservedBadge, DeprecatedBadge } from '../../badges'; import { RoleTableDisplay } from '../../role_table_display'; import { RolesAPIClient } from '../../roles'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { UserAPIClient } from '..'; interface Props { userAPIClient: PublicMethodsOf; rolesAPIClient: PublicMethodsOf; notifications: NotificationsStart; + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -70,6 +73,7 @@ export class UsersGridPage extends Component { public render() { const { users, roles, permissionDenied, showDeleteConfirmation, selection } = this.state; + if (permissionDenied) { return ( @@ -97,7 +101,6 @@ export class UsersGridPage extends Component { ); } - const path = '#/management/security/'; const columns: Array> = [ { field: 'username', @@ -107,7 +110,10 @@ export class UsersGridPage extends Component { sortable: true, truncateText: true, render: (username: string) => ( - + {username} ), @@ -143,8 +149,14 @@ export class UsersGridPage extends Component { width: '30%', render: (rolenames: string[]) => { const roleLinks = rolenames.map((rolename, index) => { - const roleDefinition = roles?.find(role => role.name === rolename) ?? rolename; - return ; + const roleDefinition = roles?.find((role) => role.name === rolename) ?? rolename; + return ( + + ); }); return
    {roleLinks}
    ; }, @@ -219,7 +231,10 @@ export class UsersGridPage extends Component { - + { {showDeleteConfirmation ? ( user.username)} + usersToDelete={selection.map((user) => user.username)} callback={this.handleDelete} userAPIClient={this.props.userAPIClient} notifications={this.props.notifications} diff --git a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx index 05491d6f889b6..98906f560e6cb 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx @@ -12,12 +12,13 @@ jest.mock('./edit_user', () => ({ EditUserPage: (props: any) => `User Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; import { usersManagementApp } from './users_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; import { securityMock } from '../../mocks'; -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -26,7 +27,12 @@ async function mountApp(basePath: string) { authc: securityMock.createSetup().authc, getStartServices: coreMock.createSetup().getStartServices as any, }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -49,16 +55,13 @@ describe('usersManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/users'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Users' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Users' }]); expect(container).toMatchInlineSnapshot(`
    - Users Page: {"notifications":{"toasts":{}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} + Users Page: {"notifications":{"toasts":{}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
    `); @@ -68,19 +71,13 @@ describe('usersManagementApp', () => { }); it('mount() works for the `create user` page', async () => { - const basePath = '/some-base-path/users'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Users' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
    - User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}}} + User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
    `); @@ -90,20 +87,18 @@ describe('usersManagementApp', () => { }); it('mount() works for the `edit user` page', async () => { - const basePath = '/some-base-path/users'; const userName = 'someUserName'; - window.location.hash = `${basePath}/edit/${userName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${userName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, - { href: `#/some-base-path/users/edit/${userName}`, text: userName }, + { href: `/`, text: 'Users' }, + { href: `/edit/${userName}`, text: userName }, ]); expect(container).toMatchInlineSnapshot(`
    - User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"username":"someUserName"} + User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"username":"someUserName","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someUserName","search":"","hash":""}}}
    `); @@ -113,17 +108,15 @@ describe('usersManagementApp', () => { }); it('mount() properly encodes user name in `edit user` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/users'; const username = 'some 安全性 user'; - window.location.hash = `${basePath}/edit/${username}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${username}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, + { href: `/`, text: 'Users' }, { - href: '#/some-base-path/users/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20user', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20user', text: username, }, ]); diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx index 9d337c1508ad4..82c55d67b9026 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -25,11 +25,12 @@ export const usersManagementApp = Object.freeze({ id: this.id, order: 10, title: i18n.translate('xpack.security.management.usersTitle', { defaultMessage: 'Users' }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { + const [coreStart] = await getStartServices(); const usersBreadcrumbs = [ { text: i18n.translate('xpack.security.users.breadcrumb', { defaultMessage: 'Users' }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -56,6 +57,8 @@ export const usersManagementApp = Object.freeze({ notifications={notifications} userAPIClient={userAPIClient} rolesAPIClient={rolesAPIClient} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); }; @@ -66,7 +69,7 @@ export const usersManagementApp = Object.freeze({ setBreadcrumbs([ ...usersBreadcrumbs, username - ? { text: username, href: `#${basePath}/edit/${encodeURIComponent(username)}` } + ? { text: username, href: `/edit/${encodeURIComponent(username)}` } : { text: i18n.translate('xpack.security.users.createBreadcrumb', { defaultMessage: 'Create', @@ -81,15 +84,16 @@ export const usersManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} notifications={notifications} username={username} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index eaa1d8dee0a11..3ddabb0dc55f8 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -41,7 +41,7 @@ export class SecurityNavControl extends Component { authenticatedUser: null, }; - props.user.then(authenticatedUser => { + props.user.then((authenticatedUser) => { this.setState({ authenticatedUser, }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 66731cf19006d..acf62f3376b8b 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -15,7 +15,7 @@ import { mockAuthenticatedUser } from '../../common/model/authenticated_user.moc const validLicense = { isAvailable: true, - getFeature: feature => { + getFeature: (feature) => { expect(feature).toEqual('security'); return { diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 38ef552e75a9e..da69dd051c11d 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -122,7 +122,7 @@ export class SecurityPlugin 'Protect your data and easily manage who has access to what with users and roles.', }), icon: 'securityApp', - path: '/app/kibana#/management/security/users', + path: '/app/management/security/users', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/security/public/session/session_expired.test.ts b/x-pack/plugins/security/public/session/session_expired.test.ts index f5ad9cb464e3e..1ec2f772050f9 100644 --- a/x-pack/plugins/security/public/session/session_expired.test.ts +++ b/x-pack/plugins/security/public/session/session_expired.test.ts @@ -26,8 +26,8 @@ describe('#logout', () => { beforeEach(() => { window.history.pushState({}, '', CURRENT_URL); mockGetItem.mockReset(); - newUrlPromise = new Promise(resolve => { - jest.spyOn(window.location, 'assign').mockImplementation(url => { + newUrlPromise = new Promise((resolve) => { + jest.spyOn(window.location, 'assign').mockImplementation((url) => { resolve(url); }); }); diff --git a/x-pack/plugins/security/public/session/session_timeout_http_interceptor.test.ts b/x-pack/plugins/security/public/session/session_timeout_http_interceptor.test.ts index 427bdb04f9c61..40cbd00858b5f 100644 --- a/x-pack/plugins/security/public/session/session_timeout_http_interceptor.test.ts +++ b/x-pack/plugins/security/public/session/session_timeout_http_interceptor.test.ts @@ -13,7 +13,7 @@ import { createSessionTimeoutMock } from './session_timeout.mock'; const mockCurrentUrl = (url: string) => window.history.pushState({}, '', url); const setupHttp = (basePath: string) => { - const { http } = setup(injectedMetadata => { + const { http } = setup((injectedMetadata) => { injectedMetadata.getBasePath.mockReturnValue(basePath); }); return http; diff --git a/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts index fba2a2ec98146..78c82cbc3a9a6 100644 --- a/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts +++ b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts @@ -12,7 +12,7 @@ import { UnauthorizedResponseHttpInterceptor } from './unauthorized_response_htt jest.mock('./session_expired'); const drainPromiseQueue = () => { - return new Promise(resolve => { + return new Promise((resolve) => { setImmediate(resolve); }); }; @@ -20,7 +20,7 @@ const drainPromiseQueue = () => { const mockCurrentUrl = (url: string) => window.history.pushState({}, '', url); const setupHttp = (basePath: string) => { - const { http } = setup(injectedMetadata => { + const { http } = setup((injectedMetadata) => { injectedMetadata.getBasePath.mockReturnValue(basePath); }); return http; @@ -34,7 +34,7 @@ afterEach(() => { it(`logs out 401 responses`, async () => { const http = setupHttp('/foo'); const sessionExpired = new SessionExpired(`${http.basePath}/logout`, tenant); - const logoutPromise = new Promise(resolve => { + const logoutPromise = new Promise((resolve) => { jest.spyOn(sessionExpired, 'logout').mockImplementation(() => resolve()); }); const interceptor = new UnauthorizedResponseHttpInterceptor(sessionExpired, http.anonymousPaths); diff --git a/x-pack/plugins/security/server/audit/audit_logger.test.ts b/x-pack/plugins/security/server/audit/audit_logger.test.ts deleted file mode 100644 index 4dfd69a2ccb1f..0000000000000 --- a/x-pack/plugins/security/server/audit/audit_logger.test.ts +++ /dev/null @@ -1,112 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { SecurityAuditLogger } from './audit_logger'; - -const createMockAuditLogger = () => { - return { - log: jest.fn(), - }; -}; - -describe(`#savedObjectsAuthorizationFailure`, () => { - test('logs via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(() => auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - const types = ['foo-type-1', 'foo-type-2']; - const spaceIds = ['foo-space', 'bar-space']; - const missing = [ - { - spaceId: 'foo-space', - privilege: `saved_object:${types[0]}/${action}`, - }, - { - spaceId: 'foo-space', - privilege: `saved_object:${types[1]}/${action}`, - }, - ]; - const args = { - foo: 'bar', - baz: 'quz', - }; - - securityAuditLogger.savedObjectsAuthorizationFailure( - username, - action, - types, - spaceIds, - missing, - args - ); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'saved_objects_authorization_failure', - expect.any(String), - { - username, - action, - types, - spaceIds, - missing, - args, - } - ); - expect(auditLogger.log.mock.calls[0][1]).toMatchInlineSnapshot( - `"foo-user unauthorized to [foo-action] [foo-type-1,foo-type-2] in [foo-space,bar-space]: missing [(foo-space)saved_object:foo-type-1/foo-action,(foo-space)saved_object:foo-type-2/foo-action]"` - ); - }); -}); - -describe(`#savedObjectsAuthorizationSuccess`, () => { - test('logs via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(() => auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - const types = ['foo-type-1', 'foo-type-2']; - const spaceIds = ['foo-space', 'bar-space']; - const args = { - foo: 'bar', - baz: 'quz', - }; - - securityAuditLogger.savedObjectsAuthorizationSuccess(username, action, types, spaceIds, args); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'saved_objects_authorization_success', - expect.any(String), - { - username, - action, - types, - spaceIds, - args, - } - ); - expect(auditLogger.log.mock.calls[0][1]).toMatchInlineSnapshot( - `"foo-user authorized to [foo-action] [foo-type-1,foo-type-2] in [foo-space,bar-space]"` - ); - }); -}); - -describe(`#accessAgreementAcknowledged`, () => { - test('logs via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(() => auditLogger); - const username = 'foo-user'; - const provider = { type: 'saml', name: 'saml1' }; - - securityAuditLogger.accessAgreementAcknowledged(username, provider); - - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - 'access_agreement_acknowledged', - 'foo-user acknowledged access agreement (saml/saml1).', - { username, provider } - ); - }); -}); diff --git a/x-pack/plugins/security/server/audit/audit_logger.ts b/x-pack/plugins/security/server/audit/audit_logger.ts deleted file mode 100644 index d7243ecbe13f8..0000000000000 --- a/x-pack/plugins/security/server/audit/audit_logger.ts +++ /dev/null @@ -1,69 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AuthenticationProvider } from '../../common/types'; -import { LegacyAPI } from '../plugin'; - -export class SecurityAuditLogger { - constructor(private readonly getAuditLogger: () => LegacyAPI['auditLogger']) {} - - savedObjectsAuthorizationFailure( - username: string, - action: string, - types: string[], - spaceIds: string[], - missing: Array<{ spaceId?: string; privilege: string }>, - args?: Record - ) { - const typesString = types.join(','); - const spacesString = spaceIds.length ? ` in [${spaceIds.join(',')}]` : ''; - const missingString = missing - .map(({ spaceId, privilege }) => `${spaceId ? `(${spaceId})` : ''}${privilege}`) - .join(','); - this.getAuditLogger().log( - 'saved_objects_authorization_failure', - `${username} unauthorized to [${action}] [${typesString}]${spacesString}: missing [${missingString}]`, - { - username, - action, - types, - spaceIds, - missing, - args, - } - ); - } - - savedObjectsAuthorizationSuccess( - username: string, - action: string, - types: string[], - spaceIds: string[], - args?: Record - ) { - const typesString = types.join(','); - const spacesString = spaceIds.length ? ` in [${spaceIds.join(',')}]` : ''; - this.getAuditLogger().log( - 'saved_objects_authorization_success', - `${username} authorized to [${action}] [${typesString}]${spacesString}`, - { - username, - action, - types, - spaceIds, - args, - } - ); - } - - accessAgreementAcknowledged(username: string, provider: AuthenticationProvider) { - this.getAuditLogger().log( - 'access_agreement_acknowledged', - `${username} acknowledged access agreement (${provider.type}/${provider.name}).`, - { username, provider } - ); - } -} diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts new file mode 100644 index 0000000000000..94a2ada8df1da --- /dev/null +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AuditService } from './audit_service'; +import { loggingServiceMock } from 'src/core/server/mocks'; +import { licenseMock } from '../../common/licensing/index.mock'; +import { ConfigSchema, ConfigType } from '../config'; +import { SecurityLicenseFeatures } from '../../common/licensing'; +import { BehaviorSubject } from 'rxjs'; + +const createConfig = (settings: Partial) => { + return ConfigSchema.validate(settings); +}; + +const config = createConfig({ + enabled: true, +}); + +describe('#setup', () => { + it('returns the expected contract', () => { + const logger = loggingServiceMock.createLogger(); + const auditService = new AuditService(logger); + const license = licenseMock.create(); + expect(auditService.setup({ license, config })).toMatchInlineSnapshot(` + Object { + "getLogger": [Function], + } + `); + }); +}); + +test(`calls the underlying logger with the provided message and requisite tags`, () => { + const pluginId = 'foo'; + + const logger = loggingServiceMock.createLogger(); + const license = licenseMock.create(); + license.features$ = new BehaviorSubject({ + allowAuditLogging: true, + } as SecurityLicenseFeatures).asObservable(); + + const auditService = new AuditService(logger).setup({ license, config }); + + const auditLogger = auditService.getLogger(pluginId); + + const eventType = 'bar'; + const message = 'this is my audit message'; + auditLogger.log(eventType, message); + + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith(message, { + eventType, + tags: [pluginId, eventType], + }); +}); + +test(`calls the underlying logger with the provided metadata`, () => { + const pluginId = 'foo'; + + const logger = loggingServiceMock.createLogger(); + const license = licenseMock.create(); + license.features$ = new BehaviorSubject({ + allowAuditLogging: true, + } as SecurityLicenseFeatures).asObservable(); + + const auditService = new AuditService(logger).setup({ license, config }); + + const auditLogger = auditService.getLogger(pluginId); + + const eventType = 'bar'; + const message = 'this is my audit message'; + const metadata = Object.freeze({ + property1: 'value1', + property2: false, + property3: 123, + }); + auditLogger.log(eventType, message, metadata); + + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith(message, { + eventType, + tags: [pluginId, eventType], + property1: 'value1', + property2: false, + property3: 123, + }); +}); + +test(`does not call the underlying logger if license does not support audit logging`, () => { + const pluginId = 'foo'; + + const logger = loggingServiceMock.createLogger(); + const license = licenseMock.create(); + license.features$ = new BehaviorSubject({ + allowAuditLogging: false, + } as SecurityLicenseFeatures).asObservable(); + + const auditService = new AuditService(logger).setup({ license, config }); + + const auditLogger = auditService.getLogger(pluginId); + + const eventType = 'bar'; + const message = 'this is my audit message'; + auditLogger.log(eventType, message); + + expect(logger.info).not.toHaveBeenCalled(); +}); + +test(`does not call the underlying logger if security audit logging is not enabled`, () => { + const pluginId = 'foo'; + + const logger = loggingServiceMock.createLogger(); + const license = licenseMock.create(); + license.features$ = new BehaviorSubject({ + allowAuditLogging: true, + } as SecurityLicenseFeatures).asObservable(); + + const auditService = new AuditService(logger).setup({ + license, + config: createConfig({ + enabled: false, + }), + }); + + const auditLogger = auditService.getLogger(pluginId); + + const eventType = 'bar'; + const message = 'this is my audit message'; + auditLogger.log(eventType, message); + + expect(logger.info).not.toHaveBeenCalled(); +}); + +test(`calls the underlying logger after license upgrade`, () => { + const pluginId = 'foo'; + + const logger = loggingServiceMock.createLogger(); + const license = licenseMock.create(); + + const features$ = new BehaviorSubject({ + allowAuditLogging: false, + } as SecurityLicenseFeatures); + + license.features$ = features$.asObservable(); + + const auditService = new AuditService(logger).setup({ license, config }); + + const auditLogger = auditService.getLogger(pluginId); + + const eventType = 'bar'; + const message = 'this is my audit message'; + auditLogger.log(eventType, message); + + expect(logger.info).not.toHaveBeenCalled(); + + // perform license upgrade + features$.next({ + allowAuditLogging: true, + } as SecurityLicenseFeatures); + + auditLogger.log(eventType, message); + + expect(logger.info).toHaveBeenCalledTimes(1); +}); diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts new file mode 100644 index 0000000000000..93e69fd2601e9 --- /dev/null +++ b/x-pack/plugins/security/server/audit/audit_service.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Subscription } from 'rxjs'; +import { Logger } from '../../../../../src/core/server'; +import { SecurityLicense } from '../../common/licensing'; +import { ConfigType } from '../config'; + +export interface AuditLogger { + log: (eventType: string, message: string, data?: Record) => void; +} + +export interface AuditServiceSetup { + getLogger: (id?: string) => AuditLogger; +} + +interface AuditServiceSetupParams { + license: SecurityLicense; + config: ConfigType['audit']; +} + +export class AuditService { + private licenseFeaturesSubscription?: Subscription; + private auditLoggingEnabled = false; + + constructor(private readonly logger: Logger) {} + + setup({ license, config }: AuditServiceSetupParams): AuditServiceSetup { + if (config.enabled) { + this.licenseFeaturesSubscription = license.features$.subscribe(({ allowAuditLogging }) => { + this.auditLoggingEnabled = allowAuditLogging; + }); + } + + return { + getLogger: (id?: string): AuditLogger => { + return { + log: (eventType: string, message: string, data?: Record) => { + if (!this.auditLoggingEnabled) { + return; + } + + this.logger.info(message, { + tags: id ? [id, eventType] : [eventType], + eventType, + ...data, + }); + }, + }; + }, + }; + } + + stop() { + if (this.licenseFeaturesSubscription) { + this.licenseFeaturesSubscription.unsubscribe(); + this.licenseFeaturesSubscription = undefined; + } + } +} diff --git a/x-pack/plugins/security/server/audit/index.mock.ts b/x-pack/plugins/security/server/audit/index.mock.ts index 888aa3361faf0..07341cc06e889 100644 --- a/x-pack/plugins/security/server/audit/index.mock.ts +++ b/x-pack/plugins/security/server/audit/index.mock.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SecurityAuditLogger } from './audit_logger'; +import { SecurityAuditLogger } from './security_audit_logger'; +import { AuditService } from './audit_service'; export const securityAuditLoggerMock = { create() { @@ -15,3 +16,11 @@ export const securityAuditLoggerMock = { } as unknown) as jest.Mocked; }, }; + +export const auditServiceMock = { + create() { + return { + getLogger: jest.fn(), + } as jest.Mocked>; + }, +}; diff --git a/x-pack/plugins/security/server/audit/index.ts b/x-pack/plugins/security/server/audit/index.ts index 3ab253151b805..3db160c703e34 100644 --- a/x-pack/plugins/security/server/audit/index.ts +++ b/x-pack/plugins/security/server/audit/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { SecurityAuditLogger } from './audit_logger'; +export { AuditService, AuditServiceSetup, AuditLogger } from './audit_service'; +export { SecurityAuditLogger } from './security_audit_logger'; diff --git a/x-pack/plugins/security/server/audit/security_audit_logger.test.ts b/x-pack/plugins/security/server/audit/security_audit_logger.test.ts new file mode 100644 index 0000000000000..c6883f681cf41 --- /dev/null +++ b/x-pack/plugins/security/server/audit/security_audit_logger.test.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SecurityAuditLogger } from './security_audit_logger'; + +const createMockAuditLogger = () => { + return { + log: jest.fn(), + }; +}; + +describe(`#savedObjectsAuthorizationFailure`, () => { + test('logs via auditLogger', () => { + const auditLogger = createMockAuditLogger(); + const securityAuditLogger = new SecurityAuditLogger(auditLogger); + const username = 'foo-user'; + const action = 'foo-action'; + const types = ['foo-type-1', 'foo-type-2']; + const spaceIds = ['foo-space', 'bar-space']; + const missing = [ + { + spaceId: 'foo-space', + privilege: `saved_object:${types[0]}/${action}`, + }, + { + spaceId: 'foo-space', + privilege: `saved_object:${types[1]}/${action}`, + }, + ]; + const args = { + foo: 'bar', + baz: 'quz', + }; + + securityAuditLogger.savedObjectsAuthorizationFailure( + username, + action, + types, + spaceIds, + missing, + args + ); + + expect(auditLogger.log).toHaveBeenCalledWith( + 'saved_objects_authorization_failure', + expect.any(String), + { + username, + action, + types, + spaceIds, + missing, + args, + } + ); + expect(auditLogger.log.mock.calls[0][1]).toMatchInlineSnapshot( + `"foo-user unauthorized to [foo-action] [foo-type-1,foo-type-2] in [foo-space,bar-space]: missing [(foo-space)saved_object:foo-type-1/foo-action,(foo-space)saved_object:foo-type-2/foo-action]"` + ); + }); +}); + +describe(`#savedObjectsAuthorizationSuccess`, () => { + test('logs via auditLogger', () => { + const auditLogger = createMockAuditLogger(); + const securityAuditLogger = new SecurityAuditLogger(auditLogger); + const username = 'foo-user'; + const action = 'foo-action'; + const types = ['foo-type-1', 'foo-type-2']; + const spaceIds = ['foo-space', 'bar-space']; + const args = { + foo: 'bar', + baz: 'quz', + }; + + securityAuditLogger.savedObjectsAuthorizationSuccess(username, action, types, spaceIds, args); + + expect(auditLogger.log).toHaveBeenCalledWith( + 'saved_objects_authorization_success', + expect.any(String), + { + username, + action, + types, + spaceIds, + args, + } + ); + expect(auditLogger.log.mock.calls[0][1]).toMatchInlineSnapshot( + `"foo-user authorized to [foo-action] [foo-type-1,foo-type-2] in [foo-space,bar-space]"` + ); + }); +}); + +describe(`#accessAgreementAcknowledged`, () => { + test('logs via auditLogger', () => { + const auditLogger = createMockAuditLogger(); + const securityAuditLogger = new SecurityAuditLogger(auditLogger); + const username = 'foo-user'; + const provider = { type: 'saml', name: 'saml1' }; + + securityAuditLogger.accessAgreementAcknowledged(username, provider); + + expect(auditLogger.log).toHaveBeenCalledTimes(1); + expect(auditLogger.log).toHaveBeenCalledWith( + 'access_agreement_acknowledged', + 'foo-user acknowledged access agreement (saml/saml1).', + { username, provider } + ); + }); +}); diff --git a/x-pack/plugins/security/server/audit/security_audit_logger.ts b/x-pack/plugins/security/server/audit/security_audit_logger.ts new file mode 100644 index 0000000000000..87f7201f85665 --- /dev/null +++ b/x-pack/plugins/security/server/audit/security_audit_logger.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AuthenticationProvider } from '../../common/types'; +import { AuditLogger } from './audit_service'; + +export class SecurityAuditLogger { + constructor(private readonly logger: AuditLogger) {} + + savedObjectsAuthorizationFailure( + username: string, + action: string, + types: string[], + spaceIds: string[], + missing: Array<{ spaceId?: string; privilege: string }>, + args?: Record + ) { + const typesString = types.join(','); + const spacesString = spaceIds.length ? ` in [${spaceIds.join(',')}]` : ''; + const missingString = missing + .map(({ spaceId, privilege }) => `${spaceId ? `(${spaceId})` : ''}${privilege}`) + .join(','); + this.logger.log( + 'saved_objects_authorization_failure', + `${username} unauthorized to [${action}] [${typesString}]${spacesString}: missing [${missingString}]`, + { + username, + action, + types, + spaceIds, + missing, + args, + } + ); + } + + savedObjectsAuthorizationSuccess( + username: string, + action: string, + types: string[], + spaceIds: string[], + args?: Record + ) { + const typesString = types.join(','); + const spacesString = spaceIds.length ? ` in [${spaceIds.join(',')}]` : ''; + this.logger.log( + 'saved_objects_authorization_success', + `${username} authorized to [${action}] [${typesString}]${spacesString}`, + { + username, + action, + types, + spaceIds, + args, + } + ); + } + + accessAgreementAcknowledged(username: string, provider: AuthenticationProvider) { + this.logger.log( + 'access_agreement_acknowledged', + `${username} acknowledged access agreement (${provider.type}/${provider.name}).`, + { username, provider } + ); + } +} diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 49b7b40659cfc..60d0521a2947e 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -29,6 +29,7 @@ import { AuthenticationResult } from './authentication_result'; import { Authenticator, AuthenticatorOptions, ProviderSession } from './authenticator'; import { DeauthenticationResult } from './deauthentication_result'; import { BasicAuthenticationProvider, SAMLAuthenticationProvider } from './providers'; +import { securityFeatureUsageServiceMock } from '../feature_usage/index.mock'; function getMockOptions({ session, @@ -54,6 +55,9 @@ function getMockOptions({ { isTLSEnabled: false } ), sessionStorageFactory: sessionStorageMock.createFactory(), + getFeatureUsageService: jest + .fn() + .mockReturnValue(securityFeatureUsageServiceMock.createStartContract()), }; } @@ -1451,6 +1455,9 @@ describe('Authenticator', () => { ); expect(mockSessionStorage.set).not.toHaveBeenCalled(); + expect( + mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage + ).not.toHaveBeenCalled(); }); it('fails if cannot retrieve user session', async () => { @@ -1463,6 +1470,9 @@ describe('Authenticator', () => { ); expect(mockSessionStorage.set).not.toHaveBeenCalled(); + expect( + mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage + ).not.toHaveBeenCalled(); }); it('fails if license doesn allow access agreement acknowledgement', async () => { @@ -1477,6 +1487,9 @@ describe('Authenticator', () => { ); expect(mockSessionStorage.set).not.toHaveBeenCalled(); + expect( + mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage + ).not.toHaveBeenCalled(); }); it('properly acknowledges access agreement for the authenticated user', async () => { @@ -1493,6 +1506,10 @@ describe('Authenticator', () => { type: 'basic', name: 'basic1', }); + + expect( + mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage + ).toHaveBeenCalledTimes(1); }); }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 58dea2b23e546..ac5c2a72b9667 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -38,6 +38,7 @@ import { DeauthenticationResult } from './deauthentication_result'; import { Tokens } from './tokens'; import { canRedirectRequest } from './can_redirect_request'; import { HTTPAuthorizationHeader } from './http_authentication'; +import { SecurityFeatureUsageServiceStart } from '../feature_usage'; /** * The shape of the session that is actually stored in the cookie. @@ -94,6 +95,7 @@ export interface ProviderLoginAttempt { export interface AuthenticatorOptions { auditLogger: SecurityAuditLogger; + getFeatureUsageService: () => SecurityFeatureUsageServiceStart; getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; config: Pick; basePath: HttpServiceSetup['basePath']; @@ -475,7 +477,7 @@ export class Authenticator { * @param providerType Type of the provider (`basic`, `saml`, `pki` etc.). */ isProviderTypeEnabled(providerType: string) { - return [...this.providers.values()].some(provider => provider.type === providerType); + return [...this.providers.values()].some((provider) => provider.type === providerType); } /** @@ -502,6 +504,8 @@ export class Authenticator { currentUser.username, existingSession.provider ); + + this.options.getFeatureUsageService().recordPreAccessAgreementUsage(); } /** @@ -511,7 +515,7 @@ export class Authenticator { */ private setupHTTPAuthenticationProvider(options: AuthenticationProviderOptions) { const supportedSchemes = new Set( - this.options.config.authc.http.schemes.map(scheme => scheme.toLowerCase()) + this.options.config.authc.http.schemes.map((scheme) => scheme.toLowerCase()) ); // If `autoSchemesEnabled` is set we should allow schemes that other providers use to diff --git a/x-pack/plugins/security/server/authentication/index.test.ts b/x-pack/plugins/security/server/authentication/index.test.ts index 1c1e0ed781f18..c7323509c00d6 100644 --- a/x-pack/plugins/security/server/authentication/index.test.ts +++ b/x-pack/plugins/security/server/authentication/index.test.ts @@ -42,6 +42,8 @@ import { } from './api_keys'; import { SecurityLicense } from '../../common/licensing'; import { SecurityAuditLogger } from '../audit'; +import { SecurityFeatureUsageServiceStart } from '../feature_usage'; +import { securityFeatureUsageServiceMock } from '../feature_usage/index.mock'; describe('setupAuthentication()', () => { let mockSetupAuthenticationParams: { @@ -51,6 +53,7 @@ describe('setupAuthentication()', () => { http: jest.Mocked; clusterClient: jest.Mocked; license: jest.Mocked; + getFeatureUsageService: () => jest.Mocked; }; let mockScopedClusterClient: jest.Mocked>; beforeEach(() => { @@ -69,6 +72,9 @@ describe('setupAuthentication()', () => { clusterClient: elasticsearchServiceMock.createClusterClient(), license: licenseMock.create(), loggers: loggingServiceMock.create(), + getFeatureUsageService: jest + .fn() + .mockReturnValue(securityFeatureUsageServiceMock.createStartContract()), }; mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts index 779b852195b02..ec48c727a5739 100644 --- a/x-pack/plugins/security/server/authentication/index.ts +++ b/x-pack/plugins/security/server/authentication/index.ts @@ -17,6 +17,7 @@ import { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; import { Authenticator, ProviderSession } from './authenticator'; import { APIKeys, CreateAPIKeyParams, InvalidateAPIKeyParams } from './api_keys'; +import { SecurityFeatureUsageServiceStart } from '../feature_usage'; export { canRedirectRequest } from './can_redirect_request'; export { Authenticator, ProviderLoginAttempt } from './authenticator'; @@ -37,6 +38,7 @@ export { interface SetupAuthenticationParams { auditLogger: SecurityAuditLogger; + getFeatureUsageService: () => SecurityFeatureUsageServiceStart; http: CoreSetup['http']; clusterClient: IClusterClient; config: ConfigType; @@ -48,6 +50,7 @@ export type Authentication = UnwrapPromise scheme.toLowerCase()) + [...httpOptions.supportedSchemes].map((scheme) => scheme.toLowerCase()) ); } diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 6eb47cfa83e32..ca80761ee140c 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -280,11 +280,11 @@ describe('KerberosAuthenticationProvider', () => { } describe('`login` method', () => { - defineCommonLoginAndAuthenticateTests(request => provider.login(request)); + defineCommonLoginAndAuthenticateTests((request) => provider.login(request)); }); describe('`authenticate` method', () => { - defineCommonLoginAndAuthenticateTests(request => provider.authenticate(request, null)); + defineCommonLoginAndAuthenticateTests((request) => provider.authenticate(request, null)); it('does not handle authentication via `authorization` header with non-negotiate scheme.', async () => { const request = httpServerMock.createKibanaRequest({ @@ -376,7 +376,7 @@ describe('KerberosAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockOptions.client.asScoped.mockImplementation(scopeableRequest => { + mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index c4bbe554a3da1..2540c21210bd5 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -339,7 +339,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { private getNegotiateChallenge(error: ElasticsearchError) { const challenges = ([] as string[]).concat(error.output.headers[WWWAuthenticateHeaderName]); - const negotiateChallenge = challenges.find(challenge => + const negotiateChallenge = challenges.find((challenge) => challenge.toLowerCase().startsWith('negotiate') ); if (negotiateChallenge) { diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 14fe42aac7599..2d42d90ab60b8 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -464,7 +464,7 @@ describe('OIDCAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'expired-token', refreshToken: 'valid-refresh-token' }; - mockOptions.client.asScoped.mockImplementation(scopeableRequest => { + mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 638bb5732f3c0..28db64edd9e32 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -241,11 +241,11 @@ describe('PKIAuthenticationProvider', () => { } describe('`login` method', () => { - defineCommonLoginAndAuthenticateTests(request => provider.login(request)); + defineCommonLoginAndAuthenticateTests((request) => provider.login(request)); }); describe('`authenticate` method', () => { - defineCommonLoginAndAuthenticateTests(request => provider.authenticate(request, null)); + defineCommonLoginAndAuthenticateTests((request) => provider.authenticate(request, null)); it('does not handle authentication via `authorization` header.', async () => { const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index ec50ac090f1e7..461ad3e38eca5 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -835,7 +835,7 @@ describe('SAMLAuthenticationProvider', () => { realm: 'test-realm', }; - mockOptions.client.asScoped.mockImplementation(scopeableRequest => { + mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { if (scopeableRequest?.headers.authorization === `Bearer ${state.accessToken}`) { const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 5c5ec49890901..3161144023c1f 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -177,8 +177,9 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Login has been successfully performed.'); } else { this.logger.debug( - `Failed to perform a login: ${authenticationResult.error && - authenticationResult.error.message}` + `Failed to perform a login: ${ + authenticationResult.error && authenticationResult.error.message + }` ); } diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 7472adb30307c..92cea424e575d 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -211,7 +211,7 @@ describe('TokenAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockOptions.client.asScoped.mockImplementation(scopeableRequest => { + mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( @@ -386,7 +386,7 @@ describe('TokenAuthenticationProvider', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; const authenticationError = new errors.AuthenticationException('Some error'); - mockOptions.client.asScoped.mockImplementation(scopeableRequest => { + mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( diff --git a/x-pack/plugins/security/server/authorization/actions/ui.ts b/x-pack/plugins/security/server/authorization/actions/ui.ts index 3dae9a47b3827..fa5bea867e9cc 100644 --- a/x-pack/plugins/security/server/authorization/actions/ui.ts +++ b/x-pack/plugins/security/server/authorization/actions/ui.ts @@ -26,7 +26,7 @@ export class UIActions { if ( uiCapabilityParts.length === 0 || uiCapabilityParts.findIndex( - part => !part || !isString(part) || !uiCapabilitiesRegex.test(part) + (part) => !part || !isString(part) || !uiCapabilitiesRegex.test(part) ) >= 0 ) { throw new Error( diff --git a/x-pack/plugins/security/server/authorization/api_authorization.test.ts b/x-pack/plugins/security/server/authorization/api_authorization.test.ts index 409f998cfe8d2..183a36274142c 100644 --- a/x-pack/plugins/security/server/authorization/api_authorization.test.ts +++ b/x-pack/plugins/security/server/authorization/api_authorization.test.ts @@ -82,7 +82,7 @@ describe('initAPIAuthorization', () => { const mockCheckPrivileges = jest.fn().mockReturnValue({ hasAllRequested: true }); mockAuthz.mode.useRbacForRequest.mockReturnValue(true); - mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation(request => { + mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation((request) => { // hapi conceals the actual "request" from us, so we make sure that the headers are passed to // "checkPrivilegesDynamicallyWithRequest" because this is what we're really concerned with expect(request.headers).toMatchObject(headers); @@ -117,7 +117,7 @@ describe('initAPIAuthorization', () => { const mockCheckPrivileges = jest.fn().mockReturnValue({ hasAllRequested: false }); mockAuthz.mode.useRbacForRequest.mockReturnValue(true); - mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation(request => { + mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation((request) => { // hapi conceals the actual "request" from us, so we make sure that the headers are passed to // "checkPrivilegesDynamicallyWithRequest" because this is what we're really concerned with expect(request.headers).toMatchObject(headers); diff --git a/x-pack/plugins/security/server/authorization/api_authorization.ts b/x-pack/plugins/security/server/authorization/api_authorization.ts index 88b3f2c6f7155..0ffd3ba7ba823 100644 --- a/x-pack/plugins/security/server/authorization/api_authorization.ts +++ b/x-pack/plugins/security/server/authorization/api_authorization.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, Logger } from '../../../../../src/core/server'; -import { Authorization } from '.'; +import { HttpServiceSetup, Logger } from '../../../../../src/core/server'; +import { AuthorizationServiceSetup } from '.'; export function initAPIAuthorization( - http: CoreSetup['http'], - { actions, checkPrivilegesDynamicallyWithRequest, mode }: Authorization, + http: HttpServiceSetup, + { actions, checkPrivilegesDynamicallyWithRequest, mode }: AuthorizationServiceSetup, logger: Logger ) { http.registerOnPostAuth(async (request, response, toolkit) => { @@ -20,14 +20,14 @@ export function initAPIAuthorization( const tags = request.route.options.tags; const tagPrefix = 'access:'; - const actionTags = tags.filter(tag => tag.startsWith(tagPrefix)); + const actionTags = tags.filter((tag) => tag.startsWith(tagPrefix)); // if there are no tags starting with "access:", just continue if (actionTags.length === 0) { return toolkit.next(); } - const apiActions = actionTags.map(tag => actions.api.get(tag.substring(tagPrefix.length))); + const apiActions = actionTags.map((tag) => actions.api.get(tag.substring(tagPrefix.length))); const checkPrivileges = checkPrivilegesDynamicallyWithRequest(request); const checkPrivilegesResponse = await checkPrivileges(apiActions); diff --git a/x-pack/plugins/security/server/authorization/app_authorization.test.ts b/x-pack/plugins/security/server/authorization/app_authorization.test.ts index 6d23333022302..1dc56161d6363 100644 --- a/x-pack/plugins/security/server/authorization/app_authorization.test.ts +++ b/x-pack/plugins/security/server/authorization/app_authorization.test.ts @@ -117,7 +117,7 @@ describe('initAppAuthorization', () => { const mockCheckPrivileges = jest.fn().mockReturnValue({ hasAllRequested: true }); mockAuthz.mode.useRbacForRequest.mockReturnValue(true); - mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation(request => { + mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation((request) => { // hapi conceals the actual "request" from us, so we make sure that the headers are passed to // "checkPrivilegesDynamicallyWithRequest" because this is what we're really concerned with expect(request.headers).toMatchObject(headers); @@ -157,7 +157,7 @@ describe('initAppAuthorization', () => { const mockCheckPrivileges = jest.fn().mockReturnValue({ hasAllRequested: false }); mockAuthz.mode.useRbacForRequest.mockReturnValue(true); - mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation(request => { + mockAuthz.checkPrivilegesDynamicallyWithRequest.mockImplementation((request) => { // hapi conceals the actual "request" from us, so we make sure that the headers are passed to // "checkPrivilegesDynamicallyWithRequest" because this is what we're really concerned with expect(request.headers).toMatchObject(headers); diff --git a/x-pack/plugins/security/server/authorization/app_authorization.ts b/x-pack/plugins/security/server/authorization/app_authorization.ts index 8516e8228ab5a..1036997ca821d 100644 --- a/x-pack/plugins/security/server/authorization/app_authorization.ts +++ b/x-pack/plugins/security/server/authorization/app_authorization.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, Logger } from '../../../../../src/core/server'; -import { FeaturesService } from '../plugin'; -import { Authorization } from '.'; +import { HttpServiceSetup, Logger } from '../../../../../src/core/server'; +import { PluginSetupContract as FeaturesPluginSetup } from '../../../features/server'; +import { AuthorizationServiceSetup } from '.'; class ProtectedApplications { private applications: Set | null = null; - constructor(private readonly featuresService: FeaturesService) {} + constructor(private readonly featuresService: FeaturesPluginSetup) {} public shouldProtect(appId: string) { // Currently, once we get the list of features we essentially "lock" additional @@ -20,7 +20,7 @@ class ProtectedApplications { this.applications = new Set( this.featuresService .getFeatures() - .map(feature => feature.app) + .map((feature) => feature.app) .flat() ); } @@ -30,14 +30,14 @@ class ProtectedApplications { } export function initAppAuthorization( - http: CoreSetup['http'], + http: HttpServiceSetup, { actions, checkPrivilegesDynamicallyWithRequest, mode, - }: Pick, + }: Pick, logger: Logger, - featuresService: FeaturesService + featuresService: FeaturesPluginSetup ) { const protectedApplications = new ProtectedApplications(featuresService); diff --git a/x-pack/plugins/security/server/authorization/authorization_service.test.ts b/x-pack/plugins/security/server/authorization/authorization_service.test.ts new file mode 100644 index 0000000000000..978c985cfe820 --- /dev/null +++ b/x-pack/plugins/security/server/authorization/authorization_service.test.ts @@ -0,0 +1,263 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + mockAuthorizationModeFactory, + mockCheckPrivilegesDynamicallyWithRequestFactory, + mockCheckPrivilegesWithRequestFactory, + mockCheckSavedObjectsPrivilegesWithRequestFactory, + mockPrivilegesFactory, + mockRegisterPrivilegesWithCluster, +} from './service.test.mocks'; + +import { BehaviorSubject } from 'rxjs'; +import { CoreStatus, ServiceStatusLevels } from '../../../../../src/core/server'; +import { checkPrivilegesWithRequestFactory } from './check_privileges'; +import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically'; +import { checkSavedObjectsPrivilegesWithRequestFactory } from './check_saved_objects_privileges'; +import { authorizationModeFactory } from './mode'; +import { privilegesFactory } from './privileges'; +import { AuthorizationService } from '.'; + +import { + coreMock, + elasticsearchServiceMock, + loggingServiceMock, +} from '../../../../../src/core/server/mocks'; +import { featuresPluginMock } from '../../../features/server/mocks'; +import { licenseMock } from '../../common/licensing/index.mock'; +import { SecurityLicense, SecurityLicenseFeatures } from '../../common/licensing'; +import { nextTick } from 'test_utils/enzyme_helpers'; + +const kibanaIndexName = '.a-kibana-index'; +const application = `kibana-${kibanaIndexName}`; +const mockCheckPrivilegesWithRequest = Symbol(); +const mockCheckPrivilegesDynamicallyWithRequest = Symbol(); +const mockCheckSavedObjectsPrivilegesWithRequest = Symbol(); +const mockPrivilegesService = Symbol(); +const mockAuthorizationMode = Symbol(); +beforeEach(() => { + mockCheckPrivilegesWithRequestFactory.mockReturnValue(mockCheckPrivilegesWithRequest); + mockCheckPrivilegesDynamicallyWithRequestFactory.mockReturnValue( + mockCheckPrivilegesDynamicallyWithRequest + ); + mockCheckSavedObjectsPrivilegesWithRequestFactory.mockReturnValue( + mockCheckSavedObjectsPrivilegesWithRequest + ); + mockPrivilegesFactory.mockReturnValue(mockPrivilegesService); + mockAuthorizationModeFactory.mockReturnValue(mockAuthorizationMode); +}); + +afterEach(() => { + mockRegisterPrivilegesWithCluster.mockClear(); +}); + +it(`#setup returns exposed services`, () => { + const mockClusterClient = elasticsearchServiceMock.createClusterClient(); + const mockGetSpacesService = jest + .fn() + .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }); + const mockFeaturesSetup = featuresPluginMock.createSetup(); + const mockLicense = licenseMock.create(); + const mockCoreSetup = coreMock.createSetup(); + + const authorizationService = new AuthorizationService(); + const authz = authorizationService.setup({ + http: mockCoreSetup.http, + capabilities: mockCoreSetup.capabilities, + status: mockCoreSetup.status, + clusterClient: mockClusterClient, + license: mockLicense, + loggers: loggingServiceMock.create(), + kibanaIndexName, + packageVersion: 'some-version', + features: mockFeaturesSetup, + getSpacesService: mockGetSpacesService, + }); + + expect(authz.actions.version).toBe('version:some-version'); + expect(authz.applicationName).toBe(application); + + expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest); + expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith( + authz.actions, + mockClusterClient, + authz.applicationName + ); + + expect(authz.checkPrivilegesDynamicallyWithRequest).toBe( + mockCheckPrivilegesDynamicallyWithRequest + ); + expect(checkPrivilegesDynamicallyWithRequestFactory).toHaveBeenCalledWith( + mockCheckPrivilegesWithRequest, + mockGetSpacesService + ); + + expect(authz.checkSavedObjectsPrivilegesWithRequest).toBe( + mockCheckSavedObjectsPrivilegesWithRequest + ); + expect(checkSavedObjectsPrivilegesWithRequestFactory).toHaveBeenCalledWith( + mockCheckPrivilegesWithRequest, + mockGetSpacesService + ); + + expect(authz.privileges).toBe(mockPrivilegesService); + expect(privilegesFactory).toHaveBeenCalledWith(authz.actions, mockFeaturesSetup, mockLicense); + + expect(authz.mode).toBe(mockAuthorizationMode); + expect(authorizationModeFactory).toHaveBeenCalledWith(mockLicense); + + expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledTimes(1); + expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledWith(expect.any(Function)); +}); + +describe('#start', () => { + let statusSubject: BehaviorSubject; + let licenseSubject: BehaviorSubject; + let mockLicense: jest.Mocked; + beforeEach(() => { + const mockClusterClient = elasticsearchServiceMock.createClusterClient(); + + licenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures); + mockLicense = licenseMock.create(); + mockLicense.isEnabled.mockReturnValue(false); + mockLicense.features$ = licenseSubject; + + statusSubject = new BehaviorSubject({ + elasticsearch: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' }, + savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' }, + }); + const mockCoreSetup = coreMock.createSetup(); + mockCoreSetup.status.core$ = statusSubject; + + const authorizationService = new AuthorizationService(); + authorizationService.setup({ + http: mockCoreSetup.http, + capabilities: mockCoreSetup.capabilities, + status: mockCoreSetup.status, + clusterClient: mockClusterClient, + license: mockLicense, + loggers: loggingServiceMock.create(), + kibanaIndexName, + packageVersion: 'some-version', + features: featuresPluginMock.createSetup(), + getSpacesService: jest + .fn() + .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }), + }); + + const featuresStart = featuresPluginMock.createStart(); + featuresStart.getFeatures.mockReturnValue([]); + + authorizationService.start({ clusterClient: mockClusterClient, features: featuresStart }); + + // ES and license aren't available yet. + expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled(); + }); + + it('registers cluster privileges', async () => { + // ES is available now, but not license. + statusSubject.next({ + elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' }, + savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' }, + }); + expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled(); + + // Both ES and license are available now. + mockLicense.isEnabled.mockReturnValue(true); + licenseSubject.next(({} as unknown) as SecurityLicenseFeatures); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1); + + await nextTick(); + + // New changes still trigger privileges re-registration. + licenseSubject.next(({} as unknown) as SecurityLicenseFeatures); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2); + }); + + it('schedules retries if fails to register cluster privileges', async () => { + jest.useFakeTimers(); + + mockRegisterPrivilegesWithCluster.mockRejectedValue(new Error('Some error')); + + // Both ES and license are available. + mockLicense.isEnabled.mockReturnValue(true); + statusSubject.next({ + elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' }, + savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' }, + }); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1); + + // Next retry isn't performed immediately, retry happens only after a timeout. + await nextTick(); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1); + jest.advanceTimersByTime(100); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2); + + // Delay between consequent retries is increasing. + await nextTick(); + jest.advanceTimersByTime(100); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2); + await nextTick(); + jest.advanceTimersByTime(100); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(3); + + // When call finally succeeds retries aren't scheduled anymore. + mockRegisterPrivilegesWithCluster.mockResolvedValue(undefined); + await nextTick(); + jest.runAllTimers(); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(4); + await nextTick(); + jest.runAllTimers(); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(4); + + // New changes still trigger privileges re-registration. + licenseSubject.next(({} as unknown) as SecurityLicenseFeatures); + expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(5); + }); +}); + +it('#stop unsubscribes from license and ES updates.', () => { + const mockClusterClient = elasticsearchServiceMock.createClusterClient(); + + const licenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures); + const mockLicense = licenseMock.create(); + mockLicense.isEnabled.mockReturnValue(false); + mockLicense.features$ = licenseSubject; + + const mockCoreSetup = coreMock.createSetup(); + mockCoreSetup.status.core$ = new BehaviorSubject({ + elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' }, + savedObjects: { level: ServiceStatusLevels.available, summary: 'Service is working' }, + }); + + const authorizationService = new AuthorizationService(); + authorizationService.setup({ + http: mockCoreSetup.http, + capabilities: mockCoreSetup.capabilities, + status: mockCoreSetup.status, + clusterClient: mockClusterClient, + license: mockLicense, + loggers: loggingServiceMock.create(), + kibanaIndexName, + packageVersion: 'some-version', + features: featuresPluginMock.createSetup(), + getSpacesService: jest + .fn() + .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }), + }); + + const featuresStart = featuresPluginMock.createStart(); + featuresStart.getFeatures.mockReturnValue([]); + authorizationService.start({ clusterClient: mockClusterClient, features: featuresStart }); + + authorizationService.stop(); + + // After stop we don't register privileges even if all requirements are met. + mockLicense.isEnabled.mockReturnValue(true); + licenseSubject.next(({} as unknown) as SecurityLicenseFeatures); + expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled(); +}); diff --git a/x-pack/plugins/security/server/authorization/authorization_service.ts b/x-pack/plugins/security/server/authorization/authorization_service.ts new file mode 100644 index 0000000000000..989784a1436d2 --- /dev/null +++ b/x-pack/plugins/security/server/authorization/authorization_service.ts @@ -0,0 +1,221 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { combineLatest, BehaviorSubject, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter } from 'rxjs/operators'; +import { UICapabilities } from 'ui/capabilities'; +import { + LoggerFactory, + KibanaRequest, + IClusterClient, + ServiceStatusLevels, + Logger, + StatusServiceSetup, + HttpServiceSetup, + CapabilitiesSetup, +} from '../../../../../src/core/server'; + +import { + PluginSetupContract as FeaturesPluginSetup, + PluginStartContract as FeaturesPluginStart, +} from '../../../features/server'; + +import { SpacesService } from '../plugin'; +import { Actions } from './actions'; +import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges'; +import { + CheckPrivilegesDynamicallyWithRequest, + checkPrivilegesDynamicallyWithRequestFactory, +} from './check_privileges_dynamically'; +import { + CheckSavedObjectsPrivilegesWithRequest, + checkSavedObjectsPrivilegesWithRequestFactory, +} from './check_saved_objects_privileges'; +import { AuthorizationMode, authorizationModeFactory } from './mode'; +import { privilegesFactory, PrivilegesService } from './privileges'; +import { initAppAuthorization } from './app_authorization'; +import { initAPIAuthorization } from './api_authorization'; +import { disableUICapabilitiesFactory } from './disable_ui_capabilities'; +import { validateFeaturePrivileges } from './validate_feature_privileges'; +import { validateReservedPrivileges } from './validate_reserved_privileges'; +import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; +import { APPLICATION_PREFIX } from '../../common/constants'; +import { SecurityLicense } from '../../common/licensing'; + +export { Actions } from './actions'; +export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges'; +export { featurePrivilegeIterator } from './privileges'; + +interface AuthorizationServiceSetupParams { + packageVersion: string; + http: HttpServiceSetup; + status: StatusServiceSetup; + capabilities: CapabilitiesSetup; + clusterClient: IClusterClient; + license: SecurityLicense; + loggers: LoggerFactory; + features: FeaturesPluginSetup; + kibanaIndexName: string; + getSpacesService(): SpacesService | undefined; +} + +interface AuthorizationServiceStartParams { + features: FeaturesPluginStart; + clusterClient: IClusterClient; +} + +export interface AuthorizationServiceSetup { + actions: Actions; + checkPrivilegesWithRequest: CheckPrivilegesWithRequest; + checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest; + checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest; + applicationName: string; + mode: AuthorizationMode; + privileges: PrivilegesService; +} + +export class AuthorizationService { + private logger!: Logger; + private license!: SecurityLicense; + private status!: StatusServiceSetup; + private applicationName!: string; + private privileges!: PrivilegesService; + + private statusSubscription?: Subscription; + + setup({ + http, + capabilities, + status, + packageVersion, + clusterClient, + license, + loggers, + features, + kibanaIndexName, + getSpacesService, + }: AuthorizationServiceSetupParams): AuthorizationServiceSetup { + this.logger = loggers.get('authorization'); + this.license = license; + this.status = status; + this.applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`; + + const mode = authorizationModeFactory(license); + const actions = new Actions(packageVersion); + this.privileges = privilegesFactory(actions, features, license); + + const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( + actions, + clusterClient, + this.applicationName + ); + + const authz = { + actions, + applicationName: this.applicationName, + mode, + privileges: this.privileges, + checkPrivilegesWithRequest, + checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory( + checkPrivilegesWithRequest, + getSpacesService + ), + checkSavedObjectsPrivilegesWithRequest: checkSavedObjectsPrivilegesWithRequestFactory( + checkPrivilegesWithRequest, + getSpacesService + ), + }; + + capabilities.registerSwitcher( + async (request: KibanaRequest, uiCapabilities: UICapabilities) => { + // If we have a license which doesn't enable security, or we're a legacy user we shouldn't + // disable any ui capabilities + if (!mode.useRbacForRequest(request)) { + return uiCapabilities; + } + + const disableUICapabilities = disableUICapabilitiesFactory( + request, + features.getFeatures(), + this.logger, + authz + ); + + if (!request.auth.isAuthenticated) { + return disableUICapabilities.all(uiCapabilities); + } + + return await disableUICapabilities.usingPrivileges(uiCapabilities); + } + ); + + initAPIAuthorization(http, authz, loggers.get('api-authorization')); + initAppAuthorization(http, authz, loggers.get('app-authorization'), features); + + return authz; + } + + start({ clusterClient, features }: AuthorizationServiceStartParams) { + const allFeatures = features.getFeatures(); + validateFeaturePrivileges(allFeatures); + validateReservedPrivileges(allFeatures); + + this.registerPrivileges(clusterClient); + } + + stop() { + if (this.statusSubscription !== undefined) { + this.statusSubscription.unsubscribe(); + this.statusSubscription = undefined; + } + } + + private registerPrivileges(clusterClient: IClusterClient) { + const RETRY_SCALE_DURATION = 100; + const RETRY_TIMEOUT_MAX = 10000; + const retries$ = new BehaviorSubject(0); + let retryTimeout: NodeJS.Timeout; + + // Register cluster privileges once Elasticsearch is available and Security plugin is enabled. + this.statusSubscription = combineLatest([ + this.status.core$, + this.license.features$, + retries$.asObservable().pipe( + // We shouldn't emit new value if retry counter is reset. This comparator isn't called for + // the initial value. + distinctUntilChanged((prev, curr) => prev === curr || curr === 0) + ), + ]) + .pipe( + filter( + ([status]) => + this.license.isEnabled() && status.elasticsearch.level === ServiceStatusLevels.available + ) + ) + .subscribe(async () => { + // If status or license change occurred before retry timeout we should cancel it. + if (retryTimeout) { + clearTimeout(retryTimeout); + } + + try { + await registerPrivilegesWithCluster( + this.logger, + this.privileges, + this.applicationName, + clusterClient + ); + retries$.next(0); + } catch (err) { + const retriesElapsed = retries$.getValue() + 1; + retryTimeout = setTimeout( + () => retries$.next(retriesElapsed), + Math.min(retriesElapsed * RETRY_SCALE_DURATION, RETRY_TIMEOUT_MAX) + ); + } + }); + } +} diff --git a/x-pack/plugins/security/server/authorization/check_privileges.test.ts b/x-pack/plugins/security/server/authorization/check_privileges.test.ts index a64c5d509ca11..65a3d1bf1650b 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.test.ts @@ -333,7 +333,7 @@ describe('#atSpaces', () => { applications: [ { application, - resources: options.spaceIds.map(spaceId => `space:${spaceId}`), + resources: options.spaceIds.map((spaceId) => `space:${spaceId}`), privileges: uniq([ mockActions.version, mockActions.login, diff --git a/x-pack/plugins/security/server/authorization/check_privileges.ts b/x-pack/plugins/security/server/authorization/check_privileges.ts index 177a49d6defe9..44e9438bd07f5 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.ts @@ -52,7 +52,7 @@ export function checkPrivilegesWithRequestFactory( applicationPrivilegesResponse: HasPrivilegesResponseApplication ) => { return Object.values(applicationPrivilegesResponse).some( - resource => !resource[actions.version] && resource[actions.login] + (resource) => !resource[actions.version] && resource[actions.login] ); }; @@ -121,7 +121,7 @@ export function checkPrivilegesWithRequestFactory( return await checkPrivilegesAtResources([spaceResource], privilegeOrPrivileges); }, async atSpaces(spaceIds: string[], privilegeOrPrivileges: string | string[]) { - const spaceResources = spaceIds.map(spaceId => + const spaceResources = spaceIds.map((spaceId) => ResourceSerializer.serializeSpaceResource(spaceId) ); return await checkPrivilegesAtResources(spaceResources, privilegeOrPrivileges); diff --git a/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.test.ts b/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.test.ts index 43b3824500579..4ab00b511b48b 100644 --- a/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.test.ts @@ -78,7 +78,7 @@ describe('#checkSavedObjectsPrivileges', () => { expect(mockCheckPrivilegesWithRequest).toHaveBeenCalledTimes(1); expect(mockCheckPrivilegesWithRequest).toHaveBeenCalledWith(request); expect(mockCheckPrivileges.atSpaces).toHaveBeenCalledTimes(1); - const spaceIds = mockSpacesService!.namespaceToSpaceId.mock.results.map(x => x.value); + const spaceIds = mockSpacesService!.namespaceToSpaceId.mock.results.map((x) => x.value); expect(mockCheckPrivileges.atSpaces).toHaveBeenCalledWith(spaceIds, actions); }); }); diff --git a/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.ts b/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.ts index 43140143a1773..d9b070c72f946 100644 --- a/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.ts +++ b/x-pack/plugins/security/server/authorization/check_saved_objects_privileges.ts @@ -37,7 +37,7 @@ export const checkSavedObjectsPrivilegesWithRequestFactory = ( } else if (!namespaceOrNamespaces.length) { throw new Error(`Can't check saved object privileges for 0 namespaces`); } - const spaceIds = namespaceOrNamespaces.map(x => spacesService.namespaceToSpaceId(x)); + const spaceIds = namespaceOrNamespaces.map((x) => spacesService.namespaceToSpaceId(x)); return await checkPrivilegesWithRequest(request).atSpaces(spaceIds, actions); } else if (spacesService) { const spaceId = spacesService.namespaceToSpaceId(namespaceOrNamespaces); diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts index ea97a1b3b590c..082484d5fa6b4 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts @@ -20,15 +20,15 @@ const mockRequest = httpServerMock.createKibanaRequest(); const createMockAuthz = (options: MockAuthzOptions) => { const mock = authorizationMock.create({ version: '1.0.0-zeta1' }); - mock.checkPrivilegesDynamicallyWithRequest.mockImplementation(request => { + mock.checkPrivilegesDynamicallyWithRequest.mockImplementation((request) => { expect(request).toBe(mockRequest); - return jest.fn().mockImplementation(checkActions => { + return jest.fn().mockImplementation((checkActions) => { if ('rejectCheckPrivileges' in options) { throw options.rejectCheckPrivileges; } - const expected = options.resolveCheckPrivileges.privileges.map(x => x.privilege); + const expected = options.resolveCheckPrivileges.privileges.map((x) => x.privilege); expect(checkActions).toEqual(expected); return options.resolveCheckPrivileges; }); diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index f0f1a42ad0bd5..183ad9169a123 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -10,17 +10,17 @@ import { KibanaRequest, Logger } from '../../../../../src/core/server'; import { Feature } from '../../../features/server'; import { CheckPrivilegesResponse } from './check_privileges'; -import { Authorization } from './index'; +import { AuthorizationServiceSetup } from '.'; export function disableUICapabilitiesFactory( request: KibanaRequest, features: Feature[], logger: Logger, - authz: Authorization + authz: AuthorizationServiceSetup ) { const featureNavLinkIds = features - .map(feature => feature.navLinkId) - .filter(navLinkId => navLinkId != null); + .map((feature) => feature.navLinkId) + .filter((navLinkId) => navLinkId != null); const shouldDisableFeatureUICapability = ( featureId: keyof UICapabilities, @@ -60,7 +60,9 @@ export function disableUICapabilitiesFactory( return [authz.actions.ui.get(featureId, uiCapability)]; } if (isObject(value)) { - return Object.keys(value).map(item => authz.actions.ui.get(featureId, uiCapability, item)); + return Object.keys(value).map((item) => + authz.actions.ui.get(featureId, uiCapability, item) + ); } throw new Error(`Expected value type of boolean or object, but found ${value}`); } @@ -106,7 +108,7 @@ export function disableUICapabilitiesFactory( const action = authz.actions.ui.get(featureId, ...uiCapabilityParts); return checkPrivilegesResponse.privileges.some( - x => x.privilege === action && x.authorized === true + (x) => x.privilege === action && x.authorized === true ); }; diff --git a/x-pack/plugins/security/server/authorization/index.test.ts b/x-pack/plugins/security/server/authorization/index.test.ts deleted file mode 100644 index 3252053454764..0000000000000 --- a/x-pack/plugins/security/server/authorization/index.test.ts +++ /dev/null @@ -1,100 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - mockAuthorizationModeFactory, - mockCheckPrivilegesDynamicallyWithRequestFactory, - mockCheckPrivilegesWithRequestFactory, - mockCheckSavedObjectsPrivilegesWithRequestFactory, - mockPrivilegesFactory, -} from './service.test.mocks'; - -import { checkPrivilegesWithRequestFactory } from './check_privileges'; -import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically'; -import { checkSavedObjectsPrivilegesWithRequestFactory } from './check_saved_objects_privileges'; -import { authorizationModeFactory } from './mode'; -import { privilegesFactory } from './privileges'; -import { setupAuthorization } from '.'; - -import { - coreMock, - elasticsearchServiceMock, - loggingServiceMock, -} from '../../../../../src/core/server/mocks'; -import { licenseMock } from '../../common/licensing/index.mock'; - -test(`returns exposed services`, () => { - const kibanaIndexName = '.a-kibana-index'; - const application = `kibana-${kibanaIndexName}`; - - const mockCheckPrivilegesWithRequest = Symbol(); - mockCheckPrivilegesWithRequestFactory.mockReturnValue(mockCheckPrivilegesWithRequest); - - const mockCheckPrivilegesDynamicallyWithRequest = Symbol(); - mockCheckPrivilegesDynamicallyWithRequestFactory.mockReturnValue( - mockCheckPrivilegesDynamicallyWithRequest - ); - - const mockCheckSavedObjectsPrivilegesWithRequest = Symbol(); - mockCheckSavedObjectsPrivilegesWithRequestFactory.mockReturnValue( - mockCheckSavedObjectsPrivilegesWithRequest - ); - - const mockPrivilegesService = Symbol(); - mockPrivilegesFactory.mockReturnValue(mockPrivilegesService); - const mockAuthorizationMode = Symbol(); - mockAuthorizationModeFactory.mockReturnValue(mockAuthorizationMode); - - const mockClusterClient = elasticsearchServiceMock.createClusterClient(); - const mockGetSpacesService = jest - .fn() - .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }); - const mockFeaturesService = { getFeatures: () => [] }; - const mockLicense = licenseMock.create(); - - const authz = setupAuthorization({ - http: coreMock.createSetup().http, - clusterClient: mockClusterClient, - license: mockLicense, - loggers: loggingServiceMock.create(), - kibanaIndexName, - packageVersion: 'some-version', - featuresService: mockFeaturesService, - getSpacesService: mockGetSpacesService, - }); - - expect(authz.actions.version).toBe('version:some-version'); - expect(authz.applicationName).toBe(application); - - expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest); - expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith( - authz.actions, - mockClusterClient, - authz.applicationName - ); - - expect(authz.checkPrivilegesDynamicallyWithRequest).toBe( - mockCheckPrivilegesDynamicallyWithRequest - ); - expect(checkPrivilegesDynamicallyWithRequestFactory).toHaveBeenCalledWith( - mockCheckPrivilegesWithRequest, - mockGetSpacesService - ); - - expect(authz.checkSavedObjectsPrivilegesWithRequest).toBe( - mockCheckSavedObjectsPrivilegesWithRequest - ); - expect(checkSavedObjectsPrivilegesWithRequestFactory).toHaveBeenCalledWith( - mockCheckPrivilegesWithRequest, - mockGetSpacesService - ); - - expect(authz.privileges).toBe(mockPrivilegesService); - expect(privilegesFactory).toHaveBeenCalledWith(authz.actions, mockFeaturesService, mockLicense); - - expect(authz.mode).toBe(mockAuthorizationMode); - expect(authorizationModeFactory).toHaveBeenCalledWith(mockLicense); -}); diff --git a/x-pack/plugins/security/server/authorization/index.ts b/x-pack/plugins/security/server/authorization/index.ts index cf970a561b93f..d5c1323354f86 100644 --- a/x-pack/plugins/security/server/authorization/index.ts +++ b/x-pack/plugins/security/server/authorization/index.ts @@ -4,134 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UICapabilities } from 'ui/capabilities'; -import { - CoreSetup, - LoggerFactory, - KibanaRequest, - IClusterClient, -} from '../../../../../src/core/server'; - -import { FeaturesService, SpacesService } from '../plugin'; -import { Actions } from './actions'; -import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges'; -import { - CheckPrivilegesDynamicallyWithRequest, - checkPrivilegesDynamicallyWithRequestFactory, -} from './check_privileges_dynamically'; -import { - CheckSavedObjectsPrivilegesWithRequest, - checkSavedObjectsPrivilegesWithRequestFactory, -} from './check_saved_objects_privileges'; -import { AuthorizationMode, authorizationModeFactory } from './mode'; -import { privilegesFactory, PrivilegesService } from './privileges'; -import { initAppAuthorization } from './app_authorization'; -import { initAPIAuthorization } from './api_authorization'; -import { disableUICapabilitiesFactory } from './disable_ui_capabilities'; -import { validateFeaturePrivileges } from './validate_feature_privileges'; -import { validateReservedPrivileges } from './validate_reserved_privileges'; -import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; -import { APPLICATION_PREFIX } from '../../common/constants'; -import { SecurityLicense } from '../../common/licensing'; - export { Actions } from './actions'; +export { AuthorizationService, AuthorizationServiceSetup } from './authorization_service'; export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges'; export { featurePrivilegeIterator } from './privileges'; - -interface SetupAuthorizationParams { - packageVersion: string; - http: CoreSetup['http']; - clusterClient: IClusterClient; - license: SecurityLicense; - loggers: LoggerFactory; - featuresService: FeaturesService; - kibanaIndexName: string; - getSpacesService(): SpacesService | undefined; -} - -export interface Authorization { - actions: Actions; - checkPrivilegesWithRequest: CheckPrivilegesWithRequest; - checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest; - checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest; - applicationName: string; - mode: AuthorizationMode; - privileges: PrivilegesService; - disableUnauthorizedCapabilities: ( - request: KibanaRequest, - capabilities: UICapabilities - ) => Promise; - registerPrivilegesWithCluster: () => Promise; -} - -export function setupAuthorization({ - http, - packageVersion, - clusterClient, - license, - loggers, - featuresService, - kibanaIndexName, - getSpacesService, -}: SetupAuthorizationParams): Authorization { - const actions = new Actions(packageVersion); - const mode = authorizationModeFactory(license); - const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`; - const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( - actions, - clusterClient, - applicationName - ); - const privileges = privilegesFactory(actions, featuresService, license); - const logger = loggers.get('authorization'); - - const authz = { - actions, - applicationName, - checkPrivilegesWithRequest, - checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory( - checkPrivilegesWithRequest, - getSpacesService - ), - checkSavedObjectsPrivilegesWithRequest: checkSavedObjectsPrivilegesWithRequestFactory( - checkPrivilegesWithRequest, - getSpacesService - ), - mode, - privileges, - - async disableUnauthorizedCapabilities(request: KibanaRequest, capabilities: UICapabilities) { - // If we have a license which doesn't enable security, or we're a legacy user we shouldn't - // disable any ui capabilities - if (!mode.useRbacForRequest(request)) { - return capabilities; - } - - const disableUICapabilities = disableUICapabilitiesFactory( - request, - featuresService.getFeatures(), - logger, - authz - ); - - if (!request.auth.isAuthenticated) { - return disableUICapabilities.all(capabilities); - } - - return await disableUICapabilities.usingPrivileges(capabilities); - }, - - registerPrivilegesWithCluster: async () => { - const features = featuresService.getFeatures(); - validateFeaturePrivileges(features); - validateReservedPrivileges(features); - - await registerPrivilegesWithCluster(logger, privileges, applicationName, clusterClient); - }, - }; - - initAPIAuthorization(http, authz, loggers.get('api-authorization')); - initAppAuthorization(http, authz, loggers.get('app-authorization'), featuresService); - - return authz; -} diff --git a/x-pack/plugins/security/server/authorization/privilege_serializer.test.ts b/x-pack/plugins/security/server/authorization/privilege_serializer.test.ts index ecfe0d34fdbcb..b35bfe9b0a271 100644 --- a/x-pack/plugins/security/server/authorization/privilege_serializer.test.ts +++ b/x-pack/plugins/security/server/authorization/privilege_serializer.test.ts @@ -7,14 +7,14 @@ import { PrivilegeSerializer } from './privilege_serializer'; describe(`#isSerializedGlobalBasePrivilege`, () => { - ['all', 'read'].forEach(validValue => { + ['all', 'read'].forEach((validValue) => { test(`returns true for '${validValue}'`, () => { expect(PrivilegeSerializer.isSerializedGlobalBasePrivilege(validValue)).toBe(true); }); }); ['space_all', 'space_read', 'foo', 'bar', 'feature_foo', 'feature_foo.privilege1'].forEach( - invalidValue => { + (invalidValue) => { test(`returns false for '${invalidValue}'`, () => { expect(PrivilegeSerializer.isSerializedGlobalBasePrivilege(invalidValue)).toBe(false); }); @@ -23,13 +23,13 @@ describe(`#isSerializedGlobalBasePrivilege`, () => { }); describe(`#isSerializedSpaceBasePrivilege`, () => { - ['space_all', 'space_read'].forEach(validValue => { + ['space_all', 'space_read'].forEach((validValue) => { test(`returns true for '${validValue}'`, () => { expect(PrivilegeSerializer.isSerializedSpaceBasePrivilege(validValue)).toBe(true); }); }); - ['all', 'read', 'foo', 'bar', 'feature_foo', 'feature_foo.privilege1'].forEach(invalid => { + ['all', 'read', 'foo', 'bar', 'feature_foo', 'feature_foo.privilege1'].forEach((invalid) => { test(`returns false for '${invalid}'`, () => { expect(PrivilegeSerializer.isSerializedSpaceBasePrivilege(invalid)).toBe(false); }); @@ -37,7 +37,7 @@ describe(`#isSerializedSpaceBasePrivilege`, () => { }); describe(`#isSerializedReservedPrivilege`, () => { - ['reserved_foo', 'reserved_bar'].forEach(validValue => { + ['reserved_foo', 'reserved_bar'].forEach((validValue) => { test(`returns true for '${validValue}'`, () => { expect(PrivilegeSerializer.isSerializedReservedPrivilege(validValue)).toBe(true); }); @@ -52,7 +52,7 @@ describe(`#isSerializedReservedPrivilege`, () => { 'bar', 'feature_foo', 'feature_foo.privilege1', - ].forEach(invalidValue => { + ].forEach((invalidValue) => { test(`returns false for '${invalidValue}'`, () => { expect(PrivilegeSerializer.isSerializedReservedPrivilege(invalidValue)).toBe(false); }); @@ -60,14 +60,14 @@ describe(`#isSerializedReservedPrivilege`, () => { }); describe(`#isSerializedFeaturePrivilege`, () => { - ['feature_foo.privilege1', 'feature_bar.privilege2'].forEach(validValue => { + ['feature_foo.privilege1', 'feature_bar.privilege2'].forEach((validValue) => { test(`returns true for '${validValue}'`, () => { expect(PrivilegeSerializer.isSerializedFeaturePrivilege(validValue)).toBe(true); }); }); ['all', 'read', 'space_all', 'space_read', 'reserved_foo', 'reserved_bar'].forEach( - invalidValue => { + (invalidValue) => { test(`returns false for '${invalidValue}'`, () => { expect(PrivilegeSerializer.isSerializedFeaturePrivilege(invalidValue)).toBe(false); }); @@ -154,7 +154,7 @@ describe('#deserializeFeaturePrivilege', () => { 'feature_foo_privilege-1', // no '.' 'feature_foo.', // has a '.' but nothing after it 'feature_.privilege-1', // nothing before the '.' - ].forEach(privilege => { + ].forEach((privilege) => { test(`throws error when deserializing ${privilege}`, () => { expect(() => PrivilegeSerializer.deserializeFeaturePrivilege(privilege) diff --git a/x-pack/plugins/security/server/authorization/privilege_serializer.ts b/x-pack/plugins/security/server/authorization/privilege_serializer.ts index 2bbebaa1cc951..bc5bf81c2d429 100644 --- a/x-pack/plugins/security/server/authorization/privilege_serializer.ts +++ b/x-pack/plugins/security/server/authorization/privilege_serializer.ts @@ -10,7 +10,7 @@ const reservedPrefix = 'reserved_'; const basePrivilegeNames = ['all', 'read']; const globalBasePrivileges = [...basePrivilegeNames]; const spaceBasePrivileges = basePrivilegeNames.map( - privilegeName => `${spacePrefix}${privilegeName}` + (privilegeName) => `${spacePrefix}${privilegeName}` ); const deserializeFeaturePrivilegeRegexp = new RegExp( `^${featurePrefix}([a-zA-Z0-9_-]+)\\.([a-zA-Z0-9_-]+)$` diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/api.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/api.ts index b13132f6efbe5..6b7d94bb0127e 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/api.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/api.ts @@ -10,7 +10,7 @@ import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeApiBuilder extends BaseFeaturePrivilegeBuilder { public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { if (privilegeDefinition.api) { - return privilegeDefinition.api.map(operation => this.actions.api.get(operation)); + return privilegeDefinition.api.map((operation) => this.actions.api.get(operation)); } return []; diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/app.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/app.ts index 514d6734b47ba..213aa83f2d26e 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/app.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/app.ts @@ -15,6 +15,6 @@ export class FeaturePrivilegeAppBuilder extends BaseFeaturePrivilegeBuilder { return []; } - return appIds.map(appId => this.actions.app.get(appId)); + return appIds.map((appId) => this.actions.app.get(appId)); } } diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/catalogue.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/catalogue.ts index fc15aff32b975..f1ea7091b9481 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/catalogue.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/catalogue.ts @@ -15,7 +15,7 @@ export class FeaturePrivilegeCatalogueBuilder extends BaseFeaturePrivilegeBuilde return []; } - return catalogueEntries.map(catalogueEntryId => + return catalogueEntries.map((catalogueEntryId) => this.actions.ui.get('catalogue', catalogueEntryId) ); } diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts index c293319070419..3d6dfbdac0251 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts @@ -30,7 +30,7 @@ export const featurePrivilegeBuilderFactory = (actions: Actions): FeaturePrivile return { getActions(privilege: FeatureKibanaPrivileges, feature: Feature) { - return flatten(builders.map(builder => builder.getActions(privilege, feature))); + return flatten(builders.map((builder) => builder.getActions(privilege, feature))); }, }; }; diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts index 7a2bb87d72b45..be784949dc2fa 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts @@ -16,7 +16,7 @@ export class FeaturePrivilegeManagementBuilder extends BaseFeaturePrivilegeBuild } return Object.entries(managementSections).reduce((acc, [sectionId, items]) => { - return [...acc, ...items.map(item => this.actions.ui.get('management', sectionId, item))]; + return [...acc, ...items.map((item) => this.actions.ui.get('management', sectionId, item))]; }, [] as string[]); } } diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/saved_object.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/saved_object.ts index 9baa8dadc2923..2c325fc8c6cb7 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/saved_object.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/saved_object.ts @@ -16,13 +16,13 @@ export class FeaturePrivilegeSavedObjectBuilder extends BaseFeaturePrivilegeBuil public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { return uniq([ ...flatten( - privilegeDefinition.savedObject.all.map(type => [ - ...allOperations.map(operation => this.actions.savedObject.get(type, operation)), + privilegeDefinition.savedObject.all.map((type) => [ + ...allOperations.map((operation) => this.actions.savedObject.get(type, operation)), ]) ), ...flatten( - privilegeDefinition.savedObject.read.map(type => [ - ...readOperations.map(operation => this.actions.savedObject.get(type, operation)), + privilegeDefinition.savedObject.read.map((type) => [ + ...readOperations.map((operation) => this.actions.savedObject.get(type, operation)), ]) ), ]); diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/ui.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/ui.ts index 28a22285c2b8f..31bc351206e54 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/ui.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/ui.ts @@ -9,6 +9,6 @@ import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeUIBuilder extends BaseFeaturePrivilegeBuilder { public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { - return privilegeDefinition.ui.map(ui => this.actions.ui.get(feature.id, ui)); + return privilegeDefinition.ui.map((ui) => this.actions.ui.get(feature.id, ui)); } } diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts index 7d92eacfe6b35..485783253d29d 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts @@ -140,7 +140,7 @@ describe('featurePrivilegeIterator', () => { const actualPrivileges = Array.from( featurePrivilegeIterator(feature, { augmentWithSubFeaturePrivileges: true, - predicate: privilegeId => privilegeId === 'all', + predicate: (privilegeId) => privilegeId === 'all', }) ); diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts index b023c12d35b79..06f064a379fe6 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts @@ -8,6 +8,8 @@ import { Feature } from '../../../../features/server'; import { Actions } from '../actions'; import { privilegesFactory } from './privileges'; +import { featuresPluginMock } from '../../../../features/server/mocks'; + const actions = new Actions('1.0.0-zeta1'); describe('features', () => { @@ -42,7 +44,9 @@ describe('features', () => { }), ]; - const mockFeaturesService = { getFeatures: jest.fn().mockReturnValue(features) }; + const mockFeaturesService = featuresPluginMock.createSetup(); + mockFeaturesService.getFeatures.mockReturnValue(features); + const mockLicenseService = { getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), }; diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.ts index 9a8935f80a174..5a15290a7f1a2 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.ts @@ -6,11 +6,10 @@ import { uniq } from 'lodash'; import { SecurityLicense } from '../../../common/licensing'; -import { Feature } from '../../../../features/server'; +import { Feature, PluginSetupContract as FeaturesPluginSetup } from '../../../../features/server'; import { RawKibanaPrivileges } from '../../../common/model'; import { Actions } from '../actions'; import { featurePrivilegeBuilderFactory } from './feature_privilege_builder'; -import { FeaturesService } from '../../plugin'; import { featurePrivilegeIterator, subFeaturePrivilegeIterator, @@ -22,7 +21,7 @@ export interface PrivilegesService { export function privilegesFactory( actions: Actions, - featuresService: FeaturesService, + featuresService: FeaturesPluginSetup, licenseService: Pick ) { const featurePrivilegeBuilder = featurePrivilegeBuilderFactory(actions); @@ -31,12 +30,14 @@ export function privilegesFactory( get() { const features = featuresService.getFeatures(); const { allowSubFeaturePrivileges } = licenseService.getFeatures(); - const basePrivilegeFeatures = features.filter(feature => !feature.excludeFromBasePrivileges); + const basePrivilegeFeatures = features.filter( + (feature) => !feature.excludeFromBasePrivileges + ); let allActions: string[] = []; let readActions: string[] = []; - basePrivilegeFeatures.forEach(feature => { + basePrivilegeFeatures.forEach((feature) => { for (const { privilegeId, privilege } of featurePrivilegeIterator(feature, { augmentWithSubFeaturePrivileges: true, predicate: (pId, featurePrivilege) => !featurePrivilege.excludeFromBasePrivileges, @@ -110,7 +111,7 @@ export function privilegesFactory( }, reserved: features.reduce((acc: Record, feature: Feature) => { if (feature.reserved) { - feature.reserved.privileges.forEach(reservedPrivilege => { + feature.reserved.privileges.forEach((reservedPrivilege) => { acc[reservedPrivilege.id] = [ actions.version, ...uniq(featurePrivilegeBuilder.getActions(reservedPrivilege.privilege, feature)), diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts index dc406c17925dd..e21203e60b887 100644 --- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts +++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts @@ -49,7 +49,7 @@ const registerPrivilegesWithClusterTest = ( }); for (const deletedPrivilege of deletedPrivileges) { expect(mockLogger.debug).toHaveBeenCalledWith( - `Deleting Kibana Privilege ${deletedPrivilege} from Elasticearch for ${application}` + `Deleting Kibana Privilege ${deletedPrivilege} from Elasticsearch for ${application}` ); expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( 'shield.deletePrivilege', @@ -82,7 +82,7 @@ const registerPrivilegesWithClusterTest = ( `Registering Kibana Privileges with Elasticsearch for ${application}` ); expect(mockLogger.debug).toHaveBeenCalledWith( - `Kibana Privileges already registered with Elasticearch for ${application}` + `Kibana Privileges already registered with Elasticsearch for ${application}` ); }; }; @@ -101,7 +101,7 @@ const registerPrivilegesWithClusterTest = ( test(description, async () => { const mockClusterClient = elasticsearchServiceMock.createClusterClient(); - mockClusterClient.callAsInternalUser.mockImplementation(async api => { + mockClusterClient.callAsInternalUser.mockImplementation(async (api) => { switch (api) { case 'shield.getPrivilege': { if (throwErrorWhenGettingPrivileges) { diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts index 22e7830d20e28..8e54794494a90 100644 --- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts +++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts @@ -61,14 +61,14 @@ export async function registerPrivilegesWithCluster( privilege: application, }); if (arePrivilegesEqual(existingPrivileges, expectedPrivileges)) { - logger.debug(`Kibana Privileges already registered with Elasticearch for ${application}`); + logger.debug(`Kibana Privileges already registered with Elasticsearch for ${application}`); return; } const privilegesToDelete = getPrivilegesToDelete(existingPrivileges, expectedPrivileges); for (const privilegeToDelete of privilegesToDelete) { logger.debug( - `Deleting Kibana Privilege ${privilegeToDelete} from Elasticearch for ${application}` + `Deleting Kibana Privilege ${privilegeToDelete} from Elasticsearch for ${application}` ); try { await clusterClient.callAsInternalUser('shield.deletePrivilege', { diff --git a/x-pack/plugins/security/server/authorization/service.test.mocks.ts b/x-pack/plugins/security/server/authorization/service.test.mocks.ts index 5cd2eac20094d..d73adde66a490 100644 --- a/x-pack/plugins/security/server/authorization/service.test.mocks.ts +++ b/x-pack/plugins/security/server/authorization/service.test.mocks.ts @@ -28,3 +28,8 @@ export const mockAuthorizationModeFactory = jest.fn(); jest.mock('./mode', () => ({ authorizationModeFactory: mockAuthorizationModeFactory, })); + +export const mockRegisterPrivilegesWithCluster = jest.fn(); +jest.mock('./register_privileges_with_cluster', () => ({ + registerPrivilegesWithCluster: mockRegisterPrivilegesWithCluster, +})); diff --git a/x-pack/plugins/security/server/authorization/validate_feature_privileges.ts b/x-pack/plugins/security/server/authorization/validate_feature_privileges.ts index 510feb1151a9b..79e5348b4ac64 100644 --- a/x-pack/plugins/security/server/authorization/validate_feature_privileges.ts +++ b/x-pack/plugins/security/server/authorization/validate_feature_privileges.ts @@ -9,15 +9,15 @@ import { Feature } from '../../../features/server'; export function validateFeaturePrivileges(features: Feature[]) { for (const feature of features) { const seenPrivilegeIds = new Set(); - Object.keys(feature.privileges ?? {}).forEach(privilegeId => { + Object.keys(feature.privileges ?? {}).forEach((privilegeId) => { seenPrivilegeIds.add(privilegeId); seenPrivilegeIds.add(`minimal_${privilegeId}`); }); const subFeatureEntries = feature.subFeatures ?? []; - subFeatureEntries.forEach(subFeature => { - subFeature.privilegeGroups.forEach(subFeaturePrivilegeGroup => { - subFeaturePrivilegeGroup.privileges.forEach(subFeaturePrivilege => { + subFeatureEntries.forEach((subFeature) => { + subFeature.privilegeGroups.forEach((subFeaturePrivilegeGroup) => { + subFeaturePrivilegeGroup.privileges.forEach((subFeaturePrivilege) => { if (seenPrivilegeIds.has(subFeaturePrivilege.id)) { throw new Error( `Feature '${feature.id}' already has a privilege with ID '${subFeaturePrivilege.id}'. Sub feature '${subFeature.name}' cannot also specify this.` diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 695653a2ac1db..4026666d042bd 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -15,7 +15,7 @@ const providerOptionsSchema = (providerType: string, optionsSchema: Type) = schema.conditional( schema.siblingRef('providers'), schema.arrayOf(schema.string(), { - validate: providers => (!providers.includes(providerType) ? 'error' : undefined), + validate: (providers) => (!providers.includes(providerType) ? 'error' : undefined), }), optionsSchema, schema.never() @@ -45,7 +45,7 @@ function getUniqueProviderSchema( return schema.maybe( schema.recordOf(schema.string(), schema.object(getCommonProviderSchemaProperties(overrides)), { validate(config) { - if (Object.values(config).filter(provider => provider.enabled).length > 1) { + if (Object.values(config).filter((provider) => provider.enabled).length > 1) { return `Only one "${providerType}" provider can be configured.`; } }, @@ -65,7 +65,7 @@ const providersConfigSchema = schema.object( icon: schema.string({ defaultValue: 'logoElasticsearch' }), showInSelector: schema.boolean({ defaultValue: true, - validate: value => { + validate: (value) => { if (!value) { return '`basic` provider only supports `true` in `showInSelector`.'; } @@ -81,7 +81,7 @@ const providersConfigSchema = schema.object( icon: schema.string({ defaultValue: 'logoElasticsearch' }), showInSelector: schema.boolean({ defaultValue: true, - validate: value => { + validate: (value) => { if (!value) { return '`token` provider only supports `true` in `showInSelector`.'; } diff --git a/x-pack/plugins/security/server/feature_usage/feature_usage_service.test.ts b/x-pack/plugins/security/server/feature_usage/feature_usage_service.test.ts new file mode 100644 index 0000000000000..46796fa73ef26 --- /dev/null +++ b/x-pack/plugins/security/server/feature_usage/feature_usage_service.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SecurityFeatureUsageService } from './feature_usage_service'; + +describe('#setup', () => { + it('registers all known security features', () => { + const featureUsage = { register: jest.fn() }; + const securityFeatureUsage = new SecurityFeatureUsageService(); + securityFeatureUsage.setup({ featureUsage }); + expect(featureUsage.register).toHaveBeenCalledTimes(2); + expect(featureUsage.register.mock.calls.map((c) => c[0])).toMatchInlineSnapshot(` + Array [ + "Subfeature privileges", + "Pre-access agreement", + ] + `); + }); +}); + +describe('start contract', () => { + it('notifies when sub-feature privileges are in use', () => { + const featureUsage = { notifyUsage: jest.fn(), getLastUsages: jest.fn() }; + const securityFeatureUsage = new SecurityFeatureUsageService(); + const startContract = securityFeatureUsage.start({ featureUsage }); + startContract.recordSubFeaturePrivilegeUsage(); + expect(featureUsage.notifyUsage).toHaveBeenCalledTimes(1); + expect(featureUsage.notifyUsage).toHaveBeenCalledWith('Subfeature privileges'); + }); + + it('notifies when pre-access agreement is used', () => { + const featureUsage = { notifyUsage: jest.fn(), getLastUsages: jest.fn() }; + const securityFeatureUsage = new SecurityFeatureUsageService(); + const startContract = securityFeatureUsage.start({ featureUsage }); + startContract.recordPreAccessAgreementUsage(); + expect(featureUsage.notifyUsage).toHaveBeenCalledTimes(1); + expect(featureUsage.notifyUsage).toHaveBeenCalledWith('Pre-access agreement'); + }); +}); diff --git a/x-pack/plugins/security/server/feature_usage/feature_usage_service.ts b/x-pack/plugins/security/server/feature_usage/feature_usage_service.ts new file mode 100644 index 0000000000000..1bc1e664981bf --- /dev/null +++ b/x-pack/plugins/security/server/feature_usage/feature_usage_service.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FeatureUsageServiceSetup, FeatureUsageServiceStart } from '../../../licensing/server'; + +interface SetupDeps { + featureUsage: FeatureUsageServiceSetup; +} + +interface StartDeps { + featureUsage: FeatureUsageServiceStart; +} + +export interface SecurityFeatureUsageServiceStart { + recordPreAccessAgreementUsage: () => void; + recordSubFeaturePrivilegeUsage: () => void; +} + +export class SecurityFeatureUsageService { + public setup({ featureUsage }: SetupDeps) { + featureUsage.register('Subfeature privileges', 'gold'); + featureUsage.register('Pre-access agreement', 'gold'); + } + + public start({ featureUsage }: StartDeps): SecurityFeatureUsageServiceStart { + return { + recordPreAccessAgreementUsage() { + featureUsage.notifyUsage('Pre-access agreement'); + }, + recordSubFeaturePrivilegeUsage() { + featureUsage.notifyUsage('Subfeature privileges'); + }, + }; + } +} diff --git a/x-pack/plugins/security/server/feature_usage/index.mock.ts b/x-pack/plugins/security/server/feature_usage/index.mock.ts new file mode 100644 index 0000000000000..6ed42145abd76 --- /dev/null +++ b/x-pack/plugins/security/server/feature_usage/index.mock.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SecurityFeatureUsageServiceStart } from './feature_usage_service'; + +export const securityFeatureUsageServiceMock = { + createStartContract() { + return { + recordPreAccessAgreementUsage: jest.fn(), + recordSubFeaturePrivilegeUsage: jest.fn(), + } as jest.Mocked; + }, +}; diff --git a/x-pack/plugins/security/server/feature_usage/index.ts b/x-pack/plugins/security/server/feature_usage/index.ts new file mode 100644 index 0000000000000..a3e1f35ee3824 --- /dev/null +++ b/x-pack/plugins/security/server/feature_usage/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + SecurityFeatureUsageService, + SecurityFeatureUsageServiceStart, +} from './feature_usage_service'; diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index 0011737d85734..a0a06b537213d 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -27,6 +27,7 @@ export { SAMLLogin, OIDCLogin, } from './authentication'; +export { AuditLogger } from './audit'; export { SecurityPluginSetup }; export { AuthenticatedUser } from '../common/model'; @@ -53,7 +54,7 @@ export const config: PluginConfigDescriptor> = { } return Object.values(providers?.[providerType] || {}).some( - provider => (provider as { enabled: boolean | undefined })?.enabled !== false + (provider) => (provider as { enabled: boolean | undefined })?.enabled !== false ); }; diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index a6407366bbd3b..c2d99433b0346 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SecurityPluginSetup } from './plugin'; - import { authenticationMock } from './authentication/index.mock'; import { authorizationMock } from './authorization/index.mock'; import { licenseMock } from '../common/licensing/index.mock'; +import { auditServiceMock } from './audit/index.mock'; function createSetupMock() { const mockAuthz = authorizationMock.create(); return { + audit: auditServiceMock.create(), authc: authenticationMock.create(), authz: { actions: mockAuthz.actions, @@ -21,7 +21,6 @@ function createSetupMock() { }, registerSpacesService: jest.fn(), license: licenseMock.create(), - __legacyCompat: {} as SecurityPluginSetup['__legacyCompat'], }; } diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index d58c999ddccdf..e01c608e5f306 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -25,6 +25,7 @@ describe('Security Plugin', () => { idleTimeout: 1500, lifespan: null, }, + audit: { enabled: false }, authc: { selector: { enabled: false }, providers: ['saml', 'token'], @@ -38,20 +39,19 @@ describe('Security Plugin', () => { mockCoreSetup.http.isTlsEnabled = true; mockClusterClient = elasticsearchServiceMock.createCustomClusterClient(); - mockCoreSetup.elasticsearch.createClient.mockReturnValue( - (mockClusterClient as unknown) as jest.Mocked - ); + mockCoreSetup.elasticsearch.legacy.createClient.mockReturnValue(mockClusterClient); - mockDependencies = { licensing: { license$: of({}) } } as PluginSetupDependencies; + mockDependencies = ({ + licensing: { license$: of({}), featureUsage: { register: jest.fn() } }, + } as unknown) as PluginSetupDependencies; }); describe('setup()', () => { it('exposes proper contract', async () => { await expect(plugin.setup(mockCoreSetup, mockDependencies)).resolves.toMatchInlineSnapshot(` Object { - "__legacyCompat": Object { - "registerLegacyAPI": [Function], - "registerPrivilegesWithCluster": [Function], + "audit": Object { + "getLogger": [Function], }, "authc": Object { "areAPIKeysEnabled": [Function], @@ -111,8 +111,8 @@ describe('Security Plugin', () => { it('properly creates cluster client instance', async () => { await plugin.setup(mockCoreSetup, mockDependencies); - expect(mockCoreSetup.elasticsearch.createClient).toHaveBeenCalledTimes(1); - expect(mockCoreSetup.elasticsearch.createClient).toHaveBeenCalledWith('security', { + expect(mockCoreSetup.elasticsearch.legacy.createClient).toHaveBeenCalledTimes(1); + expect(mockCoreSetup.elasticsearch.legacy.createClient).toHaveBeenCalledWith('security', { plugins: [elasticsearchClientPlugin], }); }); diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 77a2d716e6d87..c8f47aaae7b5d 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -8,42 +8,35 @@ import { combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { TypeOf } from '@kbn/config-schema'; import { + deepFreeze, ICustomClusterClient, CoreSetup, + CoreStart, Logger, PluginInitializerContext, } from '../../../../src/core/server'; -import { deepFreeze } from '../../../../src/core/server'; import { SpacesPluginSetup } from '../../spaces/server'; -import { PluginSetupContract as FeaturesSetupContract } from '../../features/server'; -import { LicensingPluginSetup } from '../../licensing/server'; +import { + PluginSetupContract as FeaturesPluginSetup, + PluginStartContract as FeaturesPluginStart, +} from '../../features/server'; +import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server'; import { Authentication, setupAuthentication } from './authentication'; -import { Authorization, setupAuthorization } from './authorization'; +import { AuthorizationService, AuthorizationServiceSetup } from './authorization'; import { ConfigSchema, createConfig } from './config'; import { defineRoutes } from './routes'; import { SecurityLicenseService, SecurityLicense } from '../common/licensing'; import { setupSavedObjects } from './saved_objects'; -import { SecurityAuditLogger } from './audit'; +import { AuditService, SecurityAuditLogger, AuditServiceSetup } from './audit'; import { elasticsearchClientPlugin } from './elasticsearch_client_plugin'; +import { SecurityFeatureUsageService, SecurityFeatureUsageServiceStart } from './feature_usage'; export type SpacesService = Pick< SpacesPluginSetup['spacesService'], 'getSpaceId' | 'namespaceToSpaceId' >; -export type FeaturesService = Pick; - -/** - * Describes a set of APIs that is available in the legacy platform only and required by this plugin - * to function properly. - */ -export interface LegacyAPI { - auditLogger: { - log: (eventType: string, message: string, data?: Record) => void; - }; -} - /** * Describes public Security plugin contract returned at the `setup` stage. */ @@ -58,8 +51,9 @@ export interface SecurityPluginSetup { | 'grantAPIKeyAsInternalUser' | 'invalidateAPIKeyAsInternalUser' >; - authz: Pick; + authz: Pick; license: SecurityLicense; + audit: Pick; /** * If Spaces plugin is available it's supposed to register its SpacesService with Security plugin @@ -70,18 +64,18 @@ export interface SecurityPluginSetup { * @param service Spaces service exposed by the Spaces plugin. */ registerSpacesService: (service: SpacesService) => void; - - __legacyCompat: { - registerLegacyAPI: (legacyAPI: LegacyAPI) => void; - registerPrivilegesWithCluster: () => void; - }; } export interface PluginSetupDependencies { - features: FeaturesService; + features: FeaturesPluginSetup; licensing: LicensingPluginSetup; } +export interface PluginStartDependencies { + features: FeaturesPluginStart; + licensing: LicensingPluginStart; +} + /** * Represents Security Plugin instance that will be managed by the Kibana plugin system. */ @@ -91,14 +85,18 @@ export class Plugin { private spacesService?: SpacesService | symbol = Symbol('not accessed'); private securityLicenseService?: SecurityLicenseService; - private legacyAPI?: LegacyAPI; - private readonly getLegacyAPI = () => { - if (!this.legacyAPI) { - throw new Error('Legacy API is not registered!'); + private readonly featureUsageService = new SecurityFeatureUsageService(); + private featureUsageServiceStart?: SecurityFeatureUsageServiceStart; + private readonly getFeatureUsageService = () => { + if (!this.featureUsageServiceStart) { + throw new Error(`featureUsageServiceStart is not registered!`); } - return this.legacyAPI; + return this.featureUsageServiceStart; }; + private readonly auditService = new AuditService(this.initializerContext.logger.get('audit')); + private readonly authorizationService = new AuthorizationService(); + private readonly getSpacesService = () => { // Changing property value from Symbol to undefined denotes the fact that property was accessed. if (!this.wasSpacesServiceAccessed()) { @@ -112,10 +110,13 @@ export class Plugin { this.logger = this.initializerContext.logger.get(); } - public async setup(core: CoreSetup, { features, licensing }: PluginSetupDependencies) { + public async setup( + core: CoreSetup, + { features, licensing }: PluginSetupDependencies + ) { const [config, legacyConfig] = await combineLatest([ this.initializerContext.config.create>().pipe( - map(rawConfig => + map((rawConfig) => createConfig(rawConfig, this.initializerContext.logger.get('config'), { isTLSEnabled: core.http.isTlsEnabled, }) @@ -126,7 +127,7 @@ export class Plugin { .pipe(first()) .toPromise(); - this.clusterClient = core.elasticsearch.createClient('security', { + this.clusterClient = core.elasticsearch.legacy.createClient('security', { plugins: [elasticsearchClientPlugin], }); @@ -135,9 +136,14 @@ export class Plugin { license$: licensing.license$, }); - const auditLogger = new SecurityAuditLogger(() => this.getLegacyAPI().auditLogger); + this.featureUsageService.setup({ featureUsage: licensing.featureUsage }); + + const audit = this.auditService.setup({ license, config: config.audit }); + const auditLogger = new SecurityAuditLogger(audit.getLogger()); + const authc = await setupAuthentication({ auditLogger, + getFeatureUsageService: this.getFeatureUsageService, http: core.http, clusterClient: this.clusterClient, config, @@ -145,15 +151,17 @@ export class Plugin { loggers: this.initializerContext.logger, }); - const authz = await setupAuthorization({ + const authz = this.authorizationService.setup({ http: core.http, + capabilities: core.capabilities, + status: core.status, clusterClient: this.clusterClient, license, loggers: this.initializerContext.logger, kibanaIndexName: legacyConfig.kibana.index, packageVersion: this.initializerContext.env.packageInfo.version, getSpacesService: this.getSpacesService, - featuresService: features, + features, }); setupSavedObjects({ @@ -163,8 +171,6 @@ export class Plugin { getSpacesService: this.getSpacesService, }); - core.capabilities.registerSwitcher(authz.disableUnauthorizedCapabilities); - defineRoutes({ router: core.http.createRouter(), basePath: core.http.basePath, @@ -175,9 +181,18 @@ export class Plugin { authc, authz, license, + getFeatures: () => + core + .getStartServices() + .then(([, { features: featuresStart }]) => featuresStart.getFeatures()), + getFeatureUsageService: this.getFeatureUsageService, }); return deepFreeze({ + audit: { + getLogger: audit.getLogger, + }, + authc: { isAuthenticated: authc.isAuthenticated, getCurrentUser: authc.getCurrentUser, @@ -196,24 +211,22 @@ export class Plugin { license, - registerSpacesService: service => { + registerSpacesService: (service) => { if (this.wasSpacesServiceAccessed()) { throw new Error('Spaces service has been accessed before registration.'); } this.spacesService = service; }, - - __legacyCompat: { - registerLegacyAPI: (legacyAPI: LegacyAPI) => (this.legacyAPI = legacyAPI), - - registerPrivilegesWithCluster: async () => await authz.registerPrivilegesWithCluster(), - }, }); } - public start() { + public start(core: CoreStart, { features, licensing }: PluginStartDependencies) { this.logger.debug('Starting plugin'); + this.featureUsageServiceStart = this.featureUsageService.start({ + featureUsage: licensing.featureUsage, + }); + this.authorizationService.start({ features, clusterClient: this.clusterClient! }); } public stop() { @@ -228,6 +241,12 @@ export class Plugin { this.securityLicenseService.stop(); this.securityLicenseService = undefined; } + + if (this.featureUsageServiceStart) { + this.featureUsageServiceStart = undefined; + } + this.auditService.stop(); + this.authorizationService.stop(); } private wasSpacesServiceAccessed() { diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts index cb86c1024ae9a..dd472c0b60cbc 100644 --- a/x-pack/plugins/security/server/routes/api_keys/invalidate.ts +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts @@ -33,7 +33,7 @@ export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDe // Invalidate all API keys in parallel. const invalidationResult = ( await Promise.all( - request.body.apiKeys.map(async key => { + request.body.apiKeys.map(async (key) => { try { const body: { id: string; owner?: boolean } = { id: key.id }; if (!request.body.isAdmin) { diff --git a/x-pack/plugins/security/server/routes/authentication/basic.test.ts b/x-pack/plugins/security/server/routes/authentication/basic.test.ts index 5eed8e166c957..944bc567de586 100644 --- a/x-pack/plugins/security/server/routes/authentication/basic.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/basic.test.ts @@ -28,7 +28,7 @@ describe('Basic authentication routes', () => { router = routeParamsMock.router; authc = routeParamsMock.authc; - authc.isProviderTypeEnabled.mockImplementation(provider => provider === 'basic'); + authc.isProviderTypeEnabled.mockImplementation((provider) => provider === 'basic'); mockContext = ({ licensing: { @@ -156,7 +156,7 @@ describe('Basic authentication routes', () => { it('prefers `token` authentication provider if it is enabled', async () => { authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockAuthenticatedUser())); authc.isProviderTypeEnabled.mockImplementation( - provider => provider === 'token' || provider === 'basic' + (provider) => provider === 'token' || provider === 'basic' ); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts index c9e963f0b8fc7..08cd3ba487b0b 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts @@ -16,8 +16,8 @@ export function defineGetBuiltinPrivilegesRoutes({ router, clusterClient }: Rout .callAsCurrentUser('shield.getBuiltinPrivileges'); // Exclude the `none` privilege, as it doesn't make sense as an option within the Kibana UI - privileges.cluster = privileges.cluster.filter(privilege => privilege !== 'none'); - privileges.index = privileges.index.filter(privilege => privilege !== 'none'); + privileges.cluster = privileges.cluster.filter((privilege) => privilege !== 'none'); + privileges.index = privileges.index.filter((privilege) => privilege !== 'none'); return response.ok({ body: privileges }); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts index 609b7d2f35c4b..0fd087231b770 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts @@ -56,13 +56,13 @@ function transformRoleApplicationsToKibanaPrivileges( application: string ) { const roleKibanaApplications = roleApplications.filter( - roleApplication => + (roleApplication) => roleApplication.application === application || roleApplication.application === RESERVED_PRIVILEGES_APPLICATION_WILDCARD ); // if any application entry contains an empty resource, we throw an error - if (roleKibanaApplications.some(entry => entry.resources.length === 0)) { + if (roleKibanaApplications.some((entry) => entry.resources.length === 0)) { throw new Error(`ES returned an application entry without resources, can't process this`); } @@ -70,9 +70,9 @@ function transformRoleApplicationsToKibanaPrivileges( // and there are privileges which aren't reserved, we won't transform these if ( roleKibanaApplications.some( - entry => + (entry) => entry.application === RESERVED_PRIVILEGES_APPLICATION_WILDCARD && - !entry.privileges.every(privilege => + !entry.privileges.every((privilege) => PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) ) @@ -85,9 +85,9 @@ function transformRoleApplicationsToKibanaPrivileges( // if space privilege assigned globally, we can't transform these if ( roleKibanaApplications.some( - entry => + (entry) => entry.resources.includes(GLOBAL_RESOURCE) && - entry.privileges.some(privilege => + entry.privileges.some((privilege) => PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege) ) ) @@ -100,10 +100,10 @@ function transformRoleApplicationsToKibanaPrivileges( // if global base or reserved privilege assigned at a space, we can't transform these if ( roleKibanaApplications.some( - entry => + (entry) => !entry.resources.includes(GLOBAL_RESOURCE) && entry.privileges.some( - privilege => + (privilege) => PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) || PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) @@ -117,12 +117,12 @@ function transformRoleApplicationsToKibanaPrivileges( // if reserved privilege assigned with feature or base privileges, we won't transform these if ( roleKibanaApplications.some( - entry => - entry.privileges.some(privilege => + (entry) => + entry.privileges.some((privilege) => PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) && entry.privileges.some( - privilege => !PrivilegeSerializer.isSerializedReservedPrivilege(privilege) + (privilege) => !PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) ) ) { @@ -134,14 +134,14 @@ function transformRoleApplicationsToKibanaPrivileges( // if base privilege assigned with feature privileges, we won't transform these if ( roleKibanaApplications.some( - entry => - entry.privileges.some(privilege => + (entry) => + entry.privileges.some((privilege) => PrivilegeSerializer.isSerializedFeaturePrivilege(privilege) ) && - (entry.privileges.some(privilege => + (entry.privileges.some((privilege) => PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) ) || - entry.privileges.some(privilege => + entry.privileges.some((privilege) => PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege) )) ) @@ -154,7 +154,7 @@ function transformRoleApplicationsToKibanaPrivileges( // if any application entry contains the '*' resource in addition to another resource, we can't transform these if ( roleKibanaApplications.some( - entry => entry.resources.includes(GLOBAL_RESOURCE) && entry.resources.length > 1 + (entry) => entry.resources.includes(GLOBAL_RESOURCE) && entry.resources.length > 1 ) ) { return { @@ -162,11 +162,11 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - const allResources = roleKibanaApplications.map(entry => entry.resources).flat(); + const allResources = roleKibanaApplications.map((entry) => entry.resources).flat(); // if we have improperly formatted resource entries, we can't transform these if ( allResources.some( - resource => + (resource) => resource !== GLOBAL_RESOURCE && !ResourceSerializer.isSerializedSpaceResource(resource) ) ) { @@ -187,25 +187,25 @@ function transformRoleApplicationsToKibanaPrivileges( value: roleKibanaApplications.map(({ resources, privileges }) => { // if we're dealing with a global entry, which we've ensured above is only possible if it's the only item in the array if (resources.length === 1 && resources[0] === GLOBAL_RESOURCE) { - const reservedPrivileges = privileges.filter(privilege => + const reservedPrivileges = privileges.filter((privilege) => PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ); - const basePrivileges = privileges.filter(privilege => + const basePrivileges = privileges.filter((privilege) => PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) ); - const featurePrivileges = privileges.filter(privilege => + const featurePrivileges = privileges.filter((privilege) => PrivilegeSerializer.isSerializedFeaturePrivilege(privilege) ); return { ...(reservedPrivileges.length ? { - _reserved: reservedPrivileges.map(privilege => + _reserved: reservedPrivileges.map((privilege) => PrivilegeSerializer.deserializeReservedPrivilege(privilege) ), } : {}), - base: basePrivileges.map(privilege => + base: basePrivileges.map((privilege) => PrivilegeSerializer.serializeGlobalBasePrivilege(privilege) ), feature: featurePrivileges.reduce((acc, privilege) => { @@ -222,14 +222,14 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - const basePrivileges = privileges.filter(privilege => + const basePrivileges = privileges.filter((privilege) => PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege) ); - const featurePrivileges = privileges.filter(privilege => + const featurePrivileges = privileges.filter((privilege) => PrivilegeSerializer.isSerializedFeaturePrivilege(privilege) ); return { - base: basePrivileges.map(privilege => + base: basePrivileges.map((privilege) => PrivilegeSerializer.deserializeSpaceBasePrivilege(privilege) ), feature: featurePrivileges.reduce((acc, privilege) => { @@ -242,7 +242,7 @@ function transformRoleApplicationsToKibanaPrivileges( ]), }; }, {} as RoleKibanaPrivilege['feature']), - spaces: resources.map(resource => ResourceSerializer.deserializeSpaceResource(resource)), + spaces: resources.map((resource) => ResourceSerializer.deserializeSpaceResource(resource)), }; }), }; @@ -255,11 +255,11 @@ const extractUnrecognizedApplicationNames = ( return getUniqueList( roleApplications .filter( - roleApplication => + (roleApplication) => roleApplication.application !== application && roleApplication.application !== RESERVED_PRIVILEGES_APPLICATION_WILDCARD ) - .map(roleApplication => roleApplication.application) + .map((roleApplication) => roleApplication.application) ); }; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts index a5f6b2fd9fcc1..e0af14f90d01c 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts @@ -142,7 +142,7 @@ export function getPutPayloadSchema( schema.string({ validate(value) { const globalPrivileges = getBasePrivilegeNames().global; - if (!globalPrivileges.some(privilege => privilege === value)) { + if (!globalPrivileges.some((privilege) => privilege === value)) { return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; } }, @@ -152,7 +152,7 @@ export function getPutPayloadSchema( schema.string({ validate(value) { const spacePrivileges = getBasePrivilegeNames().space; - if (!spacePrivileges.some(privilege => privilege === value)) { + if (!spacePrivileges.some((privilege) => privilege === value)) { return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; } }, @@ -235,7 +235,7 @@ export const transformPutPayloadToElasticsearchRole = ( kibana = [], } = rolePayload; const otherApplications = allExistingApplications.filter( - roleApplication => roleApplication.application !== application + (roleApplication) => roleApplication.application !== application ); return { @@ -259,12 +259,12 @@ const transformPrivilegesToElasticsearchPrivileges = ( return { privileges: [ ...(base - ? base.map(privilege => PrivilegeSerializer.serializeGlobalBasePrivilege(privilege)) + ? base.map((privilege) => PrivilegeSerializer.serializeGlobalBasePrivilege(privilege)) : []), ...(feature ? Object.entries(feature) .map(([featureName, featurePrivileges]) => - featurePrivileges.map(privilege => + featurePrivileges.map((privilege) => PrivilegeSerializer.serializeFeaturePrivilege(featureName, privilege) ) ) @@ -279,12 +279,12 @@ const transformPrivilegesToElasticsearchPrivileges = ( return { privileges: [ ...(base - ? base.map(privilege => PrivilegeSerializer.serializeSpaceBasePrivilege(privilege)) + ? base.map((privilege) => PrivilegeSerializer.serializeSpaceBasePrivilege(privilege)) : []), ...(feature ? Object.entries(feature) .map(([featureName, featurePrivileges]) => - featurePrivileges.map(privilege => + featurePrivileges.map((privilege) => PrivilegeSerializer.serializeFeaturePrivilege(featureName, privilege) ) ) @@ -292,7 +292,7 @@ const transformPrivilegesToElasticsearchPrivileges = ( : []), ], application, - resources: (spaces as string[]).map(resource => + resources: (spaces as string[]).map((resource) => ResourceSerializer.serializeSpaceResource(resource) ), }; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index d7710bf669ce1..bec60fa149bcf 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -15,6 +15,8 @@ import { httpServerMock, } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; +import { Feature } from '../../../../../features/server'; +import { securityFeatureUsageServiceMock } from '../../../feature_usage/index.mock'; const application = 'kibana-.kibana'; const privilegeMap = { @@ -47,7 +49,12 @@ interface TestOptions { licenseCheckResult?: LicenseCheck; apiResponses?: Array<() => Promise>; payload?: Record; - asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; + asserts: { + statusCode: number; + result?: Record; + apiArguments?: unknown[][]; + recordSubFeaturePrivilegeUsage?: boolean; + }; } const putRoleTest = ( @@ -71,6 +78,47 @@ const putRoleTest = ( mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); } + mockRouteDefinitionParams.getFeatureUsageService.mockReturnValue( + securityFeatureUsageServiceMock.createStartContract() + ); + + mockRouteDefinitionParams.getFeatures.mockResolvedValue([ + new Feature({ + id: 'feature_1', + name: 'feature 1', + app: [], + privileges: { + all: { + ui: [], + savedObject: { all: [], read: [] }, + }, + read: { + ui: [], + savedObject: { all: [], read: [] }, + }, + }, + subFeatures: [ + { + name: 'sub feature 1', + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'sub_feature_privilege_1', + name: 'first sub-feature privilege', + includeIn: 'none', + ui: [], + savedObject: { all: [], read: [] }, + }, + ], + }, + ], + }, + ], + }), + ]); + definePutRolesRoutes(mockRouteDefinitionParams); const [[{ validate }, handler]] = mockRouteDefinitionParams.router.put.mock.calls; @@ -99,6 +147,16 @@ const putRoleTest = ( expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); + + if (asserts.recordSubFeaturePrivilegeUsage) { + expect( + mockRouteDefinitionParams.getFeatureUsageService().recordSubFeaturePrivilegeUsage + ).toHaveBeenCalledTimes(1); + } else { + expect( + mockRouteDefinitionParams.getFeatureUsageService().recordSubFeaturePrivilegeUsage + ).not.toHaveBeenCalled(); + } }); }; @@ -598,5 +656,131 @@ describe('PUT role', () => { result: undefined, }, }); + + putRoleTest(`notifies when sub-feature privileges are included`, { + name: 'foo-role', + payload: { + kibana: [ + { + spaces: ['*'], + feature: { + feature_1: ['sub_feature_privilege_1'], + }, + }, + ], + }, + apiResponses: [async () => ({}), async () => {}], + asserts: { + recordSubFeaturePrivilegeUsage: true, + apiArguments: [ + ['shield.getRole', { name: 'foo-role', ignore: [404] }], + [ + 'shield.putRole', + { + name: 'foo-role', + body: { + cluster: [], + indices: [], + run_as: [], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_feature_1.sub_feature_privilege_1'], + resources: ['*'], + }, + ], + metadata: undefined, + }, + }, + ], + ], + statusCode: 204, + result: undefined, + }, + }); + + putRoleTest(`does not record sub-feature privilege usage for unknown privileges`, { + name: 'foo-role', + payload: { + kibana: [ + { + spaces: ['*'], + feature: { + feature_1: ['unknown_sub_feature_privilege_1'], + }, + }, + ], + }, + apiResponses: [async () => ({}), async () => {}], + asserts: { + recordSubFeaturePrivilegeUsage: false, + apiArguments: [ + ['shield.getRole', { name: 'foo-role', ignore: [404] }], + [ + 'shield.putRole', + { + name: 'foo-role', + body: { + cluster: [], + indices: [], + run_as: [], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_feature_1.unknown_sub_feature_privilege_1'], + resources: ['*'], + }, + ], + metadata: undefined, + }, + }, + ], + ], + statusCode: 204, + result: undefined, + }, + }); + + putRoleTest(`does not record sub-feature privilege usage for unknown features`, { + name: 'foo-role', + payload: { + kibana: [ + { + spaces: ['*'], + feature: { + unknown_feature: ['sub_feature_privilege_1'], + }, + }, + ], + }, + apiResponses: [async () => ({}), async () => {}], + asserts: { + recordSubFeaturePrivilegeUsage: false, + apiArguments: [ + ['shield.getRole', { name: 'foo-role', ignore: [404] }], + [ + 'shield.putRole', + { + name: 'foo-role', + body: { + cluster: [], + indices: [], + run_as: [], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_unknown_feature.sub_feature_privilege_1'], + resources: ['*'], + }, + ], + metadata: undefined, + }, + }, + ], + ], + statusCode: 204, + result: undefined, + }, + }); }); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 5db83375afa96..d83cf92bcaa0d 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { Feature } from '../../../../../features/common'; import { RouteDefinitionParams } from '../../index'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../../errors'; @@ -14,7 +15,37 @@ import { transformPutPayloadToElasticsearchRole, } from './model'; -export function definePutRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) { +const roleGrantsSubFeaturePrivileges = ( + features: Feature[], + role: TypeOf> +) => { + if (!role.kibana) { + return false; + } + + const subFeaturePrivileges = new Map( + features.map((feature) => [ + feature.id, + feature.subFeatures.map((sf) => sf.privilegeGroups.map((pg) => pg.privileges)).flat(2), + ]) + ); + + const hasAnySubFeaturePrivileges = role.kibana.some((kibanaPrivilege) => + Object.entries(kibanaPrivilege.feature ?? {}).some(([featureId, privileges]) => { + return !!subFeaturePrivileges.get(featureId)?.some(({ id }) => privileges.includes(id)); + }) + ); + + return hasAnySubFeaturePrivileges; +}; + +export function definePutRolesRoutes({ + router, + authz, + clusterClient, + getFeatures, + getFeatureUsageService, +}: RouteDefinitionParams) { router.put( { path: '/api/security/role/{name}', @@ -46,9 +77,16 @@ export function definePutRolesRoutes({ router, authz, clusterClient }: RouteDefi rawRoles[name] ? rawRoles[name].applications : [] ); - await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.putRole', { name: request.params.name, body }); + const [features] = await Promise.all([ + getFeatures(), + clusterClient + .asScoped(request) + .callAsCurrentUser('shield.putRole', { name: request.params.name, body }), + ]); + + if (roleGrantsSubFeaturePrivileges(features, request.body)) { + getFeatureUsageService().recordSubFeaturePrivilegeUsage(); + } return response.noContent(); } catch (error) { diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index b0c74b98ee19b..1a93d6701e257 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -29,5 +29,7 @@ export const routeDefinitionParamsMock = { authz: authorizationMock.create(), license: licenseMock.create(), httpResources: httpResourcesMock.createRegistrar(), + getFeatures: jest.fn(), + getFeatureUsageService: jest.fn(), }), }; diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index e43072b95c906..5721a2699d15c 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Feature } from '../../../features/server'; import { CoreSetup, HttpResources, @@ -13,7 +14,7 @@ import { } from '../../../../../src/core/server'; import { SecurityLicense } from '../../common/licensing'; import { Authentication } from '../authentication'; -import { Authorization } from '../authorization'; +import { AuthorizationServiceSetup } from '../authorization'; import { ConfigType } from '../config'; import { defineAuthenticationRoutes } from './authentication'; @@ -23,6 +24,7 @@ import { defineIndicesRoutes } from './indices'; import { defineUsersRoutes } from './users'; import { defineRoleMappingRoutes } from './role_mapping'; import { defineViewRoutes } from './views'; +import { SecurityFeatureUsageServiceStart } from '../feature_usage'; /** * Describes parameters used to define HTTP routes. @@ -35,8 +37,10 @@ export interface RouteDefinitionParams { clusterClient: IClusterClient; config: ConfigType; authc: Authentication; - authz: Authorization; + authz: AuthorizationServiceSetup; license: SecurityLicense; + getFeatures: () => Promise; + getFeatureUsageService: () => SecurityFeatureUsageServiceStart; } export function defineRoutes(params: RouteDefinitionParams) { diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.ts b/x-pack/plugins/security/server/routes/indices/get_fields.ts index 64c3d4f7471ef..356b78aa33879 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.ts @@ -34,7 +34,7 @@ export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinition body: Array.from( new Set( Object.values(indexMappings) - .map(indexMapping => Object.keys(indexMapping.mappings)) + .map((indexMapping) => Object.keys(indexMapping.mappings)) .flat() ) ), diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts index 2be4f4cd89177..b056d9e358737 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts @@ -80,7 +80,7 @@ async function getEnabledRoleMappingsFeatures(clusterClient: IClusterClient, log .callAsInternalUser('nodes.info', { filterPath: 'nodes.*.settings.script', }) - .catch(error => { + .catch((error) => { // fall back to assuming that node settings are unset/at their default values. // this will allow the role mappings UI to permit both role template script types, // even if ES will disallow it at mapping evaluation time. @@ -95,7 +95,7 @@ async function getEnabledRoleMappingsFeatures(clusterClient: IClusterClient, log method: 'GET', path: '/_xpack/usage', }) - .catch(error => { + .catch((error) => { // fall back to no external realms configured. // this will cause a warning in the UI about no compatible realms being enabled, but will otherwise allow // the mappings screen to function correctly. @@ -115,12 +115,12 @@ async function getEnabledRoleMappingsFeatures(clusterClient: IClusterClient, log let canUseStoredScripts = true; let canUseInlineScripts = true; if (usesCustomScriptSettings(nodeScriptSettings)) { - canUseStoredScripts = Object.values(nodeScriptSettings.nodes).some(node => { + canUseStoredScripts = Object.values(nodeScriptSettings.nodes).some((node) => { const allowedTypes = node.settings.script.allowed_types; return !allowedTypes || allowedTypes.includes('stored'); }); - canUseInlineScripts = Object.values(nodeScriptSettings.nodes).some(node => { + canUseInlineScripts = Object.values(nodeScriptSettings.nodes).some((node) => { const allowedTypes = node.settings.script.allowed_types; return !allowedTypes || allowedTypes.includes('inline'); }); diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts index def6fabc0e322..63598584b5d1b 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -39,7 +39,7 @@ export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { return { name, ...mapping, - role_templates: (mapping.role_templates || []).map(entry => { + role_templates: (mapping.role_templates || []).map((entry) => { return { ...entry, template: tryParseRoleTemplate(entry.template as string), diff --git a/x-pack/plugins/security/server/routes/views/index.test.ts b/x-pack/plugins/security/server/routes/views/index.test.ts index 7cddef9bf2b98..0c0117dec5390 100644 --- a/x-pack/plugins/security/server/routes/views/index.test.ts +++ b/x-pack/plugins/security/server/routes/views/index.test.ts @@ -12,7 +12,7 @@ describe('View routes', () => { it('does not register Login routes if both `basic` and `token` providers are disabled', () => { const routeParamsMock = routeDefinitionParamsMock.create(); routeParamsMock.authc.isProviderTypeEnabled.mockImplementation( - provider => provider !== 'basic' && provider !== 'token' + (provider) => provider !== 'basic' && provider !== 'token' ); defineViewRoutes(routeParamsMock); @@ -37,7 +37,7 @@ describe('View routes', () => { it('registers Login routes if `basic` provider is enabled', () => { const routeParamsMock = routeDefinitionParamsMock.create(); routeParamsMock.authc.isProviderTypeEnabled.mockImplementation( - provider => provider !== 'token' + (provider) => provider !== 'token' ); defineViewRoutes(routeParamsMock); @@ -64,7 +64,7 @@ describe('View routes', () => { it('registers Login routes if `token` provider is enabled', () => { const routeParamsMock = routeDefinitionParamsMock.create(); routeParamsMock.authc.isProviderTypeEnabled.mockImplementation( - provider => provider !== 'basic' + (provider) => provider !== 'basic' ); defineViewRoutes(routeParamsMock); diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index 5c41a48bf5ee4..fee3adbb19f97 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -172,6 +172,7 @@ describe('Login view routes', () => { showLinks: false, showRoleMappingsManagement: true, allowSubFeaturePrivileges: true, + allowAuditLogging: true, showLogin: true, }); diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index 40c17e5429aa8..6acfd06a0309b 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -11,13 +11,16 @@ import { SavedObjectsClient, } from '../../../../../src/core/server'; import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper'; -import { Authorization } from '../authorization'; +import { AuthorizationServiceSetup } from '../authorization'; import { SecurityAuditLogger } from '../audit'; import { SpacesService } from '../plugin'; interface SetupSavedObjectsParams { auditLogger: SecurityAuditLogger; - authz: Pick; + authz: Pick< + AuthorizationServiceSetup, + 'mode' | 'actions' | 'checkSavedObjectsPrivilegesWithRequest' + >; savedObjects: CoreSetup['savedObjects']; getSpacesService(): SpacesService | undefined; } @@ -31,14 +34,16 @@ export function setupSavedObjects({ const getKibanaRequest = (request: KibanaRequest | LegacyRequest) => request instanceof KibanaRequest ? request : KibanaRequest.from(request); - savedObjects.setClientFactoryProvider(repositoryFactory => ({ request, includedHiddenTypes }) => { - const kibanaRequest = getKibanaRequest(request); - return new SavedObjectsClient( - authz.mode.useRbacForRequest(kibanaRequest) - ? repositoryFactory.createInternalRepository(includedHiddenTypes) - : repositoryFactory.createScopedRepository(kibanaRequest, includedHiddenTypes) - ); - }); + savedObjects.setClientFactoryProvider( + (repositoryFactory) => ({ request, includedHiddenTypes }) => { + const kibanaRequest = getKibanaRequest(request); + return new SavedObjectsClient( + authz.mode.useRbacForRequest(kibanaRequest) + ? repositoryFactory.createInternalRepository(includedHiddenTypes) + : repositoryFactory.createScopedRepository(kibanaRequest, includedHiddenTypes) + ); + } + ); savedObjects.addClientWrapper(Number.MAX_SAFE_INTEGER - 1, 'security', ({ client, request }) => { const kibanaRequest = getKibanaRequest(request); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 3c4034e07f995..c646cd95228f0 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -76,7 +76,7 @@ const expectForbiddenError = async (fn: Function, args: Record) => const spaceId = args.options?.namespace || 'default'; const ACTION = getCalls[0][1]; - const types = getCalls.map(x => x[0]); + const types = getCalls.map((x) => x[0]); const missing = [{ spaceId, privilege: actions[0] }]; // if there was more than one type, only the first type was unauthorized const spaceIds = [spaceId]; @@ -99,7 +99,7 @@ const expectSuccess = async (fn: Function, args: Record) => { SavedObjectActions['get'] >).mock.calls; const ACTION = getCalls[0][1]; - const types = getCalls.map(x => x[0]); + const types = getCalls.map((x) => x[0]); const spaceIds = [args.options?.namespace || 'default']; expect(clientOpts.auditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); @@ -123,7 +123,7 @@ const expectPrivilegeCheck = async (fn: Function, args: Record) => const getResults = (clientOpts.actions.savedObject.get as jest.MockedFunction< SavedObjectActions['get'] >).mock.results; - const actions = getResults.map(x => x.value); + const actions = getResults.map((x) => x.value); expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(1); expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledWith( @@ -206,8 +206,8 @@ function getMockCheckPrivilegesSuccess(actions: string | string[], namespaces?: hasAllRequested: true, username: USERNAME, privileges: _namespaces - .map(resource => - _actions.map(action => ({ + .map((resource) => + _actions.map((action) => ({ resource, privilege: action, authorized: true, diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 29503d475be73..969344afae5e3 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -207,14 +207,14 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra ) { const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; const actionsToTypesMap = new Map( - types.map(type => [this.actions.savedObject.get(type, action), type]) + types.map((type) => [this.actions.savedObject.get(type, action), type]) ); const actions = Array.from(actionsToTypesMap.keys()); const result = await this.checkPrivileges(actions, namespaceOrNamespaces); const { hasAllRequested, username, privileges } = result; const spaceIds = uniq( - privileges.map(({ resource }) => resource).filter(x => x !== undefined) + privileges.map(({ resource }) => resource).filter((x) => x !== undefined) ).sort() as string[]; const isAuthorized = @@ -253,7 +253,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra } private getUniqueObjectTypes(objects: Array<{ type: string }>) { - return uniq(objects.map(o => o.type)); + return uniq(objects.map((o) => o.type)); } private async getNamespacesPrivilegeMap(namespaces: string[]) { @@ -287,7 +287,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra } return 0; }; - return spaceIds.map(spaceId => (privilegeMap[spaceId] ? spaceId : '?')).sort(comparator); + return spaceIds.map((spaceId) => (privilegeMap[spaceId] ? spaceId : '?')).sort(comparator); } private async redactSavedObjectNamespaces( @@ -312,7 +312,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return response; } const { saved_objects: savedObjects } = response; - const namespaces = uniq(savedObjects.flatMap(savedObject => savedObject.namespaces || [])); + const namespaces = uniq(savedObjects.flatMap((savedObject) => savedObject.namespaces || [])); if (namespaces.length === 0) { return response; } @@ -321,7 +321,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return { ...response, - saved_objects: savedObjects.map(savedObject => ({ + saved_objects: savedObjects.map((savedObject) => ({ ...savedObject, namespaces: savedObject.namespaces && diff --git a/x-pack/plugins/security_solution/.gitattributes b/x-pack/plugins/security_solution/.gitattributes new file mode 100644 index 0000000000000..431f25be5e78e --- /dev/null +++ b/x-pack/plugins/security_solution/.gitattributes @@ -0,0 +1,6 @@ +# Auto-collapse generated files in GitHub +# https://help.github.com/en/articles/customizing-how-changed-files-appear-on-github +x-pack/plugins/security_solution/server/graphql/types.ts linguist-generated=true +x-pack/plugins/security_solution/public/graphql/types.ts linguist-generated=true +x-pack/plugins/security_solution/public/graphql/introspection.json linguist-generated=true + diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts new file mode 100644 index 0000000000000..482794804685d --- /dev/null +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const APP_ID = 'securitySolution'; +export const APP_NAME = 'Security'; +export const APP_ICON = 'securityAnalyticsApp'; +export const APP_PATH = `/app/security`; +export const ADD_DATA_PATH = `/app/home#/tutorial_directory/security`; +export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern'; +export const DEFAULT_DATE_FORMAT = 'dateFormat'; +export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz'; +export const DEFAULT_DARK_MODE = 'theme:darkMode'; +export const DEFAULT_INDEX_KEY = 'securitySolution:defaultIndex'; +export const DEFAULT_NUMBER_FORMAT = 'format:number:defaultPattern'; +export const DEFAULT_TIME_RANGE = 'timepicker:timeDefaults'; +export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults'; +export const DEFAULT_APP_TIME_RANGE = 'securitySolution:timeDefaults'; +export const DEFAULT_APP_REFRESH_INTERVAL = 'securitySolution:refreshIntervalDefaults'; +export const DEFAULT_SIGNALS_INDEX = '.siem-signals'; +export const DEFAULT_MAX_SIGNALS = 100; +export const DEFAULT_SEARCH_AFTER_PAGE_SIZE = 100; +export const DEFAULT_ANOMALY_SCORE = 'securitySolution:defaultAnomalyScore'; +export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000; +export const DEFAULT_SCALE_DATE_FORMAT = 'dateFormat:scaled'; +export const DEFAULT_FROM = 'now-24h'; +export const DEFAULT_TO = 'now'; +export const DEFAULT_INTERVAL_PAUSE = true; +export const DEFAULT_INTERVAL_TYPE = 'manual'; +export const DEFAULT_INTERVAL_VALUE = 300000; // ms +export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges'; + +/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ +export const DEFAULT_INDEX_PATTERN = [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', +]; + +/** This Kibana Advanced Setting enables the `Security news` feed widget */ +export const ENABLE_NEWS_FEED_SETTING = 'securitySolution:enableNewsFeed'; + +/** This Kibana Advanced Setting specifies the URL of the News feed widget */ +export const NEWS_FEED_URL_SETTING = 'securitySolution:newsFeedUrl'; + +/** The default value for News feed widget */ +export const NEWS_FEED_URL_SETTING_DEFAULT = 'https://feeds.elastic.co/security-solution'; + +/** This Kibana Advanced Setting specifies the URLs of `IP Reputation Links`*/ +export const IP_REPUTATION_LINKS_SETTING = 'securitySolution:ipReputationLinks'; + +/** The default value for `IP Reputation Links` */ +export const IP_REPUTATION_LINKS_SETTING_DEFAULT = `[ + { "name": "virustotal.com", "url_template": "https://www.virustotal.com/gui/search/{{ip}}" }, + { "name": "talosIntelligence.com", "url_template": "https://talosintelligence.com/reputation_center/lookup?search={{ip}}" } +]`; + +/** + * Id for the signals alerting type + */ +export const SIGNALS_ID = `siem.signals`; + +/** + * Id for the notifications alerting type + */ +export const NOTIFICATIONS_ID = `siem.notifications`; + +/** + * Special internal structure for tags for signals. This is used + * to filter out tags that have internal structures within them. + */ +export const INTERNAL_IDENTIFIER = '__internal'; +export const INTERNAL_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_id`; +export const INTERNAL_RULE_ALERT_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_alert_id`; +export const INTERNAL_IMMUTABLE_KEY = `${INTERNAL_IDENTIFIER}_immutable`; + +/** + * Detection engine routes + */ +export const DETECTION_ENGINE_URL = '/api/detection_engine'; +export const DETECTION_ENGINE_RULES_URL = `${DETECTION_ENGINE_URL}/rules`; +export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged`; +export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`; +export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; +export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`; +export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`; +export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`; + +export const TIMELINE_URL = '/api/timeline'; +export const TIMELINE_DRAFT_URL = `${TIMELINE_URL}/_draft`; +export const TIMELINE_EXPORT_URL = `${TIMELINE_URL}/_export`; +export const TIMELINE_IMPORT_URL = `${TIMELINE_URL}/_import`; + +/** + * Default signals index key for kibana.dev.yml + */ +export const SIGNALS_INDEX_KEY = 'signalsIndex'; +export const DETECTION_ENGINE_SIGNALS_URL = `${DETECTION_ENGINE_URL}/signals`; +export const DETECTION_ENGINE_SIGNALS_STATUS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/status`; +export const DETECTION_ENGINE_QUERY_SIGNALS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/search`; + +/** + * Common naming convention for an unauthenticated user + */ +export const UNAUTHENTICATED_USER = 'Unauthenticated'; + +/* + Licensing requirements + */ +export const MINIMUM_ML_LICENSE = 'platinum'; + +/* + Rule notifications options +*/ +export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ + '.email', + '.slack', + '.pagerduty', + '.webhook', +]; +export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions'; +export const NOTIFICATION_THROTTLE_RULE = 'rule'; + +/** + * Histograms for fields named in this list should be displayed with an + * "All others" bucket, to count events that don't specify a value for + * the field being counted + */ +export const showAllOthersBucket: string[] = [ + 'destination.ip', + 'event.action', + 'event.category', + 'event.dataset', + 'event.module', + 'signal.rule.threat.tactic.name', + 'source.ip', + 'destination.ip', + 'user.name', +]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts new file mode 100644 index 0000000000000..9eb2d9abccbd3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; +import { RiskScore } from '../types/risk_score'; +import { UUID } from '../types/uuid'; +import { IsoDateString } from '../types/iso_date_string'; +import { PositiveIntegerGreaterThanZero } from '../types/positive_integer_greater_than_zero'; +import { PositiveInteger } from '../types/positive_integer'; + +export const description = t.string; +export const enabled = t.boolean; +export const exclude_export_details = t.boolean; +export const false_positives = t.array(t.string); +export const file_name = t.string; + +/** + * TODO: Right now the filters is an "unknown", when it could more than likely + * become the actual ESFilter as a type. + */ +export const filters = t.array(t.unknown); // Filters are not easily type-able yet + +/** + * Params is an "object", since it is a type of AlertActionParams which is action templates. + * @see x-pack/plugins/alerts/common/alert.ts + */ +export const action_group = t.string; +export const action_id = t.string; +export const action_action_type_id = t.string; +export const action_params = t.object; +export const action = t.exact( + t.type({ + group: action_group, + id: action_id, + action_type_id: action_action_type_id, + params: action_params, + }) +); + +export const actions = t.array(action); + +// TODO: Create a regular expression type or custom date math part type here +export const from = t.string; + +export const immutable = t.boolean; + +// Note: Never make this a strict uuid, we allow the rule_id to be any string at the moment +// in case we encounter 3rd party rule systems which might be using auto incrementing numbers +// or other different things. +export const rule_id = t.string; + +export const id = UUID; +export const index = t.array(t.string); +export const interval = t.string; +export const query = t.string; +export const language = t.keyof({ kuery: null, lucene: null }); +export const objects = t.array(t.type({ rule_id })); +export const output_index = t.string; +export const saved_id = t.string; +export const timeline_id = t.string; +export const timeline_title = t.string; +export const throttle = t.string; +export const anomaly_threshold = PositiveInteger; +export const machine_learning_job_id = t.string; + +/** + * Note that this is a plain unknown object because we allow the UI + * to send us extra additional information as "meta" which can be anything. + * + * TODO: Strip away extra information and possibly even "freeze" this object + * so we have tighter control over 3rd party data structures. + */ +export const meta = t.object; +export const max_signals = PositiveIntegerGreaterThanZero; +export const name = t.string; +export const risk_score = RiskScore; +export const severity = t.keyof({ low: null, medium: null, high: null, critical: null }); +export const status = t.keyof({ open: null, closed: null }); +export const job_status = t.keyof({ succeeded: null, failed: null, 'going to run': null }); + +// TODO: Create a regular expression type or custom date math part type here +export const to = t.string; + +export const type = t.keyof({ machine_learning: null, query: null, saved_query: null }); +export const queryFilter = t.string; +export const references = t.array(t.string); +export const per_page = PositiveInteger; +export const page = PositiveIntegerGreaterThanZero; +export const signal_ids = t.array(t.string); + +// TODO: Can this be more strict or is this is the set of all Elastic Queries? +export const signal_status_query = t.object; + +export const sort_field = t.string; +export const sort_order = t.keyof({ asc: null, desc: null }); +export const tags = t.array(t.string); +export const fields = t.array(t.string); +export const threat_framework = t.string; +export const threat_tactic_id = t.string; +export const threat_tactic_name = t.string; +export const threat_tactic_reference = t.string; +export const threat_tactic = t.type({ + id: threat_tactic_id, + name: threat_tactic_name, + reference: threat_tactic_reference, +}); +export const threat_technique_id = t.string; +export const threat_technique_name = t.string; +export const threat_technique_reference = t.string; +export const threat_technique = t.exact( + t.type({ + id: threat_technique_id, + name: threat_technique_name, + reference: threat_technique_reference, + }) +); +export const threat_techniques = t.array(threat_technique); +export const threat = t.array( + t.exact( + t.type({ + framework: threat_framework, + tactic: threat_tactic, + technique: threat_techniques, + }) + ) +); +export const created_at = IsoDateString; +export const updated_at = IsoDateString; +export const updated_by = t.string; +export const created_by = t.string; +export const version = PositiveIntegerGreaterThanZero; +export const last_success_at = IsoDateString; +export const last_success_message = t.string; +export const last_failure_at = IsoDateString; +export const last_failure_message = t.string; +export const status_date = IsoDateString; +export const rules_installed = PositiveInteger; +export const rules_updated = PositiveInteger; +export const status_code = PositiveInteger; +export const message = t.string; +export const perPage = PositiveInteger; +export const total = PositiveInteger; +export const success = t.boolean; +export const success_count = PositiveInteger; +export const rules_custom_installed = PositiveInteger; +export const rules_not_installed = PositiveInteger; +export const rules_not_updated = PositiveInteger; +export const note = t.string; + +// NOTE: Experimental list support not being shipped currently and behind a feature flag +// TODO: Remove this comment once we lists have passed testing and is ready for the release +export const list_field = t.string; +export const list_values_operator = t.keyof({ included: null, excluded: null }); +export const list_values_type = t.keyof({ match: null, match_all: null, list: null, exists: null }); +export const list_values = t.exact( + t.intersection([ + t.type({ + name: t.string, + }), + t.partial({ + id: t.string, + description: t.string, + created_at, + }), + ]) +); +export const list = t.exact( + t.intersection([ + t.type({ + field: t.string, + values_operator: list_values_operator, + values_type: list_values_type, + }), + t.partial({ values: t.array(list_values) }), + ]) +); +export const list_and = t.intersection([ + list, + t.partial({ + and: t.array(list), + }), +]); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/__mocks__/utils.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/__mocks__/utils.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.test.ts similarity index 86% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.test.ts index 9bbde3d5236db..2a4d75522d010 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.test.ts @@ -9,19 +9,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getErrorPayload } from './__mocks__/utils'; import { errorSchema, ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('error_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an error with a UUID given for id', () => { const error = getErrorPayload(); const decoded = errorSchema.decode(getErrorPayload()); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.ts index f9c776e3b3cdc..986d3ad87ec85 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { rule_id, status_code, message } from './schemas'; +import { rule_id, status_code, message } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ // We use id: t.string intentionally and _never_ the id from global schemas as diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts similarity index 93% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts index 1b7d7994462c7..51163c3d76ed6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts @@ -9,19 +9,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getFindResponseSingle, getBaseResponsePayload } from './__mocks__/utils'; import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; -import { exactCheck } from '../../../../../../common/exact_check'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('find_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a typical single find rules response', () => { const payload = getFindResponseSingle(); const decoded = findRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.ts similarity index 89% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.ts index d7e8a246cfe01..77077ce2e22ac 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { rulesSchema } from './rules_schema'; -import { page, perPage, total } from './schemas'; +import { page, perPage, total } from '../common/schemas'; export const findRulesSchema = t.exact( t.type({ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts new file mode 100644 index 0000000000000..d7efe4b30af11 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { pipe } from 'fp-ts/lib/pipeable'; +import { left, Either } from 'fp-ts/lib/Either'; +import { ImportRulesSchema, importRulesSchema } from './import_rules_schema'; +import { ErrorSchema } from './error_schema'; +import { Errors } from 'io-ts'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; + +describe('import_rules_schema', () => { + test('it should validate an empty import response with no errors', () => { + const payload: ImportRulesSchema = { success: true, success_count: 0, errors: [] }; + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate an empty import response with a single error', () => { + const payload: ImportRulesSchema = { + success: false, + success_count: 0, + errors: [{ error: { status_code: 400, message: 'some message' } }], + }; + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate an empty import response with two errors', () => { + const payload: ImportRulesSchema = { + success: false, + success_count: 0, + errors: [ + { error: { status_code: 400, message: 'some message' } }, + { error: { status_code: 500, message: 'some message' } }, + ], + }; + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT validate a status_count that is a negative number', () => { + const payload: ImportRulesSchema = { + success: false, + success_count: -1, + errors: [], + }; + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "-1" supplied to "success_count"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a success that is not a boolean', () => { + type UnsafeCastForTest = Either< + Errors, + { + success: string; + success_count: number; + errors: Array< + { + id?: string | undefined; + rule_id?: string | undefined; + } & { + error: { + status_code: number; + message: string; + }; + } + >; + } + >; + const payload: Omit & { success: string } = { + success: 'hello', + success_count: 0, + errors: [], + }; + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded as UnsafeCastForTest); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "hello" supplied to "success"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a success an extra invalid field', () => { + const payload: ImportRulesSchema & { invalid_field: string } = { + success: true, + success_count: 0, + errors: [], + invalid_field: 'invalid_data', + }; + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_field"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate an extra field in the second position of the array', () => { + type InvalidError = ErrorSchema & { invalid_data?: string }; + const payload: Omit & { + errors: InvalidError[]; + } = { + success: true, + success_count: 0, + errors: [ + { error: { status_code: 400, message: 'some message' } }, + { invalid_data: 'something', error: { status_code: 500, message: 'some message' } }, + ], + }; + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_data"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts similarity index 91% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts index dec32b18e2b24..adea77e7b933f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { success, success_count } from './schemas'; +import { success, success_count } from '../common/schemas'; import { errorSchema } from './error_schema'; /* eslint-enable @typescript-eslint/camelcase */ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts similarity index 90% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts index 2d3fd75914822..fc3f89996daf1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts @@ -7,19 +7,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { PrePackagedRulesSchema, prePackagedRulesSchema } from './prepackaged_rules_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty prepackaged response with defaults', () => { const payload: PrePackagedRulesSchema = { rules_installed: 0, rules_updated: 0 }; const decoded = prePackagedRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.ts similarity index 89% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.ts index f0eff0ba19753..3b0107c91fee0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { rules_installed, rules_updated } from './schemas'; +import { rules_installed, rules_updated } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ export const prePackagedRulesSchema = t.exact( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts index abe601a546111..eeae72209829e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts @@ -10,19 +10,10 @@ import { PrePackagedRulesStatusSchema, prePackagedRulesStatusSchema, } from './prepackaged_rules_status_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty prepackaged response with defaults', () => { const payload: PrePackagedRulesStatusSchema = { rules_installed: 0, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts similarity index 96% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts index 72e5821eb4697..ee8e7b48a58bc 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts @@ -12,7 +12,7 @@ import { rules_custom_installed, rules_not_installed, rules_not_updated, -} from './schemas'; +} from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ export const prePackagedRulesStatusSchema = t.exact( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts similarity index 93% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts index 98cb2ef058485..04cf012f36dba 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts @@ -11,19 +11,10 @@ import { getBaseResponsePayload, getErrorPayload } from './__mocks__/utils'; import { RulesBulkSchema, rulesBulkSchema } from './rules_bulk_schema'; import { RulesSchema } from './rules_schema'; import { ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rule_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a regular message and and error together with a uuid', () => { const payload: RulesBulkSchema = [getBaseResponsePayload(), getErrorPayload()]; const decoded = rulesBulkSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts new file mode 100644 index 0000000000000..8ed9c30507f4f --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts @@ -0,0 +1,653 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as t from 'io-ts'; + +import { + rulesSchema, + RulesSchema, + checkTypeDependents, + getDependents, + addSavedId, + addQueryFields, + addTimelineTitle, + addMlFields, +} from './rules_schema'; +import { getBaseResponsePayload, getMlRuleResponsePayload } from './__mocks__/utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; +import { TypeAndTimelineOnly } from './type_timeline_only_schema'; + +export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; + +describe('rules_schema', () => { + test('it should validate a type of "query" without anything extra', () => { + const payload = getBaseResponsePayload(); + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate invalid_data for the type', () => { + const payload: Omit & { type: string } = getBaseResponsePayload(); + payload.type = 'invalid_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "invalid_data" supplied to "type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "query" with a saved_id together', () => { + const payload = getBaseResponsePayload(); + payload.type = 'query'; + payload.saved_id = 'save id 123'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expected.type = 'saved_query'; + expected.saved_id = 'save id 123'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + delete payload.saved_id; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "saved_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + expected.timeline_id = 'some timeline id'; + expected.timeline_title = 'some timeline title'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_id = 'some timeline id'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + + describe('checkTypeDependents', () => { + test('it should validate a type of "query" without anything extra', () => { + const payload = getBaseResponsePayload(); + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate invalid_data for the type', () => { + const payload: Omit & { type: string } = getBaseResponsePayload(); + payload.type = 'invalid_data'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "invalid_data" supplied to "type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "query" with a saved_id together', () => { + const payload = getBaseResponsePayload(); + payload.type = 'query'; + payload.saved_id = 'save id 123'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expected.type = 'saved_query'; + expected.saved_id = 'save id 123'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + delete payload.saved_id; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "saved_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + expected.timeline_id = 'some timeline id'; + expected.timeline_title = 'some timeline title'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_title = 'some timeline title'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_title = 'some timeline title'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_id = 'some timeline id'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + }); + + describe('getDependents', () => { + test('it should validate a type of "query" without anything extra', () => { + const payload = getBaseResponsePayload(); + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate invalid_data for the type', () => { + const payload: Omit & { type: string } = getBaseResponsePayload(); + payload.type = 'invalid_data'; + + const dependents = getDependents((payload as unknown) as TypeAndTimelineOnly); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "invalid_data" supplied to "type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "query" with a saved_id together', () => { + const payload = getBaseResponsePayload(); + payload.type = 'query'; + payload.saved_id = 'save id 123'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expected.type = 'saved_query'; + expected.saved_id = 'save id 123'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + delete payload.saved_id; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "saved_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + expected.timeline_id = 'some timeline id'; + expected.timeline_title = 'some timeline title'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_title = 'some timeline title'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_title = 'some timeline title'; + + const decoded = checkTypeDependents(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_id = 'some timeline id'; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it validates an ML rule response', () => { + const payload = getMlRuleResponsePayload(); + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getMlRuleResponsePayload(); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it rejects a response with both ML and query properties', () => { + const payload = { + ...getBaseResponsePayload(), + ...getMlRuleResponsePayload(), + }; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "query,language"']); + expect(message.schema).toEqual({}); + }); + }); + + describe('addSavedId', () => { + test('should return empty array if not given a type of "saved_query"', () => { + const emptyArray = addSavedId({ type: 'query' }); + const expected: t.Mixed[] = []; + expect(emptyArray).toEqual(expected); + }); + + test('should array of size 1 given a "saved_query"', () => { + const array = addSavedId({ type: 'saved_query' }); + expect(array.length).toEqual(1); + }); + }); + + describe('addTimelineTitle', () => { + test('should return empty array if not given a timeline_id', () => { + const emptyArray = addTimelineTitle({ type: 'query' }); + const expected: t.Mixed[] = []; + expect(emptyArray).toEqual(expected); + }); + + test('should array of size 2 given a "timeline_id" that is not null', () => { + const array = addTimelineTitle({ type: 'query', timeline_id: 'some id' }); + expect(array.length).toEqual(2); + }); + }); + + describe('addQueryFields', () => { + test('should return empty array if type is not "query"', () => { + const fields = addQueryFields({ type: 'machine_learning' }); + const expected: t.Mixed[] = []; + expect(fields).toEqual(expected); + }); + + test('should return two fields for a rule of type "query"', () => { + const fields = addQueryFields({ type: 'query' }); + expect(fields.length).toEqual(2); + }); + + test('should return two fields for a rule of type "saved_query"', () => { + const fields = addQueryFields({ type: 'saved_query' }); + expect(fields.length).toEqual(2); + }); + }); + + describe('addMlFields', () => { + test('should return empty array if type is not "machine_learning"', () => { + const fields = addMlFields({ type: 'query' }); + const expected: t.Mixed[] = []; + expect(fields).toEqual(expected); + }); + + test('should return two fields for a rule of type "machine_learning"', () => { + const fields = addMlFields({ type: 'machine_learning' }); + expect(fields.length).toEqual(2); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts new file mode 100644 index 0000000000000..a7a31ec9e1b59 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ +import * as t from 'io-ts'; +import { isObject } from 'lodash/fp'; +import { Either, left, fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema'; +import { isMlRule } from '../../../machine_learning/helpers'; + +import { + actions, + anomaly_threshold, + description, + enabled, + false_positives, + from, + id, + immutable, + index, + interval, + rule_id, + language, + name, + output_index, + max_signals, + machine_learning_job_id, + query, + references, + severity, + updated_by, + tags, + to, + risk_score, + created_at, + created_by, + updated_at, + saved_id, + timeline_id, + timeline_title, + type, + threat, + throttle, + job_status, + status_date, + last_success_at, + last_success_message, + last_failure_at, + last_failure_message, + version, + filters, + meta, + note, +} from '../common/schemas'; +import { ListsDefaultArray } from '../types/lists_default_array'; + +/** + * This is the required fields for the rules schema response. Put all required properties on + * this base for schemas such as create_rules, update_rules, for the correct validation of the + * output schema. + */ +export const requiredRulesSchema = t.type({ + description, + enabled, + false_positives, + from, + id, + immutable, + interval, + rule_id, + output_index, + max_signals, + risk_score, + name, + references, + severity, + updated_by, + tags, + to, + type, + threat, + created_at, + updated_at, + created_by, + version, + exceptions_list: ListsDefaultArray, +}); + +export type RequiredRulesSchema = t.TypeOf; + +/** + * If you have type dependents or exclusive or situations add them here AND update the + * check_type_dependents file for whichever REST flow it is going through. + */ +export const dependentRulesSchema = t.partial({ + // query fields + language, + query, + + // when type = saved_query, saved_is is required + saved_id, + + // These two are required together or not at all. + timeline_id, + timeline_title, + + // ML fields + anomaly_threshold, + machine_learning_job_id, +}); + +/** + * This is the partial or optional fields for the rules schema. Put all optional + * properties on this. DO NOT PUT type dependents such as xor relationships here. + * Instead use dependentRulesSchema and check_type_dependents for how to do those. + */ +export const partialRulesSchema = t.partial({ + actions, + throttle, + status: job_status, + status_date, + last_success_at, + last_success_message, + last_failure_at, + last_failure_message, + filters, + meta, + index, + note, +}); + +/** + * This is the rules schema WITHOUT typeDependents. You don't normally want to use this for a decode + */ +export const rulesWithoutTypeDependentsSchema = t.intersection([ + t.exact(dependentRulesSchema), + t.exact(partialRulesSchema), + t.exact(requiredRulesSchema), +]); +export type RulesWithoutTypeDependentsSchema = t.TypeOf; + +/** + * This is the rulesSchema you want to use for checking type dependents and all the properties + * through: rulesSchema.decode(someJSONObject) + */ +export const rulesSchema = new t.Type< + RulesWithoutTypeDependentsSchema, + RulesWithoutTypeDependentsSchema, + unknown +>( + 'RulesSchema', + (input: unknown): input is RulesWithoutTypeDependentsSchema => isObject(input), + (input): Either => { + return checkTypeDependents(input); + }, + t.identity +); + +/** + * This is the correct type you want to use for Rules that are outputted from the + * REST interface. This has all base and all optional properties merged together. + */ +export type RulesSchema = t.TypeOf; + +export const addSavedId = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.type === 'saved_query') { + return [t.exact(t.type({ saved_id: dependentRulesSchema.props.saved_id }))]; + } else { + return []; + } +}; + +export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.timeline_id != null) { + return [ + t.exact(t.type({ timeline_title: dependentRulesSchema.props.timeline_title })), + t.exact(t.type({ timeline_id: dependentRulesSchema.props.timeline_id })), + ]; + } else { + return []; + } +}; + +export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.type === 'query' || typeAndTimelineOnly.type === 'saved_query') { + return [ + t.exact(t.type({ query: dependentRulesSchema.props.query })), + t.exact(t.type({ language: dependentRulesSchema.props.language })), + ]; + } else { + return []; + } +}; + +export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (isMlRule(typeAndTimelineOnly.type)) { + return [ + t.exact(t.type({ anomaly_threshold: dependentRulesSchema.props.anomaly_threshold })), + t.exact( + t.type({ machine_learning_job_id: dependentRulesSchema.props.machine_learning_job_id }) + ), + ]; + } else { + return []; + } +}; + +export const getDependents = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed => { + const dependents: t.Mixed[] = [ + t.exact(requiredRulesSchema), + t.exact(partialRulesSchema), + ...addSavedId(typeAndTimelineOnly), + ...addTimelineTitle(typeAndTimelineOnly), + ...addQueryFields(typeAndTimelineOnly), + ...addMlFields(typeAndTimelineOnly), + ]; + + if (dependents.length > 1) { + // This unsafe cast is because t.intersection does not use an array but rather a set of + // tuples and really does not look like they expected us to ever dynamically build up + // intersections, but here we are doing that. Looking at their code, although they limit + // the array elements to 5, it looks like you have N number of intersections + const unsafeCast: [t.Mixed, t.Mixed] = dependents as [t.Mixed, t.Mixed]; + return t.intersection(unsafeCast); + } else { + // We are not allowed to call t.intersection with a single value so we return without + // it here normally. + return dependents[0]; + } +}; + +export const checkTypeDependents = (input: unknown): Either => { + const typeOnlyDecoded = typeAndTimelineOnlySchema.decode(input); + const onLeft = (errors: t.Errors): Either => left(errors); + const onRight = ( + typeAndTimelineOnly: TypeAndTimelineOnly + ): Either => { + const intersections = getDependents(typeAndTimelineOnly); + return intersections.decode(input); + }; + return pipe(typeOnlyDecoded, fold(onLeft, onRight)); +}; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts similarity index 85% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts index 8f06e2c6e49b0..c7335ffd62f02 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts @@ -8,19 +8,10 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { TypeAndTimelineOnly, typeAndTimelineOnlySchema } from './type_timeline_only_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rule_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a a type and timeline_id together', () => { const payload: TypeAndTimelineOnly = { type: 'query', diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts index 6d11ff03563d1..d23d4ad2e83d4 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { timeline_id, type } from './schemas'; +import { timeline_id, type } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ /** diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/iso_date_string.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/iso_date_string.test.ts index 9f9181359d44a..e8bce3f38f4b3 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/iso_date_string.test.ts @@ -7,7 +7,7 @@ import { IsoDateString } from './iso_date_string'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('ios_date_string', () => { test('it should validate a iso string', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/iso_date_string.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/iso_date_string.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.test.ts similarity index 98% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.test.ts index dc0bd6cacf0d6..31e0a8e5c2c73 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.test.ts @@ -7,7 +7,7 @@ import { ListsDefaultArray } from './lists_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('lists_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.ts similarity index 97% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.ts index 743914ad070a2..8244f4a29e193 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.ts @@ -11,7 +11,7 @@ import { list_and as listAnd, list_values as listValues, list_values_operator as listOperator, -} from '../response/schemas'; +} from '../common/schemas'; export type ListsDefaultArrayC = t.Type; export type List = t.TypeOf; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts index a3338c878bd71..821eb066a6531 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts @@ -7,7 +7,7 @@ import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_zero'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/postive_integer.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/postive_integer.test.ts index 48ea2025b9b12..ea00ecf5efe0d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/postive_integer.test.ts @@ -7,7 +7,7 @@ import { PositiveInteger } from './positive_integer'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/references_default_array.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/references_default_array.test.ts index 3aaff7e00ad51..43e2dbdac1fe1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/references_default_array.test.ts @@ -7,7 +7,7 @@ import { ReferencesDefaultArray } from './references_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('references_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/references_default_array.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/references_default_array.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/risk_score.test.ts similarity index 96% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/risk_score.test.ts index 41c0faf4d608d..cf849f28a0963 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/risk_score.test.ts @@ -7,7 +7,7 @@ import { RiskScore } from './risk_score'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('risk_score', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/risk_score.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/risk_score.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/uuid.test.ts similarity index 94% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/uuid.test.ts index b640b449e6b8a..d3a68a7575487 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/uuid.test.ts @@ -7,7 +7,7 @@ import { UUID } from './uuid'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('uuid', () => { test('it should validate a uuid', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/uuid.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/types/uuid.ts diff --git a/x-pack/plugins/siem/common/detection_engine/transform_actions.test.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts similarity index 100% rename from x-pack/plugins/siem/common/detection_engine/transform_actions.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts diff --git a/x-pack/plugins/siem/common/detection_engine/transform_actions.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts similarity index 92% rename from x-pack/plugins/siem/common/detection_engine/transform_actions.ts rename to x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts index 4ce3823575833..7c15bc143e0fd 100644 --- a/x-pack/plugins/siem/common/detection_engine/transform_actions.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../alerting/common'; +import { AlertAction } from '../../../alerts/common'; import { RuleAlertAction } from './types'; export const transformRuleToAlertAction = ({ diff --git a/x-pack/plugins/security_solution/common/detection_engine/types.ts b/x-pack/plugins/security_solution/common/detection_engine/types.ts new file mode 100644 index 0000000000000..431d716a9f205 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; + +import { AlertAction } from '../../../alerts/common'; + +export type RuleAlertAction = Omit & { + action_type_id: string; +}; + +export const RuleTypeSchema = t.keyof({ + query: null, + saved_query: null, + machine_learning: null, +}); +export type RuleType = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts new file mode 100644 index 0000000000000..6c8c5e3f51808 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts @@ -0,0 +1,334 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + EndpointDocGenerator, + Event, + Tree, + TreeNode, + RelatedEventCategory, + ECSCategory, +} from './generate_data'; + +interface Node { + events: Event[]; + children: Node[]; + parent_entity_id?: string; +} + +describe('data generator', () => { + let generator: EndpointDocGenerator; + beforeEach(() => { + generator = new EndpointDocGenerator('seed'); + }); + + it('creates the same documents with same random seed', () => { + const generator1 = new EndpointDocGenerator('seed'); + const generator2 = new EndpointDocGenerator('seed'); + const timestamp = new Date().getTime(); + const metadata1 = generator1.generateHostMetadata(timestamp); + const metadata2 = generator2.generateHostMetadata(timestamp); + expect(metadata1).toEqual(metadata2); + }); + + it('creates different documents with different random seeds', () => { + const generator1 = new EndpointDocGenerator('seed'); + const generator2 = new EndpointDocGenerator('different seed'); + const timestamp = new Date().getTime(); + const metadata1 = generator1.generateHostMetadata(timestamp); + const metadata2 = generator2.generateHostMetadata(timestamp); + expect(metadata1).not.toEqual(metadata2); + }); + + it('creates host metadata documents', () => { + const timestamp = new Date().getTime(); + const metadata = generator.generateHostMetadata(timestamp); + expect(metadata['@timestamp']).toEqual(timestamp); + expect(metadata.event.created).toEqual(timestamp); + expect(metadata.endpoint).not.toBeNull(); + expect(metadata.agent).not.toBeNull(); + expect(metadata.host).not.toBeNull(); + }); + + it('creates policy response documents', () => { + const timestamp = new Date().getTime(); + const hostPolicyResponse = generator.generatePolicyResponse(timestamp); + expect(hostPolicyResponse['@timestamp']).toEqual(timestamp); + expect(hostPolicyResponse.event.created).toEqual(timestamp); + expect(hostPolicyResponse.endpoint).not.toBeNull(); + expect(hostPolicyResponse.agent).not.toBeNull(); + expect(hostPolicyResponse.host).not.toBeNull(); + expect(hostPolicyResponse.endpoint.policy.applied).not.toBeNull(); + }); + + it('creates alert event documents', () => { + const timestamp = new Date().getTime(); + const alert = generator.generateAlert(timestamp); + expect(alert['@timestamp']).toEqual(timestamp); + expect(alert.event.action).not.toBeNull(); + expect(alert.endpoint).not.toBeNull(); + expect(alert.agent).not.toBeNull(); + expect(alert.host).not.toBeNull(); + expect(alert.process.entity_id).not.toBeNull(); + }); + + it('creates process event documents', () => { + const timestamp = new Date().getTime(); + const processEvent = generator.generateEvent({ timestamp }); + expect(processEvent['@timestamp']).toEqual(timestamp); + expect(processEvent.event.category).toEqual('process'); + expect(processEvent.event.kind).toEqual('event'); + expect(processEvent.event.type).toEqual('start'); + expect(processEvent.agent).not.toBeNull(); + expect(processEvent.host).not.toBeNull(); + expect(processEvent.process.entity_id).not.toBeNull(); + expect(processEvent.process.name).not.toBeNull(); + }); + + it('creates other event documents', () => { + const timestamp = new Date().getTime(); + const processEvent = generator.generateEvent({ timestamp, eventCategory: 'dns' }); + expect(processEvent['@timestamp']).toEqual(timestamp); + expect(processEvent.event.category).toEqual('dns'); + expect(processEvent.event.kind).toEqual('event'); + expect(processEvent.event.type).toEqual('start'); + expect(processEvent.agent).not.toBeNull(); + expect(processEvent.host).not.toBeNull(); + expect(processEvent.process.entity_id).not.toBeNull(); + expect(processEvent.process.name).not.toBeNull(); + }); + + describe('creates a resolver tree structure', () => { + let tree: Tree; + const ancestors = 3; + const childrenPerNode = 3; + const generations = 3; + beforeEach(() => { + tree = generator.generateTree({ + alwaysGenMaxChildrenPerNode: true, + ancestors, + children: childrenPerNode, + generations, + percentTerminated: 100, + percentWithRelated: 100, + relatedEvents: [ + { category: RelatedEventCategory.Driver, count: 1 }, + { category: RelatedEventCategory.File, count: 2 }, + { category: RelatedEventCategory.Network, count: 1 }, + ], + }); + }); + + const eventInNode = (event: Event, node: TreeNode) => { + const inLifecycle = node.lifecycle.includes(event); + const inRelated = node.relatedEvents.includes(event); + + return (inRelated || inLifecycle) && event.process.entity_id === node.id; + }; + + it('has the right related events for each node', () => { + const checkRelatedEvents = (node: TreeNode) => { + expect(node.relatedEvents.length).toEqual(4); + + const counts: Record = {}; + for (const event of node.relatedEvents) { + if (Array.isArray(event.event.category)) { + for (const cat of event.event.category) { + counts[cat] = counts[cat] + 1 || 1; + } + } else { + counts[event.event.category] = counts[event.event.category] + 1 || 1; + } + } + expect(counts[ECSCategory.Driver]).toEqual(1); + expect(counts[ECSCategory.File]).toEqual(2); + expect(counts[ECSCategory.Network]).toEqual(1); + }; + + for (const node of tree.ancestry.values()) { + checkRelatedEvents(node); + } + + for (const node of tree.children.values()) { + checkRelatedEvents(node); + } + + checkRelatedEvents(tree.origin); + }); + + it('has the right number of ancestors', () => { + expect(tree.ancestry.size).toEqual(ancestors); + }); + + it('has the right number of total children', () => { + // the total number of children (not including the origin) = ((childrenPerNode^(generations + 1) - 1) / (childrenPerNode - 1)) - 1 + // https://stackoverflow.com/questions/7842397/what-is-the-total-number-of-nodes-in-a-full-k-ary-tree-in-terms-of-the-number-o + const leaves = Math.pow(childrenPerNode, generations); + // last -1 is for the origin since it's not in the children map + const nodes = (childrenPerNode * leaves - 1) / (childrenPerNode - 1) - 1; + expect(tree.children.size).toEqual(nodes); + }); + + it('has 2 lifecycle events for ancestors, children, and the origin', () => { + for (const node of tree.ancestry.values()) { + expect(node.lifecycle.length).toEqual(2); + } + + for (const node of tree.children.values()) { + expect(node.lifecycle.length).toEqual(2); + } + + expect(tree.origin.lifecycle.length).toEqual(2); + }); + + it('has all events in one of the tree fields', () => { + expect(tree.allEvents.length).toBeGreaterThan(0); + + tree.allEvents.forEach((event) => { + if (event.event.kind === 'alert') { + expect(event).toEqual(tree.alertEvent); + } else { + const ancestor = tree.ancestry.get(event.process.entity_id); + if (ancestor) { + expect(eventInNode(event, ancestor)).toBeTruthy(); + return; + } + + const children = tree.children.get(event.process.entity_id); + if (children) { + expect(eventInNode(event, children)).toBeTruthy(); + return; + } + + expect(eventInNode(event, tree.origin)).toBeTruthy(); + } + }); + }); + + const nodeEventCount = (node: TreeNode) => { + return node.lifecycle.length + node.relatedEvents.length; + }; + + it('has the correct number of total events', () => { + // starts at 1 because the alert is in the allEvents array + let total = 1; + for (const node of tree.ancestry.values()) { + total += nodeEventCount(node); + } + + for (const node of tree.children.values()) { + total += nodeEventCount(node); + } + + total += nodeEventCount(tree.origin); + + expect(tree.allEvents.length).toEqual(total); + }); + }); + + describe('creates alert ancestor tree', () => { + let events: Event[]; + + beforeEach(() => { + events = generator.createAlertEventAncestry(3, 0, 0, 0); + }); + + it('with n-1 process events', () => { + for (let i = events.length - 2; i > 0; ) { + const parentEntityIdOfChild = events[i].process.parent?.entity_id; + for ( + ; + --i >= -1 && (events[i].event.kind !== 'event' || events[i].event.category !== 'process'); + + ) { + // related event - skip it + } + expect(i).toBeGreaterThanOrEqual(0); + expect(parentEntityIdOfChild).toEqual(events[i].process.entity_id); + } + }); + + it('with a corresponding alert at the end', () => { + let previousProcessEventIndex = events.length - 2; + for ( + ; + previousProcessEventIndex >= -1 && + (events[previousProcessEventIndex].event.kind !== 'event' || + events[previousProcessEventIndex].event.category !== 'process'); + previousProcessEventIndex-- + ) { + // related event - skip it + } + expect(previousProcessEventIndex).toBeGreaterThanOrEqual(0); + // The alert should be last and have the same entity_id as the previous process event + expect(events[events.length - 1].process.entity_id).toEqual( + events[previousProcessEventIndex].process.entity_id + ); + expect(events[events.length - 1].process.parent?.entity_id).toEqual( + events[previousProcessEventIndex].process.parent?.entity_id + ); + expect(events[events.length - 1].event.kind).toEqual('alert'); + expect(events[events.length - 1].event.category).toEqual('malware'); + }); + }); + + function buildResolverTree(events: Event[]): Node { + // First pass we gather up all the events by entity_id + const tree: Record = {}; + events.forEach((event) => { + if (event.process.entity_id in tree) { + tree[event.process.entity_id].events.push(event); + } else { + tree[event.process.entity_id] = { + events: [event], + children: [], + parent_entity_id: event.process.parent?.entity_id, + }; + } + }); + // Second pass add child references to each node + for (const value of Object.values(tree)) { + if (value.parent_entity_id) { + tree[value.parent_entity_id].children.push(value); + } + } + // The root node must be first in the array or this fails + return tree[events[0].process.entity_id]; + } + + function countResolverEvents(rootNode: Node, generations: number): number { + // Start at the root, traverse N levels of the tree and check that we found all nodes + let nodes = [rootNode]; + let visitedEvents = 0; + for (let i = 0; i < generations + 1; i++) { + let nextNodes: Node[] = []; + nodes.forEach((node) => { + nextNodes = nextNodes.concat(node.children); + visitedEvents += node.events.length; + }); + nodes = nextNodes; + } + return visitedEvents; + } + + it('creates tree of process children', () => { + const timestamp = new Date().getTime(); + const root = generator.generateEvent({ timestamp }); + const generations = 2; + const events = [root, ...generator.descendantsTreeGenerator(root, generations)]; + const rootNode = buildResolverTree(events); + const visitedEvents = countResolverEvents(rootNode, generations); + expect(visitedEvents).toEqual(events.length); + }); + + it('creates full resolver tree', () => { + const alertAncestors = 3; + const generations = 2; + const events = [...generator.fullResolverTreeGenerator(alertAncestors, generations)]; + const rootNode = buildResolverTree(events); + const visitedEvents = countResolverEvents(rootNode, alertAncestors + generations); + expect(visitedEvents).toEqual(events.length); + }); +}); diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts new file mode 100644 index 0000000000000..b17a5aa28ac6a --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -0,0 +1,1121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import uuid from 'uuid'; +import seedrandom from 'seedrandom'; +import { + AlertEvent, + EndpointEvent, + Host, + HostMetadata, + HostOS, + HostPolicyResponse, + HostPolicyResponseActionStatus, + PolicyData, +} from './types'; +import { factory as policyFactory } from './models/policy_config'; + +export type Event = AlertEvent | EndpointEvent; + +interface EventOptions { + timestamp?: number; + entityID?: string; + parentEntityID?: string; + eventType?: string; + eventCategory?: string | string[]; + processName?: string; +} + +const Windows: HostOS[] = [ + { + name: 'windows 10.0', + full: 'Windows 10', + version: '10.0', + variant: 'Windows Pro', + }, + { + name: 'windows 10.0', + full: 'Windows Server 2016', + version: '10.0', + variant: 'Windows Server', + }, + { + name: 'windows 6.2', + full: 'Windows Server 2012', + version: '6.2', + variant: 'Windows Server', + }, + { + name: 'windows 6.3', + full: 'Windows Server 2012R2', + version: '6.3', + variant: 'Windows Server Release 2', + }, +]; + +const Linux: HostOS[] = []; + +const Mac: HostOS[] = []; + +const OS: HostOS[] = [...Windows, ...Mac, ...Linux]; + +const POLICIES: Array<{ name: string; id: string }> = [ + { + name: 'Default', + id: '00000000-0000-0000-0000-000000000000', + }, + { + name: 'With Eventing', + id: 'C2A9093E-E289-4C0A-AA44-8C32A414FA7A', + }, +]; + +const FILE_OPERATIONS: string[] = ['creation', 'open', 'rename', 'execution', 'deletion']; + +interface EventInfo { + category: string | string[]; + /** + * This denotes the `event.type` field for when an event is created, this can be `start` or `creation` + */ + creationType: string; +} + +/** + * The valid ecs categories. + */ +export enum ECSCategory { + Driver = 'driver', + File = 'file', + Network = 'network', + /** + * Registry has not been added to ecs yet. + */ + Registry = 'registry', + Authentication = 'authentication', + Session = 'session', +} + +/** + * High level categories for related events. These specify the type of related events that should be generated. + */ +export enum RelatedEventCategory { + /** + * The Random category allows the related event categories to be chosen randomly + */ + Random = 'random', + Driver = 'driver', + File = 'file', + Network = 'network', + Registry = 'registry', + /** + * Security isn't an actual category but defines a type of related event to be created. + */ + Security = 'security', +} + +/** + * This map defines the relationship between a higher level event type defined by the RelatedEventCategory enums and + * the ECS categories that is should map to. This should only be used for tests that need to determine the exact + * ecs categories that were created based on the related event information passed to the generator. + */ +export const categoryMapping: Record = { + [RelatedEventCategory.Security]: [ECSCategory.Authentication, ECSCategory.Session], + [RelatedEventCategory.Driver]: ECSCategory.Driver, + [RelatedEventCategory.File]: ECSCategory.File, + [RelatedEventCategory.Network]: ECSCategory.Network, + [RelatedEventCategory.Registry]: ECSCategory.Registry, + /** + * Random is only used by the generator to indicate that it should randomly choose the event information when generating + * related events. It does not map to a specific ecs category. + */ + [RelatedEventCategory.Random]: '', +}; + +/** + * The related event category and number of events that should be generated. + */ +export interface RelatedEventInfo { + category: RelatedEventCategory; + count: number; +} + +// These are from the v1 schemas and aren't all valid ECS event categories, still in flux +const OTHER_EVENT_CATEGORIES: Record< + Exclude, + EventInfo +> = { + [RelatedEventCategory.Security]: { + category: categoryMapping[RelatedEventCategory.Security], + creationType: 'start', + }, + [RelatedEventCategory.Driver]: { + category: categoryMapping[RelatedEventCategory.Driver], + creationType: 'start', + }, + [RelatedEventCategory.File]: { + category: categoryMapping[RelatedEventCategory.File], + creationType: 'creation', + }, + [RelatedEventCategory.Network]: { + category: categoryMapping[RelatedEventCategory.Network], + creationType: 'start', + }, + [RelatedEventCategory.Registry]: { + category: categoryMapping[RelatedEventCategory.Registry], + creationType: 'creation', + }, +}; + +interface HostInfo { + elastic: { + agent: { + id: string; + }; + }; + agent: { + version: string; + id: string; + }; + host: Host; + endpoint: { + policy: { + id: string; + }; + }; +} + +interface NodeState { + event: Event; + childrenCreated: number; + maxChildren: number; +} + +/** + * The Tree and TreeNode interfaces define structures to make testing of resolver functionality easier. The `generateTree` + * method builds a `Tree` structures which organizes the different parts of the resolver tree. Maps are used to allow + * tests to quickly verify if the node they retrieved from ES was actually created by the generator or if there is an + * issue with the implementation. The `Tree` structure serves as a source of truth for queries to ES. The entire Tree + * is stored in memory so it can be quickly accessed by the tests. The resolver api_integration tests currently leverage + * these structures for verifying that its implementation is returning the correct documents from ES and structuring + * the response correctly. + */ + +/** + * Defines the fields for each node in the tree. + */ +export interface TreeNode { + /** + * The entity_id for the node + */ + id: string; + lifecycle: Event[]; + relatedEvents: Event[]; +} + +/** + * A resolver tree that makes accessing specific nodes easier for tests. + */ +export interface Tree { + /** + * Map of entity_id to node + */ + children: Map; + /** + * Map of entity_id to node + */ + ancestry: Map; + origin: TreeNode; + alertEvent: Event; + /** + * All events from children, ancestry, origin, and the alert in a single array + */ + allEvents: Event[]; +} + +export interface TreeOptions { + /** + * The value in ancestors does not include the origin/root node + */ + ancestors?: number; + generations?: number; + children?: number; + relatedEvents?: RelatedEventInfo[]; + percentWithRelated?: number; + percentTerminated?: number; + alwaysGenMaxChildrenPerNode?: boolean; +} + +export class EndpointDocGenerator { + commonInfo: HostInfo; + random: seedrandom.prng; + + constructor(seed: string | seedrandom.prng = Math.random().toString()) { + if (typeof seed === 'string') { + this.random = seedrandom(seed); + } else { + this.random = seed; + } + this.commonInfo = this.createHostData(); + } + + /** + * Creates new random IP addresses for the host to simulate new DHCP assignment + */ + public updateHostData() { + this.commonInfo.host.ip = this.randomArray(3, () => this.randomIP()); + } + + /** + * Creates new random policy id for the host to simulate new policy application + */ + public updatePolicyId() { + this.commonInfo.endpoint.policy.id = this.randomChoice(POLICIES).id; + } + + private createHostData(): HostInfo { + return { + agent: { + version: this.randomVersion(), + id: this.seededUUIDv4(), + }, + elastic: { + agent: { + id: this.seededUUIDv4(), + }, + }, + host: { + id: this.seededUUIDv4(), + hostname: this.randomHostname(), + ip: this.randomArray(3, () => this.randomIP()), + mac: this.randomArray(3, () => this.randomMac()), + os: this.randomChoice(OS), + }, + endpoint: { + policy: this.randomChoice(POLICIES), + }, + }; + } + + /** + * Creates a host metadata document + * @param ts - Timestamp to put in the event + */ + public generateHostMetadata(ts = new Date().getTime()): HostMetadata { + return { + '@timestamp': ts, + event: { + created: ts, + }, + ...this.commonInfo, + }; + } + + /** + * Creates an alert from the simulated host represented by this EndpointDocGenerator + * @param ts - Timestamp to put in the event + * @param entityID - entityID of the originating process + * @param parentEntityID - optional entityID of the parent process, if it exists + */ + public generateAlert( + ts = new Date().getTime(), + entityID = this.randomString(10), + parentEntityID?: string + ): AlertEvent { + return { + ...this.commonInfo, + '@timestamp': ts, + event: { + action: this.randomChoice(FILE_OPERATIONS), + kind: 'alert', + category: 'malware', + id: this.seededUUIDv4(), + dataset: 'endpoint', + module: 'endpoint', + type: 'creation', + }, + file: { + owner: 'SYSTEM', + name: 'fake_malware.exe', + path: 'C:/fake_malware.exe', + accessed: ts, + mtime: ts, + created: ts, + size: 3456, + hash: { + md5: 'fake file md5', + sha1: 'fake file sha1', + sha256: 'fake file sha256', + }, + code_signature: { + trusted: false, + subject_name: 'bad signer', + }, + malware_classification: { + identifier: 'endpointpe', + score: 1, + threshold: 0.66, + version: '3.0.33', + }, + temp_file_path: 'C:/temp/fake_malware.exe', + }, + process: { + pid: 2, + name: 'malware writer', + start: ts, + uptime: 0, + user: 'SYSTEM', + entity_id: entityID, + executable: 'C:/malware.exe', + parent: parentEntityID ? { entity_id: parentEntityID, pid: 1 } : undefined, + token: { + domain: 'NT AUTHORITY', + integrity_level: 16384, + integrity_level_name: 'system', + privileges: [ + { + description: 'Replace a process level token', + enabled: false, + name: 'SeAssignPrimaryTokenPrivilege', + }, + ], + sid: 'S-1-5-18', + type: 'tokenPrimary', + user: 'SYSTEM', + }, + code_signature: { + trusted: false, + subject_name: 'bad signer', + }, + hash: { + md5: 'fake md5', + sha1: 'fake sha1', + sha256: 'fake sha256', + }, + }, + dll: [ + { + pe: { + architecture: 'x64', + imphash: 'c30d230b81c734e82e86e2e2fe01cd01', + }, + code_signature: { + subject_name: 'Cybereason Inc', + trusted: true, + }, + compile_time: 1534424710, + hash: { + md5: '1f2d082566b0fc5f2c238a5180db7451', + sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d', + sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2', + }, + malware_classification: { + identifier: 'Whitelisted', + score: 0, + threshold: 0, + version: '3.0.0', + }, + mapped_address: 5362483200, + mapped_size: 0, + path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe', + }, + ], + }; + } + + /** + * Creates an event, customized by the options parameter + * @param options - Allows event field values to be specified + */ + public generateEvent(options: EventOptions = {}): EndpointEvent { + return { + '@timestamp': options.timestamp ? options.timestamp : new Date().getTime(), + agent: { ...this.commonInfo.agent, type: 'endpoint' }, + ecs: { + version: '1.4.0', + }, + event: { + category: options.eventCategory ? options.eventCategory : 'process', + kind: 'event', + type: options.eventType ? options.eventType : 'start', + id: this.seededUUIDv4(), + }, + host: this.commonInfo.host, + process: { + entity_id: options.entityID ? options.entityID : this.randomString(10), + parent: options.parentEntityID ? { entity_id: options.parentEntityID } : undefined, + name: options.processName ? options.processName : randomProcessName(), + }, + }; + } + + /** + * This generates a full resolver tree and keeps the entire tree in memory. This is useful for tests that want + * to compare results from elasticsearch with the actual events created by this generator. Because all the events + * are stored in memory do not use this function to generate large trees. + * + * @param options - options for the layout of the tree, like how many children, generations, and ancestry + * @returns a Tree structure that makes accessing specific events easier + */ + public generateTree(options: TreeOptions = {}): Tree { + const addEventToMap = (nodeMap: Map, event: Event) => { + const nodeId = event.process.entity_id; + // if a node already exists for the entity_id we'll use that one, otherwise let's create a new empty node + // and add the event to the right array. + let node = nodeMap.get(nodeId); + if (!node) { + node = { id: nodeId, lifecycle: [], relatedEvents: [] }; + } + + // place the event in the right array depending on its category + if (event.event.category === 'process') { + node.lifecycle.push(event); + } else { + node.relatedEvents.push(event); + } + return nodeMap.set(nodeId, node); + }; + + const ancestry = this.createAlertEventAncestry( + options.ancestors, + options.relatedEvents, + options.percentWithRelated, + options.percentTerminated + ); + + // create a mapping of entity_id -> lifecycle and related events + // slice gets everything but the last item which is an alert + const ancestryNodes: Map = ancestry + .slice(0, -1) + .reduce(addEventToMap, new Map()); + + const alert = ancestry[ancestry.length - 1]; + const origin = ancestryNodes.get(alert.process.entity_id); + if (!origin) { + throw Error(`could not find origin while building tree: ${alert.process.entity_id}`); + } + + // remove the origin node from the ancestry array + ancestryNodes.delete(alert.process.entity_id); + + const children = Array.from( + this.descendantsTreeGenerator( + alert, + options.generations, + options.children, + options.relatedEvents, + options.percentWithRelated, + options.percentTerminated, + options.alwaysGenMaxChildrenPerNode + ) + ); + + const childrenNodes: Map = children.reduce(addEventToMap, new Map()); + + return { + children: childrenNodes, + ancestry: ancestryNodes, + alertEvent: alert, + allEvents: [...ancestry, ...children], + origin, + }; + } + + /** + * Wrapper generator for fullResolverTreeGenerator to make it easier to quickly stream + * many resolver trees to Elasticsearch. + * @param numAlerts - number of alerts to generate + * @param alertAncestors - number of ancestor generations to create relative to the alert + * @param childGenerations - number of child generations to create relative to the alert + * @param maxChildrenPerNode - maximum number of children for any given node in the tree + * @param relatedEventsPerNode - number of related events (file, registry, etc) to create for each process event in the tree + * @param percentNodesWithRelated - percent of nodes which should have related events + * @param percentTerminated - percent of nodes which will have process termination events + * @param alwaysGenMaxChildrenPerNode - flag to always return the max children per node instead of it being a random number of children + */ + public *alertsGenerator( + numAlerts: number, + alertAncestors?: number, + childGenerations?: number, + maxChildrenPerNode?: number, + relatedEventsPerNode?: number, + percentNodesWithRelated?: number, + percentTerminated?: number, + alwaysGenMaxChildrenPerNode?: boolean + ) { + for (let i = 0; i < numAlerts; i++) { + yield* this.fullResolverTreeGenerator( + alertAncestors, + childGenerations, + maxChildrenPerNode, + relatedEventsPerNode, + percentNodesWithRelated, + percentTerminated, + alwaysGenMaxChildrenPerNode + ); + } + } + + /** + * Generator function that creates the full set of events needed to render resolver. + * The number of nodes grows exponentially with the number of generations and children per node. + * Each node is logically a process, and will have 1 or more process events associated with it. + * @param alertAncestors - number of ancestor generations to create relative to the alert + * @param childGenerations - number of child generations to create relative to the alert + * @param maxChildrenPerNode - maximum number of children for any given node in the tree + * @param relatedEventsPerNode - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node + * or a number which defines the number of related events and will default to random categories + * @param percentNodesWithRelated - percent of nodes which should have related events + * @param percentTerminated - percent of nodes which will have process termination events + * @param alwaysGenMaxChildrenPerNode - flag to always return the max children per node instead of it being a random number of children + */ + public *fullResolverTreeGenerator( + alertAncestors?: number, + childGenerations?: number, + maxChildrenPerNode?: number, + relatedEventsPerNode?: RelatedEventInfo[] | number, + percentNodesWithRelated?: number, + percentTerminated?: number, + alwaysGenMaxChildrenPerNode?: boolean + ) { + const ancestry = this.createAlertEventAncestry( + alertAncestors, + relatedEventsPerNode, + percentNodesWithRelated, + percentTerminated + ); + for (let i = 0; i < ancestry.length; i++) { + yield ancestry[i]; + } + // ancestry will always have at least 2 elements, and the last element will be the alert + yield* this.descendantsTreeGenerator( + ancestry[ancestry.length - 1], + childGenerations, + maxChildrenPerNode, + relatedEventsPerNode, + percentNodesWithRelated, + percentTerminated, + alwaysGenMaxChildrenPerNode + ); + } + + /** + * Creates an alert event and associated process ancestry. The alert event will always be the last event in the return array. + * @param alertAncestors - number of ancestor generations to create + * @param relatedEventsPerNode - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node + * or a number which defines the number of related events and will default to random categories + * @param pctWithRelated - percent of ancestors that will have related events + * @param pctWithTerminated - percent of ancestors that will have termination events + */ + public createAlertEventAncestry( + alertAncestors = 3, + relatedEventsPerNode: RelatedEventInfo[] | number = 5, + pctWithRelated = 30, + pctWithTerminated = 100 + ): Event[] { + const events = []; + const startDate = new Date().getTime(); + const root = this.generateEvent({ timestamp: startDate + 1000 }); + events.push(root); + let ancestor = root; + let timestamp = root['@timestamp'] + 1000; + + // generate related alerts for root + const processDuration: number = 6 * 3600; + if (this.randomN(100) < pctWithRelated) { + for (const relatedEvent of this.relatedEventsGenerator( + ancestor, + relatedEventsPerNode, + processDuration + )) { + events.push(relatedEvent); + } + } + + // generate the termination event for the root + if (this.randomN(100) < pctWithTerminated) { + const termProcessDuration = this.randomN(1000000); // This lets termination events be up to 1 million seconds after the creation event (~11 days) + events.push( + this.generateEvent({ + timestamp: timestamp + termProcessDuration * 1000, + entityID: root.process.entity_id, + parentEntityID: root.process.parent?.entity_id, + eventCategory: 'process', + eventType: 'end', + }) + ); + } + + for (let i = 0; i < alertAncestors; i++) { + ancestor = this.generateEvent({ + timestamp, + parentEntityID: ancestor.process.entity_id, + }); + events.push(ancestor); + timestamp = timestamp + 1000; + + if (this.randomN(100) < pctWithTerminated) { + const termProcessDuration = this.randomN(1000000); // This lets termination events be up to 1 million seconds after the creation event (~11 days) + events.push( + this.generateEvent({ + timestamp: timestamp + termProcessDuration * 1000, + entityID: ancestor.process.entity_id, + parentEntityID: ancestor.process.parent?.entity_id, + eventCategory: 'process', + eventType: 'end', + }) + ); + } + + // generate related alerts for ancestor + if (this.randomN(100) < pctWithRelated) { + for (const relatedEvent of this.relatedEventsGenerator( + ancestor, + relatedEventsPerNode, + processDuration + )) { + events.push(relatedEvent); + } + } + } + events.push( + this.generateAlert(timestamp, ancestor.process.entity_id, ancestor.process.parent?.entity_id) + ); + return events; + } + + /** + * Creates the child generations of a process. The number of returned events grows exponentially with generations and maxChildrenPerNode. + * @param root - The process event to use as the root node of the tree + * @param generations - number of child generations to create. The root node is not counted as a generation. + * @param maxChildrenPerNode - maximum number of children for any given node in the tree + * @param relatedEventsPerNode - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node + * or a number which defines the number of related events and will default to random categories + * @param percentNodesWithRelated - percent of nodes which should have related events + * @param percentChildrenTerminated - percent of nodes which will have process termination events + * @param alwaysGenMaxChildrenPerNode - flag to always return the max children per node instead of it being a random number of children + */ + public *descendantsTreeGenerator( + root: Event, + generations = 2, + maxChildrenPerNode = 2, + relatedEventsPerNode: RelatedEventInfo[] | number = 3, + percentNodesWithRelated = 100, + percentChildrenTerminated = 100, + alwaysGenMaxChildrenPerNode = false + ) { + let maxChildren = this.randomN(maxChildrenPerNode + 1); + if (alwaysGenMaxChildrenPerNode) { + maxChildren = maxChildrenPerNode; + } + + const rootState: NodeState = { + event: root, + childrenCreated: 0, + maxChildren, + }; + const lineage: NodeState[] = [rootState]; + let timestamp = root['@timestamp']; + while (lineage.length > 0) { + const currentState = lineage[lineage.length - 1]; + // If we get to a state node and it has made all the children, move back up a level + if ( + currentState.childrenCreated === currentState.maxChildren || + lineage.length === generations + 1 + ) { + lineage.pop(); + // eslint-disable-next-line no-continue + continue; + } + // Otherwise, add a child and any nodes associated with it + currentState.childrenCreated++; + timestamp = timestamp + 1000; + const child = this.generateEvent({ + timestamp, + parentEntityID: currentState.event.process.entity_id, + }); + + maxChildren = this.randomN(maxChildrenPerNode + 1); + if (alwaysGenMaxChildrenPerNode) { + maxChildren = maxChildrenPerNode; + } + lineage.push({ + event: child, + childrenCreated: 0, + maxChildren, + }); + yield child; + let processDuration: number = 6 * 3600; + if (this.randomN(100) < percentChildrenTerminated) { + processDuration = this.randomN(1000000); // This lets termination events be up to 1 million seconds after the creation event (~11 days) + yield this.generateEvent({ + timestamp: timestamp + processDuration * 1000, + entityID: child.process.entity_id, + parentEntityID: child.process.parent?.entity_id, + eventCategory: 'process', + eventType: 'end', + }); + } + if (this.randomN(100) < percentNodesWithRelated) { + yield* this.relatedEventsGenerator(child, relatedEventsPerNode, processDuration); + } + } + } + + /** + * Creates related events for a process event + * @param node - process event to relate events to by entityID + * @param relatedEvents - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node + * or a number which defines the number of related events and will default to random categories + * @param processDuration - maximum number of seconds after process event that related event timestamp can be + */ + public *relatedEventsGenerator( + node: Event, + relatedEvents: RelatedEventInfo[] | number = 10, + processDuration: number = 6 * 3600 + ) { + let relatedEventsInfo: RelatedEventInfo[]; + if (typeof relatedEvents === 'number') { + relatedEventsInfo = [{ category: RelatedEventCategory.Random, count: relatedEvents }]; + } else { + relatedEventsInfo = relatedEvents; + } + for (const event of relatedEventsInfo) { + let eventInfo: EventInfo; + + for (let i = 0; i < event.count; i++) { + if (event.category === RelatedEventCategory.Random) { + eventInfo = this.randomChoice(Object.values(OTHER_EVENT_CATEGORIES)); + } else { + eventInfo = OTHER_EVENT_CATEGORIES[event.category]; + } + + const ts = node['@timestamp'] + this.randomN(processDuration) * 1000; + yield this.generateEvent({ + timestamp: ts, + entityID: node.process.entity_id, + parentEntityID: node.process.parent?.entity_id, + eventCategory: eventInfo.category, + eventType: eventInfo.creationType, + }); + } + } + } + + /** + * Generates an Ingest `datasource` that includes the Endpoint Policy data + */ + public generatePolicyDatasource(): PolicyData { + const created = new Date(Date.now() - 8.64e7).toISOString(); // 24h ago + return { + id: this.seededUUIDv4(), + name: 'Endpoint Policy', + description: 'Policy to protect the worlds data', + created_at: created, + created_by: 'elastic', + updated_at: new Date().toISOString(), + updated_by: 'elastic', + config_id: this.seededUUIDv4(), + enabled: true, + output_id: '', + inputs: [ + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + policy: { + value: policyFactory(), + }, + }, + }, + ], + namespace: 'default', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '1.0.0', + }, + revision: 1, + }; + } + + /** + * Generates a Host Policy response message + */ + public generatePolicyResponse( + ts = new Date().getTime(), + allStatus?: HostPolicyResponseActionStatus + ): HostPolicyResponse { + const policyVersion = this.seededUUIDv4(); + const status = () => { + return allStatus || this.randomHostPolicyResponseActionStatus(); + }; + return { + '@timestamp': ts, + agent: { + id: this.commonInfo.agent.id, + version: '1.0.0-local.20200416.0', + }, + elastic: { + agent: { + id: this.commonInfo.elastic.agent.id, + }, + }, + ecs: { + version: '1.4.0', + }, + host: { + id: this.commonInfo.host.id, + }, + endpoint: { + policy: { + applied: { + actions: [ + { + name: 'configure_elasticsearch_connection', + message: 'elasticsearch comes configured successfully', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'configure_kernel', + message: 'Failed to configure kernel', + status: HostPolicyResponseActionStatus.failure, + }, + { + name: 'configure_logging', + message: 'Successfully configured logging', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'configure_malware', + message: 'Unexpected error configuring malware', + status: HostPolicyResponseActionStatus.failure, + }, + { + name: 'connect_kernel', + message: 'Successfully initialized minifilter', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'detect_file_open_events', + message: 'Successfully stopped file open event reporting', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'detect_file_write_events', + message: 'Failed to stop file write event reporting', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'detect_image_load_events', + message: 'Successfully started image load event reporting', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'detect_process_events', + message: 'Successfully started process event reporting', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'download_global_artifacts', + message: 'Failed to download EXE model', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'load_config', + message: 'Successfully parsed configuration', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'load_malware_model', + message: 'Error deserializing EXE model; no valid malware model installed', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'read_elasticsearch_config', + message: 'Successfully read Elasticsearch configuration', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'read_events_config', + message: 'Successfully read events configuration', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'read_kernel_config', + message: 'Succesfully read kernel configuration', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'read_logging_config', + message: 'Field (logging.debugview) not found in config', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'read_malware_config', + message: 'Successfully read malware detect configuration', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'workflow', + message: 'Failed to apply a portion of the configuration (kernel)', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'download_model', + message: 'Failed to apply a portion of the configuration (kernel)', + status: HostPolicyResponseActionStatus.success, + }, + { + name: 'ingest_events_config', + message: 'Failed to apply a portion of the configuration (kernel)', + status: HostPolicyResponseActionStatus.success, + }, + ], + id: this.commonInfo.endpoint.policy.id, + response: { + configurations: { + events: { + concerned_actions: ['download_model'], + status: status(), + }, + logging: { + concerned_actions: this.randomHostPolicyResponseActionNames(), + status: status(), + }, + malware: { + concerned_actions: this.randomHostPolicyResponseActionNames(), + status: status(), + }, + streaming: { + concerned_actions: this.randomHostPolicyResponseActionNames(), + status: status(), + }, + }, + }, + artifacts: { + global: { + version: '1.4.0', + identifiers: [ + { + name: 'endpointpe-model', + sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + ], + }, + user: { + version: '1.4.0', + identifiers: [ + { + name: 'user-model', + sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + ], + }, + }, + status: this.randomHostPolicyResponseActionStatus(), + version: policyVersion, + }, + }, + }, + event: { + created: ts, + id: this.seededUUIDv4(), + kind: 'state', + category: 'host', + type: 'change', + module: 'endpoint', + action: 'endpoint_policy_response', + dataset: 'endpoint.policy', + }, + }; + } + + private randomN(n: number): number { + return Math.floor(this.random() * n); + } + + private *randomNGenerator(max: number, count: number) { + let iCount = count; + while (iCount > 0) { + yield this.randomN(max); + iCount = iCount - 1; + } + } + + private randomArray(lengthLimit: number, generator: () => T): T[] { + const rand = this.randomN(lengthLimit) + 1; + return [...Array(rand).keys()].map(generator); + } + + private randomMac(): string { + return [...this.randomNGenerator(255, 6)].map((x) => x.toString(16)).join('-'); + } + + private randomIP(): string { + return [10, ...this.randomNGenerator(255, 3)].map((x) => x.toString()).join('.'); + } + + private randomVersion(): string { + return [6, ...this.randomNGenerator(10, 2)].map((x) => x.toString()).join('.'); + } + + private randomChoice(choices: T[]): T { + return choices[this.randomN(choices.length)]; + } + + private randomString(length: number): string { + return [...this.randomNGenerator(36, length)].map((x) => x.toString(36)).join(''); + } + + private randomHostname(): string { + return `Host-${this.randomString(10)}`; + } + + private seededUUIDv4(): string { + return uuid.v4({ random: [...this.randomNGenerator(255, 16)] }); + } + + private randomHostPolicyResponseActionNames(): string[] { + return this.randomArray(this.randomN(8), () => + this.randomChoice([ + 'load_config', + 'workflow', + 'download_global_artifacts', + 'configure_malware', + 'read_malware_config', + 'load_malware_model', + 'read_kernel_config', + 'configure_kernel', + 'detect_process_events', + 'detect_file_write_events', + 'detect_file_open_events', + 'detect_image_load_events', + 'connect_kernel', + ]) + ); + } + + private randomHostPolicyResponseActionStatus(): HostPolicyResponseActionStatus { + return this.randomChoice([ + HostPolicyResponseActionStatus.failure, + HostPolicyResponseActionStatus.success, + HostPolicyResponseActionStatus.warning, + ]); + } +} + +const fakeProcessNames = [ + 'lsass.exe', + 'notepad.exe', + 'mimikatz.exe', + 'powershell.exe', + 'iexlorer.exe', + 'explorer.exe', +]; +/** Return a random fake process name */ +function randomProcessName(): string { + return fakeProcessNames[Math.floor(Math.random() * fakeProcessNames.length)]; +} diff --git a/x-pack/plugins/siem/common/endpoint/models/event.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.ts similarity index 90% rename from x-pack/plugins/siem/common/endpoint/models/event.ts rename to x-pack/plugins/security_solution/common/endpoint/models/event.ts index 192daba4a717d..2c325d64f8515 100644 --- a/x-pack/plugins/siem/common/endpoint/models/event.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/event.ts @@ -9,6 +9,13 @@ export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEven return (event as LegacyEndpointEvent).endgame !== undefined; } +export function isProcessStart(event: ResolverEvent): boolean { + if (isLegacyEvent(event)) { + return event.event?.type === 'process_start' || event.event?.action === 'fork_event'; + } + return event.event.type === 'start'; +} + export function eventTimestamp(event: ResolverEvent): string | undefined | number { if (isLegacyEvent(event)) { return event.endgame.timestamp_utc; diff --git a/x-pack/plugins/siem/common/endpoint/models/policy_config.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts similarity index 100% rename from x-pack/plugins/siem/common/endpoint/models/policy_config.ts rename to x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts diff --git a/x-pack/plugins/siem/common/endpoint/schema/policy.ts b/x-pack/plugins/security_solution/common/endpoint/schema/policy.ts similarity index 100% rename from x-pack/plugins/siem/common/endpoint/schema/policy.ts rename to x-pack/plugins/security_solution/common/endpoint/schema/policy.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts new file mode 100644 index 0000000000000..8d60a532aa67c --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +/** + * Used to validate GET requests for a complete resolver tree. + */ +export const validateTree = { + params: schema.object({ id: schema.string() }), + query: schema.object({ + children: schema.number({ defaultValue: 10, min: 0, max: 100 }), + generations: schema.number({ defaultValue: 3, min: 0, max: 3 }), + ancestors: schema.number({ defaultValue: 3, min: 0, max: 5 }), + events: schema.number({ defaultValue: 100, min: 0, max: 1000 }), + afterEvent: schema.maybe(schema.string()), + afterChild: schema.maybe(schema.string()), + legacyEndpointID: schema.maybe(schema.string()), + }), +}; + +/** + * Used to validate GET requests for non process events for a specific event. + */ +export const validateEvents = { + params: schema.object({ id: schema.string() }), + query: schema.object({ + events: schema.number({ defaultValue: 100, min: 1, max: 1000 }), + afterEvent: schema.maybe(schema.string()), + legacyEndpointID: schema.maybe(schema.string()), + }), +}; + +/** + * Used to validate GET requests for the ancestors of a process event. + */ +export const validateAncestry = { + params: schema.object({ id: schema.string() }), + query: schema.object({ + ancestors: schema.number({ defaultValue: 0, min: 0, max: 10 }), + legacyEndpointID: schema.maybe(schema.string()), + }), +}; + +/** + * Used to validate GET requests for children of a specified process event. + */ +export const validateChildren = { + params: schema.object({ id: schema.string() }), + query: schema.object({ + children: schema.number({ defaultValue: 10, min: 1, max: 100 }), + generations: schema.number({ defaultValue: 3, min: 1, max: 3 }), + afterChild: schema.maybe(schema.string()), + legacyEndpointID: schema.maybe(schema.string()), + }), +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts new file mode 100644 index 0000000000000..816f9b77115ec --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/types.ts @@ -0,0 +1,733 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Datasource, NewDatasource } from '../../../ingest_manager/common'; + +/** + * Object that allows you to maintain stateful information in the location object across navigation events + * + */ + +export interface AppLocation { + pathname: string; + search: string; + hash: string; + key?: string; + state?: { + isTabChange?: boolean; + }; +} + +/** + * A deep readonly type that will make all children of a given object readonly recursively + */ +export type Immutable = T extends undefined | null | boolean | string | number + ? T + : unknown extends T + ? unknown + : T extends Array + ? ImmutableArray + : T extends Map + ? ImmutableMap + : T extends Set + ? ImmutableSet + : ImmutableObject; + +export type ImmutableArray = ReadonlyArray>; +type ImmutableMap = ReadonlyMap, Immutable>; +type ImmutableSet = ReadonlySet>; +type ImmutableObject = { readonly [K in keyof T]: Immutable }; + +export interface EventStats { + /** + * The total number of related events (all events except process and alerts) that exist for a node. + */ + total: number; + /** + * A mapping of ECS event.category to the number of related events are marked with that category + * For example: + * { + * network: 5, + * file: 2 + * } + */ + byCategory: Record; +} + +/** + * Statistical information for a node in a resolver tree. + */ +export interface ResolverNodeStats { + /** + * The stats for related events (excludes alerts and process events) for a particular node in the resolver tree. + */ + events: EventStats; + /** + * The total number of alerts that exist for a node. + */ + totalAlerts: number; +} + +/** + * A child node can also have additional children so we need to provide a pagination cursor. + */ +export interface ChildNode extends LifecycleNode { + /** + * A child node's pagination cursor can be null for a couple reasons: + * 1. At the time of querying it could have no children in ES, in which case it will be marked as + * null because we know it does not have children during this query. + * 2. If the max level was reached we do not know if this node has children or not so we'll mark it as null + */ + nextChild: string | null; +} + +/** + * The response structure for the children route. The structure is an array of nodes where each node + * has an array of lifecycle events. + */ +export interface ResolverChildren { + childNodes: ChildNode[]; + /** + * This is the children cursor for the origin of a tree. + */ + nextChild: string | null; +} + +/** + * A flattened tree representing the nodes in a resolver graph. + */ +export interface ResolverTree { + /** + * Origin of the tree. This is in the middle of the tree. Typically this would be the same + * process node that generated an alert. + */ + entityID: string; + children: ResolverChildren; + relatedEvents: Omit; + ancestry: ResolverAncestry; + lifecycle: ResolverEvent[]; + stats: ResolverNodeStats; +} + +/** + * The lifecycle events (start, end etc) for a node. + */ +export interface LifecycleNode { + entityID: string; + lifecycle: ResolverEvent[]; + /** + * stats are only set when the entire tree is being fetched + */ + stats?: ResolverNodeStats; +} + +/** + * The response structure when searching for ancestors of a node. + */ +export interface ResolverAncestry { + /** + * An array of ancestors with the lifecycle events grouped together + */ + ancestors: LifecycleNode[]; + /** + * A cursor for retrieving additional ancestors for a particular node. `null` indicates that there were no additional + * ancestors when the request returned. More could have been ingested by ES after the fact though. + */ + nextAncestor: string | null; +} + +/** + * Response structure for the related events route. + */ +export interface ResolverRelatedEvents { + entityID: string; + events: ResolverEvent[]; + nextEvent: string | null; +} + +/** + * Returned by the server via /api/endpoint/metadata + */ +export interface HostResultList { + /* the hosts restricted by the page size */ + hosts: HostInfo[]; + /* the total number of unique hosts in the index */ + total: number; + /* the page size requested */ + request_page_size: number; + /* the page index requested */ + request_page_index: number; +} + +/** + * Operating System metadata for a host. + */ +export interface HostOS { + full: string; + name: string; + version: string; + variant: string; +} + +/** + * Host metadata. Describes an endpoint host. + */ +export interface Host { + id: string; + hostname: string; + ip: string[]; + mac: string[]; + os: HostOS; +} + +/** + * A record of hashes for something. Provides hashes in multiple formats. A favorite structure of the Elastic Endpoint. + */ +interface Hashes { + /** + * A hash in MD5 format. + */ + md5: string; + /** + * A hash in SHA-1 format. + */ + sha1: string; + /** + * A hash in SHA-256 format. + */ + sha256: string; +} + +interface MalwareClassification { + identifier: string; + score: number; + threshold: number; + version: string; +} + +interface ThreadFields { + id: number; + service_name: string; + start: number; + start_address: number; + start_address_module: string; +} + +interface DllFields { + pe: { + architecture: string; + imphash: string; + }; + code_signature: { + subject_name: string; + trusted: boolean; + }; + compile_time: number; + hash: Hashes; + malware_classification: MalwareClassification; + mapped_address: number; + mapped_size: number; + path: string; +} + +/** + * Describes an Alert Event. + */ +export type AlertEvent = Immutable<{ + '@timestamp': number; + agent: { + id: string; + version: string; + }; + event: { + id: string; + action: string; + category: string; + kind: string; + dataset: string; + module: string; + type: string; + }; + endpoint: { + policy: { + id: string; + }; + }; + process: { + code_signature: { + subject_name: string; + trusted: boolean; + }; + command_line?: string; + domain?: string; + pid: number; + ppid?: number; + entity_id: string; + parent?: { + pid: number; + entity_id: string; + }; + name: string; + hash: Hashes; + pe?: { + imphash: string; + }; + executable: string; + sid?: string; + start: number; + malware_classification?: MalwareClassification; + token: { + domain: string; + type: string; + user: string; + sid: string; + integrity_level: number; + integrity_level_name: string; + privileges?: Array<{ + description: string; + name: string; + enabled: boolean; + }>; + }; + thread?: ThreadFields[]; + uptime: number; + user: string; + }; + file: { + owner: string; + name: string; + path: string; + accessed: number; + mtime: number; + created: number; + size: number; + hash: Hashes; + pe?: { + imphash: string; + }; + code_signature: { + trusted: boolean; + subject_name: string; + }; + malware_classification: MalwareClassification; + temp_file_path: string; + }; + host: Host; + dll?: DllFields[]; +}>; + +/** + * The status of the host + */ +export enum HostStatus { + /** + * Default state of the host when no host information is present or host information cannot + * be retrieved. e.g. API error + */ + ERROR = 'error', + + /** + * Host is online as indicated by its checkin status during the last checkin window + */ + ONLINE = 'online', + + /** + * Host is offline as indicated by its checkin status during the last checkin window + */ + OFFLINE = 'offline', +} + +export type HostInfo = Immutable<{ + metadata: HostMetadata; + host_status: HostStatus; +}>; + +export type HostMetadata = Immutable<{ + '@timestamp': number; + event: { + created: number; + }; + elastic: { + agent: { + id: string; + }; + }; + endpoint: { + policy: { + id: string; + }; + }; + agent: { + id: string; + version: string; + }; + host: Host; +}>; + +export interface LegacyEndpointEvent { + '@timestamp': number; + endgame: { + pid?: number; + ppid?: number; + event_type_full?: string; + event_subtype_full?: string; + event_timestamp?: number; + event_type?: number; + unique_pid: number; + unique_ppid?: number; + machine_id?: string; + process_name?: string; + process_path?: string; + timestamp_utc?: string; + serial_event_id?: number; + }; + agent: { + id: string; + type: string; + version: string; + }; + process?: object; + rule?: object; + user?: object; + event?: { + action?: string; + type?: string; + category?: string | string[]; + }; +} + +export interface EndpointEvent { + '@timestamp': number; + agent: { + id: string; + version: string; + type: string; + }; + ecs: { + version: string; + }; + event: { + category: string | string[]; + type: string | string[]; + id: string; + kind: string; + }; + host: { + id: string; + hostname: string; + ip: string[]; + mac: string[]; + os: HostOS; + }; + process: { + entity_id: string; + name: string; + parent?: { + entity_id: string; + name?: string; + }; + }; +} + +export type ResolverEvent = EndpointEvent | LegacyEndpointEvent; + +/** + * Takes a @kbn/config-schema 'schema' type and returns a type that represents valid inputs. + * Similar to `TypeOf`, but allows strings as input for `schema.number()` (which is inline + * with the behavior of the validator.) Also, for `schema.object`, when a value is a `schema.maybe` + * the key will be marked optional (via `?`) so that you can omit keys for optional values. + * + * Use this when creating a value that will be passed to the schema. + * e.g. + * ```ts + * const input: KbnConfigSchemaInputTypeOf = value + * schema.validate(input) // should be valid + * ``` + * Note that because the types coming from `@kbn/config-schema`'s schemas sometimes have deeply nested + * `Type` types, we process the result of `TypeOf` instead, as this will be consistent. + */ +export type KbnConfigSchemaInputTypeOf = T extends Record + ? KbnConfigSchemaInputObjectTypeOf< + T + > /** `schema.number()` accepts strings, so this type should accept them as well. */ + : number extends T + ? T | string + : T; + +/** + * Works like ObjectResultType, except that 'maybe' schema will create an optional key. + * This allows us to avoid passing 'maybeKey: undefined' when constructing such an object. + * + * Instead of using this directly, use `InputTypeOf`. + */ +type KbnConfigSchemaInputObjectTypeOf

    > = { + /** Use ? to make the field optional if the prop accepts undefined. + * This allows us to avoid writing `field: undefined` for optional fields. + */ + [K in Exclude>]?: KbnConfigSchemaInputTypeOf< + P[K] + >; +} & + { [K in keyof KbnConfigSchemaNonOptionalProps

    {i18n.NO_WRITE_ALERTS_CALLOUT_MSG}

    + + {i18n.DISMISS_CALLOUT} + + + ) : null; +}; + +export const NoWriteAlertsCallOut = memo(NoWriteAlertsCallOutComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/translations.ts new file mode 100644 index 0000000000000..d036c422b2fb9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_WRITE_ALERTS_CALLOUT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.noWriteAlertsCallOutTitle', + { + defaultMessage: 'Alerts index permissions required', + } +); + +export const NO_WRITE_ALERTS_CALLOUT_MSG = i18n.translate( + 'xpack.securitySolution.detectionEngine.noWriteAlertsCallOutMsg', + { + defaultMessage: + 'You are currently missing the required permissions to update alerts. Please contact your administrator for further assistance.', + } +); + +export const DISMISS_CALLOUT = i18n.translate( + 'xpack.securitySolution.detectionEngine.dismissNoWriteAlertButton', + { + defaultMessage: 'Dismiss', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/accordion_title/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/accordion_title/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/accordion_title/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/accordion_title/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/accordion_title/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/accordion_title/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/accordion_title/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/accordion_title/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/add_item_form/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/add_item_form/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/add_item_form/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/add_item_form/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/add_item_form/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/add_item_form/index.tsx new file mode 100644 index 0000000000000..c8eb6f69c95ba --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/add_item_form/index.tsx @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiFieldText, + EuiSpacer, +} from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { ChangeEvent, useCallback, useEffect, useState, useRef } from 'react'; +import styled from 'styled-components'; + +import * as RuleI18n from '../../../pages/detection_engine/rules/translations'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports'; + +interface AddItemProps { + addText: string; + field: FieldHook; + dataTestSubj: string; + idAria: string; + isDisabled: boolean; + validate?: (args: unknown) => boolean; +} + +const MyEuiFormRow = styled(EuiFormRow)` + .euiFormRow__labelWrapper { + .euiText { + padding-right: 32px; + } + } +`; + +export const MyAddItemButton = styled(EuiButtonEmpty)` + margin-top: 4px; + + &.euiButtonEmpty--xSmall { + font-size: 12px; + } + + .euiIcon { + width: 12px; + height: 12px; + } +`; + +MyAddItemButton.defaultProps = { + flush: 'left', + iconType: 'plusInCircle', + size: 'xs', +}; + +export const AddItem = ({ + addText, + dataTestSubj, + field, + idAria, + isDisabled, + validate, +}: AddItemProps) => { + const [showValidation, setShowValidation] = useState(false); + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const [haveBeenKeyboardDeleted, setHaveBeenKeyboardDeleted] = useState(-1); + + const inputsRef = useRef([]); + + const removeItem = useCallback( + (index: number) => { + const values = field.value as string[]; + const newValues = [...values.slice(0, index), ...values.slice(index + 1)]; + field.setValue(newValues.length === 0 ? [''] : newValues); + inputsRef.current = [ + ...inputsRef.current.slice(0, index), + ...inputsRef.current.slice(index + 1), + ]; + inputsRef.current = inputsRef.current.map((ref, i) => { + if (i >= index && inputsRef.current[index] != null) { + ref.value = 're-render'; + } + return ref; + }); + }, + [field] + ); + + const addItem = useCallback(() => { + const values = field.value as string[]; + field.setValue([...values, '']); + }, [field]); + + const updateItem = useCallback( + (event: ChangeEvent, index: number) => { + event.persist(); + const values = field.value as string[]; + const value = event.target.value; + field.setValue([...values.slice(0, index), value, ...values.slice(index + 1)]); + }, + [field] + ); + + const handleLastInputRef = useCallback( + (index: number, element: HTMLInputElement | null) => { + if (element != null) { + inputsRef.current = [ + ...inputsRef.current.slice(0, index), + element, + ...inputsRef.current.slice(index + 1), + ]; + } + }, + [inputsRef] + ); + + useEffect(() => { + if ( + haveBeenKeyboardDeleted !== -1 && + !isEmpty(inputsRef.current) && + inputsRef.current[haveBeenKeyboardDeleted] != null + ) { + inputsRef.current[haveBeenKeyboardDeleted].focus(); + setHaveBeenKeyboardDeleted(-1); + } + }, [haveBeenKeyboardDeleted, inputsRef.current]); + + const values = field.value as string[]; + return ( + + <> + {values.map((item, index) => { + const euiFieldProps = { + disabled: isDisabled, + ...(index === values.length - 1 + ? { inputRef: handleLastInputRef.bind(null, index) } + : {}), + ...((inputsRef.current[index] != null && inputsRef.current[index].value !== item) || + inputsRef.current[index] == null + ? { value: item } + : {}), + isInvalid: validate == null ? false : showValidation && validate(item), + }; + return ( +
    + + + setShowValidation(true)} + onChange={(e) => updateItem(e, index)} + fullWidth + {...euiFieldProps} + /> + + + removeItem(index)} + aria-label={RuleI18n.DELETE} + /> + + + + {values.length - 1 !== index && } +
    + ); + })} + + + {addText} + + +
    + ); +}; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/all_rules_tables/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/all_rules_tables/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/all_rules_tables/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/all_rules_tables/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/all_rules_tables/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/all_rules_tables/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/all_rules_tables/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/all_rules_tables/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/anomaly_threshold_slider/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/anomaly_threshold_slider/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/anomaly_threshold_slider/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/anomaly_threshold_slider/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/anomaly_threshold_slider/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/anomaly_threshold_slider/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/anomaly_threshold_slider/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/anomaly_threshold_slider/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/description_step/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/alerts/components/rules/description_step/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/actions_description.tsx similarity index 93% rename from x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/description_step/actions_description.tsx index be96ab10bd2b5..43416abe6e288 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/actions_description.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { startCase } from 'lodash/fp'; -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; const ActionsDescription = ({ actions }: { actions: AlertAction[] }) => { if (!actions.length) return null; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/assets/list_tree_icon.svg b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/assets/list_tree_icon.svg similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/description_step/assets/list_tree_icon.svg rename to x-pack/plugins/security_solution/public/alerts/components/rules/description_step/assets/list_tree_icon.svg diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.test.tsx new file mode 100644 index 0000000000000..b82d1c0a36ab2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.test.tsx @@ -0,0 +1,407 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiLoadingSpinner } from '@elastic/eui'; + +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { + esFilters, + FilterManager, + UI_SETTINGS, +} from '../../../../../../../../src/plugins/data/public'; +import { SeverityBadge } from '../severity_badge'; + +import * as i18n from './translations'; +import { + isNotEmptyArray, + buildQueryBarDescription, + buildThreatDescription, + buildUnorderedListArrayDescription, + buildStringArrayDescription, + buildSeverityDescription, + buildUrlsDescription, + buildNoteDescription, + buildRuleTypeDescription, +} from './helpers'; +import { ListItems } from './types'; + +const setupMock = coreMock.createSetup(); +const uiSettingsMock = (pinnedByDefault: boolean) => (key: string) => { + switch (key) { + case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT: + return pinnedByDefault; + default: + throw new Error(`Unexpected uiSettings key in FilterManager mock: ${key}`); + } +}; +setupMock.uiSettings.get.mockImplementation(uiSettingsMock(true)); +const mockFilterManager = new FilterManager(setupMock.uiSettings); + +const mockQueryBar = { + query: 'test query', + filters: [ + { + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + ], + saved_id: 'test123', +}; + +describe('helpers', () => { + describe('isNotEmptyArray', () => { + test('returns false if empty array', () => { + const result = isNotEmptyArray([]); + expect(result).toBeFalsy(); + }); + + test('returns false if array of empty strings', () => { + const result = isNotEmptyArray(['', '']); + expect(result).toBeFalsy(); + }); + + test('returns true if array of string with space', () => { + const result = isNotEmptyArray([' ']); + expect(result).toBeTruthy(); + }); + + test('returns true if array with at least one non-empty string', () => { + const result = isNotEmptyArray(['', 'abc']); + expect(result).toBeTruthy(); + }); + }); + + describe('buildQueryBarDescription', () => { + test('returns empty array if no filters, query or savedId exist', () => { + const emptyMockQueryBar = { + query: '', + filters: [], + saved_id: '', + }; + const result: ListItems[] = buildQueryBarDescription({ + field: 'queryBar', + filters: emptyMockQueryBar.filters, + filterManager: mockFilterManager, + query: emptyMockQueryBar.query, + savedId: emptyMockQueryBar.saved_id, + }); + expect(result).toEqual([]); + }); + + test('returns expected array of ListItems when filters exists, but no indexPatterns passed in', () => { + const mockQueryBarWithFilters = { + ...mockQueryBar, + query: '', + saved_id: '', + }; + const result: ListItems[] = buildQueryBarDescription({ + field: 'queryBar', + filters: mockQueryBarWithFilters.filters, + filterManager: mockFilterManager, + query: mockQueryBarWithFilters.query, + savedId: mockQueryBarWithFilters.saved_id, + }); + const wrapper = shallow(result[0].description as React.ReactElement); + + expect(result[0].title).toEqual(<>{i18n.FILTERS_LABEL} ); + expect(wrapper.find(EuiLoadingSpinner).exists()).toBeTruthy(); + }); + + test('returns expected array of ListItems when filters AND indexPatterns exist', () => { + const mockQueryBarWithFilters = { + ...mockQueryBar, + query: '', + saved_id: '', + }; + const result: ListItems[] = buildQueryBarDescription({ + field: 'queryBar', + filters: mockQueryBarWithFilters.filters, + filterManager: mockFilterManager, + query: mockQueryBarWithFilters.query, + savedId: mockQueryBarWithFilters.saved_id, + indexPatterns: { fields: [{ name: 'test name', type: 'test type' }], title: 'test title' }, + }); + const wrapper = shallow(result[0].description as React.ReactElement); + const filterLabelComponent = wrapper.find(esFilters.FilterLabel).at(0); + + expect(result[0].title).toEqual(<>{i18n.FILTERS_LABEL} ); + expect(filterLabelComponent.prop('valueLabel')).toEqual('file'); + expect(filterLabelComponent.prop('filter')).toEqual(mockQueryBar.filters[0]); + }); + + test('returns expected array of ListItems when "query.query" exists', () => { + const mockQueryBarWithQuery = { + ...mockQueryBar, + filters: [], + saved_id: '', + }; + const result: ListItems[] = buildQueryBarDescription({ + field: 'queryBar', + filters: mockQueryBarWithQuery.filters, + filterManager: mockFilterManager, + query: mockQueryBarWithQuery.query, + savedId: mockQueryBarWithQuery.saved_id, + }); + expect(result[0].title).toEqual(<>{i18n.QUERY_LABEL} ); + expect(result[0].description).toEqual(<>{mockQueryBarWithQuery.query} ); + }); + + test('returns expected array of ListItems when "savedId" exists', () => { + const mockQueryBarWithSavedId = { + ...mockQueryBar, + query: '', + filters: [], + }; + const result: ListItems[] = buildQueryBarDescription({ + field: 'queryBar', + filters: mockQueryBarWithSavedId.filters, + filterManager: mockFilterManager, + query: mockQueryBarWithSavedId.query, + savedId: mockQueryBarWithSavedId.saved_id, + }); + expect(result[0].title).toEqual(<>{i18n.SAVED_ID_LABEL} ); + expect(result[0].description).toEqual(<>{mockQueryBarWithSavedId.saved_id} ); + }); + }); + + describe('buildThreatDescription', () => { + test('returns empty array if no threats', () => { + const result: ListItems[] = buildThreatDescription({ label: 'Mitre Attack', threat: [] }); + expect(result).toHaveLength(0); + }); + + test('returns empty tactic link if no corresponding tactic id found', () => { + const result: ListItems[] = buildThreatDescription({ + label: 'Mitre Attack', + threat: [ + { + framework: 'MITRE ATTACK', + technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }], + tactic: { reference: 'https://test.com', name: 'Collection', id: 'TA000999' }, + }, + ], + }); + const wrapper = shallow(result[0].description as React.ReactElement); + expect(result[0].title).toEqual('Mitre Attack'); + expect(wrapper.find('[data-test-subj="threatTacticLink"]').text()).toEqual(''); + expect(wrapper.find('[data-test-subj="threatTechniqueLink"]').text()).toEqual( + 'Audio Capture (T1123)' + ); + }); + + test('returns empty technique link if no corresponding technique id found', () => { + const result: ListItems[] = buildThreatDescription({ + label: 'Mitre Attack', + threat: [ + { + framework: 'MITRE ATTACK', + technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123456' }], + tactic: { reference: 'https://test.com', name: 'Collection', id: 'TA0009' }, + }, + ], + }); + const wrapper = shallow(result[0].description as React.ReactElement); + expect(result[0].title).toEqual('Mitre Attack'); + expect(wrapper.find('[data-test-subj="threatTacticLink"]').text()).toEqual( + 'Collection (TA0009)' + ); + expect(wrapper.find('[data-test-subj="threatTechniqueLink"]').text()).toEqual(''); + }); + + test('returns with corresponding tactic and technique link text', () => { + const result: ListItems[] = buildThreatDescription({ + label: 'Mitre Attack', + threat: [ + { + framework: 'MITRE ATTACK', + technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }], + tactic: { reference: 'https://test.com', name: 'Collection', id: 'TA0009' }, + }, + ], + }); + const wrapper = shallow(result[0].description as React.ReactElement); + expect(result[0].title).toEqual('Mitre Attack'); + expect(wrapper.find('[data-test-subj="threatTacticLink"]').text()).toEqual( + 'Collection (TA0009)' + ); + expect(wrapper.find('[data-test-subj="threatTechniqueLink"]').text()).toEqual( + 'Audio Capture (T1123)' + ); + }); + + test('returns corresponding number of tactic and technique links', () => { + const result: ListItems[] = buildThreatDescription({ + label: 'Mitre Attack', + threat: [ + { + framework: 'MITRE ATTACK', + technique: [ + { reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }, + { reference: 'https://test.com', name: 'Clipboard Data', id: 'T1115' }, + ], + tactic: { reference: 'https://test.com', name: 'Collection', id: 'TA0009' }, + }, + { + framework: 'MITRE ATTACK', + technique: [ + { reference: 'https://test.com', name: 'Automated Collection', id: 'T1119' }, + ], + tactic: { reference: 'https://test.com', name: 'Discovery', id: 'TA0007' }, + }, + ], + }); + const wrapper = shallow(result[0].description as React.ReactElement); + + expect(wrapper.find('[data-test-subj="threatTacticLink"]')).toHaveLength(2); + expect(wrapper.find('[data-test-subj="threatTechniqueLink"]')).toHaveLength(3); + }); + }); + + describe('buildUnorderedListArrayDescription', () => { + test('returns empty array if "values" is empty array', () => { + const result: ListItems[] = buildUnorderedListArrayDescription( + 'Test label', + 'falsePositives', + [] + ); + expect(result).toHaveLength(0); + }); + + test('returns ListItem with corresponding number of valid values items', () => { + const result: ListItems[] = buildUnorderedListArrayDescription( + 'Test label', + 'falsePositives', + ['', 'falsePositive1', 'falsePositive2'] + ); + const wrapper = shallow(result[0].description as React.ReactElement); + + expect(result[0].title).toEqual('Test label'); + expect(wrapper.find('[data-test-subj="unorderedListArrayDescriptionItem"]')).toHaveLength(2); + }); + }); + + describe('buildStringArrayDescription', () => { + test('returns empty array if "values" is empty array', () => { + const result: ListItems[] = buildStringArrayDescription('Test label', 'tags', []); + expect(result).toHaveLength(0); + }); + + test('returns ListItem with corresponding number of valid values items', () => { + const result: ListItems[] = buildStringArrayDescription('Test label', 'tags', [ + '', + 'tag1', + 'tag2', + ]); + const wrapper = shallow(result[0].description as React.ReactElement); + + expect(result[0].title).toEqual('Test label'); + expect(wrapper.find('[data-test-subj="stringArrayDescriptionBadgeItem"]')).toHaveLength(2); + expect( + wrapper.find('[data-test-subj="stringArrayDescriptionBadgeItem"]').first().text() + ).toEqual('tag1'); + expect( + wrapper.find('[data-test-subj="stringArrayDescriptionBadgeItem"]').at(1).text() + ).toEqual('tag2'); + }); + }); + + describe('buildSeverityDescription', () => { + test('returns ListItem with passed in label and SeverityBadge component', () => { + const result: ListItems[] = buildSeverityDescription('Test label', 'Test description value'); + + expect(result[0].title).toEqual('Test label'); + expect(result[0].description).toEqual(); + }); + }); + + describe('buildUrlsDescription', () => { + test('returns empty array if "values" is empty array', () => { + const result: ListItems[] = buildUrlsDescription('Test label', []); + expect(result).toHaveLength(0); + }); + + test('returns ListItem with corresponding number of valid values items', () => { + const result: ListItems[] = buildUrlsDescription('Test label', [ + 'www.test.com', + 'www.test2.com', + ]); + const wrapper = shallow(result[0].description as React.ReactElement); + + expect(result[0].title).toEqual('Test label'); + expect(wrapper.find('[data-test-subj="urlsDescriptionReferenceLinkItem"]')).toHaveLength(2); + expect( + wrapper.find('[data-test-subj="urlsDescriptionReferenceLinkItem"]').first().text() + ).toEqual('www.test.com'); + expect( + wrapper.find('[data-test-subj="urlsDescriptionReferenceLinkItem"]').at(1).text() + ).toEqual('www.test2.com'); + }); + }); + + describe('buildNoteDescription', () => { + test('returns ListItem with passed in label and note content', () => { + const noteSample = + 'Cras mattism. [Pellentesque](https://elastic.co). ### Malesuada adipiscing tristique'; + const result: ListItems[] = buildNoteDescription('Test label', noteSample); + const wrapper = shallow(result[0].description as React.ReactElement); + const noteElement = wrapper.find('[data-test-subj="noteDescriptionItem"]').at(0); + + expect(result[0].title).toEqual('Test label'); + expect(noteElement.exists()).toBeTruthy(); + expect(noteElement.text()).toEqual(noteSample); + }); + + test('returns empty array if passed in note is empty string', () => { + const result: ListItems[] = buildNoteDescription('Test label', ''); + + expect(result).toHaveLength(0); + }); + }); + + describe('buildRuleTypeDescription', () => { + it('returns the label for a machine_learning type', () => { + const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'machine_learning'); + + expect(result.title).toEqual('Test label'); + }); + + it('returns a humanized description for a machine_learning type', () => { + const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'machine_learning'); + + expect(result.description).toEqual('Machine Learning'); + }); + + it('returns the label for a query type', () => { + const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'query'); + + expect(result.title).toEqual('Test label'); + }); + + it('returns a humanized description for a query type', () => { + const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'query'); + + expect(result.description).toEqual('Query'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.tsx new file mode 100644 index 0000000000000..091065eedfc22 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.tsx @@ -0,0 +1,294 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBadge, + EuiLoadingSpinner, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiSpacer, + EuiLink, + EuiText, +} from '@elastic/eui'; + +import { isEmpty } from 'lodash/fp'; +import React from 'react'; +import styled from 'styled-components'; + +import { RuleType } from '../../../../../common/detection_engine/types'; +import { esFilters } from '../../../../../../../../src/plugins/data/public'; + +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; + +import * as i18n from './translations'; +import { BuildQueryBarDescription, BuildThreatDescription, ListItems } from './types'; +import { SeverityBadge } from '../severity_badge'; +import ListTreeIcon from './assets/list_tree_icon.svg'; +import { assertUnreachable } from '../../../../common/lib/helpers'; + +const NoteDescriptionContainer = styled(EuiFlexItem)` + height: 105px; + overflow-y: hidden; +`; + +export const isNotEmptyArray = (values: string[]) => !isEmpty(values.join('')); + +const EuiBadgeWrap = (styled(EuiBadge)` + .euiBadge__text { + white-space: pre-wrap !important; + } +` as unknown) as typeof EuiBadge; + +export const buildQueryBarDescription = ({ + field, + filters, + filterManager, + query, + savedId, + indexPatterns, +}: BuildQueryBarDescription): ListItems[] => { + let items: ListItems[] = []; + if (!isEmpty(filters)) { + filterManager.setFilters(filters); + items = [ + ...items, + { + title: <>{i18n.FILTERS_LABEL} , + description: ( + + {filterManager.getFilters().map((filter, index) => ( + + + {indexPatterns != null ? ( + + ) : ( + + )} + + + ))} + + ), + }, + ]; + } + if (!isEmpty(query)) { + items = [ + ...items, + { + title: <>{i18n.QUERY_LABEL} , + description: <>{query} , + }, + ]; + } + if (!isEmpty(savedId)) { + items = [ + ...items, + { + title: <>{i18n.SAVED_ID_LABEL} , + description: <>{savedId} , + }, + ]; + } + return items; +}; + +const ThreatEuiFlexGroup = styled(EuiFlexGroup)` + .euiFlexItem { + margin-bottom: 0px; + } +`; + +const TechniqueLinkItem = styled(EuiButtonEmpty)` + .euiIcon { + width: 8px; + height: 8px; + } +`; + +export const buildThreatDescription = ({ label, threat }: BuildThreatDescription): ListItems[] => { + if (threat.length > 0) { + return [ + { + title: label, + description: ( + + {threat.map((singleThreat, index) => { + const tactic = tacticsOptions.find((t) => t.id === singleThreat.tactic.id); + return ( + + + {tactic != null ? tactic.text : ''} + + + {singleThreat.technique.map((technique) => { + const myTechnique = techniquesOptions.find((t) => t.id === technique.id); + return ( + + + {myTechnique != null ? myTechnique.label : ''} + + + ); + })} + + + ); + })} + + + ), + }, + ]; + } + return []; +}; + +export const buildUnorderedListArrayDescription = ( + label: string, + field: string, + values: string[] +): ListItems[] => { + if (isNotEmptyArray(values)) { + return [ + { + title: label, + description: ( + +
      + {values.map((val) => + isEmpty(val) ? null : ( +
    • + {val} +
    • + ) + )} +
    +
    + ), + }, + ]; + } + return []; +}; + +export const buildStringArrayDescription = ( + label: string, + field: string, + values: string[] +): ListItems[] => { + if (isNotEmptyArray(values)) { + return [ + { + title: label, + description: ( + + {values.map((val: string) => + isEmpty(val) ? null : ( + + + {val} + + + ) + )} + + ), + }, + ]; + } + return []; +}; + +export const buildSeverityDescription = (label: string, value: string): ListItems[] => [ + { + title: label, + description: , + }, +]; + +export const buildUrlsDescription = (label: string, values: string[]): ListItems[] => { + if (isNotEmptyArray(values)) { + return [ + { + title: label, + description: ( + +
      + {values + .filter((v) => !isEmpty(v)) + .map((val, index) => ( +
    • + + {val} + +
    • + ))} +
    +
    + ), + }, + ]; + } + return []; +}; + +export const buildNoteDescription = (label: string, note: string): ListItems[] => { + if (note.trim() !== '') { + return [ + { + title: label, + description: ( + +
    + {note} +
    +
    + ), + }, + ]; + } + return []; +}; + +export const buildRuleTypeDescription = (label: string, ruleType: RuleType): ListItems[] => { + switch (ruleType) { + case 'machine_learning': { + return [ + { + title: label, + description: i18n.ML_TYPE_DESCRIPTION, + }, + ]; + } + case 'query': + case 'saved_query': { + return [ + { + title: label, + description: i18n.QUERY_TYPE_DESCRIPTION, + }, + ]; + } + default: + return assertUnreachable(ruleType); + } +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx new file mode 100644 index 0000000000000..b8f81f6d7e5f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx @@ -0,0 +1,476 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + StepRuleDescriptionComponent, + addFilterStateIfNotThere, + buildListItems, + getDescriptionItem, +} from '.'; + +import { + esFilters, + Filter, + FilterManager, + UI_SETTINGS, +} from '../../../../../../../../src/plugins/data/public'; +import { + mockAboutStepRule, + mockDefineStepRule, +} from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; +import * as i18n from './translations'; + +import { schema } from '../step_about_rule/schema'; +import { ListItems } from './types'; +import { AboutStepRule } from '../../../pages/detection_engine/rules/types'; + +jest.mock('../../../../common/lib/kibana'); + +describe('description_step', () => { + const setupMock = coreMock.createSetup(); + const uiSettingsMock = (pinnedByDefault: boolean) => (key: string) => { + switch (key) { + case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT: + return pinnedByDefault; + default: + throw new Error(`Unexpected uiSettings key in FilterManager mock: ${key}`); + } + }; + let mockFilterManager: FilterManager; + let mockAboutStep: AboutStepRule; + + beforeEach(() => { + setupMock.uiSettings.get.mockImplementation(uiSettingsMock(true)); + mockFilterManager = new FilterManager(setupMock.uiSettings); + mockAboutStep = mockAboutStepRule(); + }); + + describe('StepRuleDescriptionComponent', () => { + test('renders correctly against snapshot when columns is "multi"', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="listItemColumnStepRuleDescription"]')).toHaveLength(2); + }); + + test('renders correctly against snapshot when columns is "single"', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="listItemColumnStepRuleDescription"]')).toHaveLength(1); + }); + + test('renders correctly against snapshot when columns is "singleSplit', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="listItemColumnStepRuleDescription"]')).toHaveLength(1); + expect( + wrapper.find('[data-test-subj="singleSplitStepRuleDescriptionList"]').at(0).prop('type') + ).toEqual('column'); + }); + }); + + describe('addFilterStateIfNotThere', () => { + test('it does not change the state if it is global', () => { + const filters: Filter[] = [ + { + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + { + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + ]; + const output = addFilterStateIfNotThere(filters); + const expected: Filter[] = [ + { + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + { + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + ]; + expect(output).toEqual(expected); + }); + + test('it adds the state if it does not exist as local', () => { + const filters: Filter[] = [ + { + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + { + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + ]; + const output = addFilterStateIfNotThere(filters); + const expected: Filter[] = [ + { + $state: { + store: esFilters.FilterStateStore.APP_STATE, + }, + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + { + $state: { + store: esFilters.FilterStateStore.APP_STATE, + }, + meta: { + alias: null, + disabled: false, + key: 'event.category', + negate: false, + params: { + query: 'file', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'event.category': 'file', + }, + }, + }, + ]; + expect(output).toEqual(expected); + }); + }); + + describe('buildListItems', () => { + test('returns expected ListItems array when given valid inputs', () => { + const result: ListItems[] = buildListItems(mockAboutStep, schema, mockFilterManager); + + expect(result.length).toEqual(9); + }); + }); + + describe('getDescriptionItem', () => { + test('returns ListItem with all values enumerated when value[field] is an array', () => { + const result: ListItems[] = getDescriptionItem( + 'tags', + 'Tags label', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Tags label'); + expect(typeof result[0].description).toEqual('object'); + }); + + test('returns ListItem with description of value[field] when value[field] is a string', () => { + const result: ListItems[] = getDescriptionItem( + 'description', + 'Description label', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Description label'); + expect(result[0].description).toEqual('24/7'); + }); + + test('returns empty array when "value" is a non-existant property in "field"', () => { + const result: ListItems[] = getDescriptionItem( + 'jibberjabber', + 'JibberJabber label', + mockAboutStep, + mockFilterManager + ); + + expect(result.length).toEqual(0); + }); + + describe('queryBar', () => { + test('returns array of ListItems when queryBar exist', () => { + const mockQueryBar = { + isNew: false, + queryBar: { + query: { + query: 'user.name: root or user.name: admin', + language: 'kuery', + }, + filters: null, + saved_id: null, + }, + }; + const result: ListItems[] = getDescriptionItem( + 'queryBar', + 'Query bar label', + mockQueryBar, + mockFilterManager + ); + + expect(result[0].title).toEqual(<>{i18n.QUERY_LABEL} ); + expect(result[0].description).toEqual(<>{mockQueryBar.queryBar.query.query} ); + }); + }); + + describe('threat', () => { + test('returns array of ListItems when threat exist', () => { + const result: ListItems[] = getDescriptionItem( + 'threat', + 'Threat label', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Threat label'); + expect(React.isValidElement(result[0].description)).toBeTruthy(); + }); + + test('filters out threats with tactic.name of "none"', () => { + const mockStep = { + ...mockAboutStep, + threat: [ + { + framework: 'mockFramework', + tactic: { + id: '1234', + name: 'none', + reference: 'reference1', + }, + technique: [ + { + id: '456', + name: 'technique1', + reference: 'technique reference', + }, + ], + }, + ], + }; + const result: ListItems[] = getDescriptionItem( + 'threat', + 'Threat label', + mockStep, + mockFilterManager + ); + + expect(result.length).toEqual(0); + }); + }); + + describe('references', () => { + test('returns array of ListItems when references exist', () => { + const result: ListItems[] = getDescriptionItem( + 'references', + 'Reference label', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Reference label'); + expect(React.isValidElement(result[0].description)).toBeTruthy(); + }); + }); + + describe('falsePositives', () => { + test('returns array of ListItems when falsePositives exist', () => { + const result: ListItems[] = getDescriptionItem( + 'falsePositives', + 'False positives label', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('False positives label'); + expect(React.isValidElement(result[0].description)).toBeTruthy(); + }); + }); + + describe('severity', () => { + test('returns array of ListItems when severity exist', () => { + const result: ListItems[] = getDescriptionItem( + 'severity', + 'Severity label', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Severity label'); + expect(React.isValidElement(result[0].description)).toBeTruthy(); + }); + }); + + describe('riskScore', () => { + test('returns array of ListItems when riskScore exist', () => { + const result: ListItems[] = getDescriptionItem( + 'riskScore', + 'Risk score label', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Risk score label'); + expect(result[0].description).toEqual(21); + }); + }); + + describe('timeline', () => { + test('returns timeline title if one exists', () => { + const mockDefineStep = mockDefineStepRule(); + const result: ListItems[] = getDescriptionItem( + 'timeline', + 'Timeline label', + mockDefineStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Timeline label'); + expect(result[0].description).toEqual('Titled timeline'); + }); + + test('returns default timeline title if none exists', () => { + const mockStep = { + ...mockDefineStepRule(), + timeline: { + id: '12345', + }, + }; + const result: ListItems[] = getDescriptionItem( + 'timeline', + 'Timeline label', + mockStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Timeline label'); + expect(result[0].description).toEqual(DEFAULT_TIMELINE_TITLE); + }); + }); + + describe('note', () => { + test('returns default "note" description', () => { + const result: ListItems[] = getDescriptionItem( + 'note', + 'Investigation guide', + mockAboutStep, + mockFilterManager + ); + + expect(result[0].title).toEqual('Investigation guide'); + expect(React.isValidElement(result[0].description)).toBeTruthy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.tsx new file mode 100644 index 0000000000000..b9642b8761019 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.tsx @@ -0,0 +1,216 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp'; +import React, { memo, useState } from 'react'; +import styled from 'styled-components'; + +import { RuleType } from '../../../../../common/detection_engine/types'; +import { + IIndexPattern, + Filter, + esFilters, + FilterManager, +} from '../../../../../../../../src/plugins/data/public'; +import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; +import { useKibana } from '../../../../common/lib/kibana'; +import { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types'; +import { FieldValueTimeline } from '../pick_timeline'; +import { FormSchema } from '../../../../shared_imports'; +import { ListItems } from './types'; +import { + buildQueryBarDescription, + buildSeverityDescription, + buildStringArrayDescription, + buildThreatDescription, + buildUnorderedListArrayDescription, + buildUrlsDescription, + buildNoteDescription, + buildRuleTypeDescription, +} from './helpers'; +import { useSiemJobs } from '../../../../common/components/ml_popover/hooks/use_siem_jobs'; +import { buildMlJobDescription } from './ml_job_description'; +import { buildActionsDescription } from './actions_description'; +import { buildThrottleDescription } from './throttle_description'; + +const DescriptionListContainer = styled(EuiDescriptionList)` + &.euiDescriptionList--column .euiDescriptionList__title { + width: 30%; + } + &.euiDescriptionList--column .euiDescriptionList__description { + width: 70%; + } +`; + +interface StepRuleDescriptionProps { + columns?: 'multi' | 'single' | 'singleSplit'; + data: unknown; + indexPatterns?: IIndexPattern; + schema: FormSchema; +} + +export const StepRuleDescriptionComponent: React.FC = ({ + data, + columns = 'multi', + indexPatterns, + schema, +}) => { + const kibana = useKibana(); + const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); + const [, siemJobs] = useSiemJobs(true); + + const keys = Object.keys(schema); + const listItems = keys.reduce((acc: ListItems[], key: string) => { + if (key === 'machineLearningJobId') { + return [ + ...acc, + buildMlJobDescription( + get(key, data) as string, + (get(key, schema) as { label: string }).label, + siemJobs + ), + ]; + } + + if (key === 'throttle') { + return [...acc, buildThrottleDescription(get(key, data), get([key, 'label'], schema))]; + } + + if (key === 'actions') { + return [...acc, buildActionsDescription(get(key, data), get([key, 'label'], schema))]; + } + + return [...acc, ...buildListItems(data, pick(key, schema), filterManager, indexPatterns)]; + }, []); + + if (columns === 'multi') { + return ( + + {chunk(Math.ceil(listItems.length / 2), listItems).map((chunkListItems, index) => ( + + + + ))} + + ); + } + + return ( + + + {columns === 'single' ? ( + + ) : ( + + )} + + + ); +}; + +export const StepRuleDescription = memo(StepRuleDescriptionComponent); + +export const buildListItems = ( + data: unknown, + schema: FormSchema, + filterManager: FilterManager, + indexPatterns?: IIndexPattern +): ListItems[] => + Object.keys(schema).reduce( + (acc, field) => [ + ...acc, + ...getDescriptionItem( + field, + get([field, 'label'], schema), + data, + filterManager, + indexPatterns + ), + ], + [] + ); + +export const addFilterStateIfNotThere = (filters: Filter[]): Filter[] => { + return filters.map((filter) => { + if (filter.$state == null) { + return { $state: { store: esFilters.FilterStateStore.APP_STATE }, ...filter }; + } else { + return filter; + } + }); +}; + +export const getDescriptionItem = ( + field: string, + label: string, + data: unknown, + filterManager: FilterManager, + indexPatterns?: IIndexPattern +): ListItems[] => { + if (field === 'queryBar') { + const filters = addFilterStateIfNotThere(get('queryBar.filters', data) ?? []); + const query = get('queryBar.query.query', data); + const savedId = get('queryBar.saved_id', data); + return buildQueryBarDescription({ + field, + filters, + filterManager, + query, + savedId, + indexPatterns, + }); + } else if (field === 'threat') { + const threat: IMitreEnterpriseAttack[] = get(field, data).filter( + (singleThreat: IMitreEnterpriseAttack) => singleThreat.tactic.name !== 'none' + ); + return buildThreatDescription({ label, threat }); + } else if (field === 'references') { + const urls: string[] = get(field, data); + return buildUrlsDescription(label, urls); + } else if (field === 'falsePositives') { + const values: string[] = get(field, data); + return buildUnorderedListArrayDescription(label, field, values); + } else if (Array.isArray(get(field, data))) { + const values: string[] = get(field, data); + return buildStringArrayDescription(label, field, values); + } else if (field === 'severity') { + const val: string = get(field, data); + return buildSeverityDescription(label, val); + } else if (field === 'timeline') { + const timeline = get(field, data) as FieldValueTimeline; + return [ + { + title: label, + description: timeline.title ?? DEFAULT_TIMELINE_TITLE, + }, + ]; + } else if (field === 'note') { + const val: string = get(field, data); + return buildNoteDescription(label, val); + } else if (field === 'ruleType') { + const ruleType: RuleType = get(field, data); + return buildRuleTypeDescription(label, ruleType); + } + + const description: string = get(field, data); + if (isNumber(description) || !isEmpty(description)) { + return [ + { + title: label, + description, + }, + ]; + } + return []; +}; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/ml_job_description.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/ml_job_description.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/description_step/ml_job_description.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/description_step/ml_job_description.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/ml_job_description.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/ml_job_description.tsx similarity index 97% rename from x-pack/plugins/siem/public/alerts/components/rules/description_step/ml_job_description.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/description_step/ml_job_description.tsx index c5df8b1a3db70..d7e06511e7937 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/description_step/ml_job_description.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/ml_job_description.tsx @@ -94,7 +94,7 @@ export const buildMlJobDescription = ( label: string, siemJobs: SiemJob[] ): ListItems => { - const siemJob = siemJobs.find(job => job.id === jobId); + const siemJob = siemJobs.find((job) => job.id === jobId); return { title: label, diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/throttle_description.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/throttle_description.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/description_step/throttle_description.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/description_step/throttle_description.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/translations.tsx new file mode 100644 index 0000000000000..3e639ede7a18b --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/translations.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const FILTERS_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.filtersLabel', + { + defaultMessage: 'Filters', + } +); + +export const QUERY_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.QueryLabel', + { + defaultMessage: 'Custom query', + } +); + +export const SAVED_ID_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.savedIdLabel', + { + defaultMessage: 'Saved query name', + } +); + +export const ML_TYPE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.mlRuleTypeDescription', + { + defaultMessage: 'Machine Learning', + } +); + +export const QUERY_TYPE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.queryRuleTypeDescription', + { + defaultMessage: 'Query', + } +); + +export const ML_JOB_STARTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDescription.mlJobStartedDescription', + { + defaultMessage: 'Started', + } +); + +export const ML_JOB_STOPPED = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDescription.mlJobStoppedDescription', + { + defaultMessage: 'Stopped', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/types.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/description_step/types.ts rename to x-pack/plugins/security_solution/public/alerts/components/rules/description_step/types.ts diff --git a/x-pack/plugins/siem/public/alerts/components/rules/mitre/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/helpers.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/mitre/helpers.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/mitre/helpers.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/mitre/helpers.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/helpers.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/mitre/helpers.ts rename to x-pack/plugins/security_solution/public/alerts/components/rules/mitre/helpers.ts diff --git a/x-pack/plugins/siem/public/alerts/components/rules/mitre/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/mitre/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/mitre/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/index.tsx new file mode 100644 index 0000000000000..88edd0e795531 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/index.tsx @@ -0,0 +1,220 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButtonIcon, + EuiFormRow, + EuiSuperSelect, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiComboBox, + EuiText, +} from '@elastic/eui'; +import { isEmpty, kebabCase, camelCase } from 'lodash/fp'; +import React, { useCallback, useState } from 'react'; +import styled from 'styled-components'; + +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; +import * as Rulei18n from '../../../pages/detection_engine/rules/translations'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports'; +import { threatDefault } from '../step_about_rule/default_value'; +import { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types'; +import { MyAddItemButton } from '../add_item_form'; +import { isMitreAttackInvalid } from './helpers'; +import * as i18n from './translations'; + +const MitreContainer = styled.div` + margin-top: 16px; +`; +const MyEuiSuperSelect = styled(EuiSuperSelect)` + width: 280px; +`; +interface AddItemProps { + field: FieldHook; + dataTestSubj: string; + idAria: string; + isDisabled: boolean; +} + +export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddItemProps) => { + const [showValidation, setShowValidation] = useState(false); + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + const removeItem = useCallback( + (index: number) => { + const values = field.value as string[]; + const newValues = [...values.slice(0, index), ...values.slice(index + 1)]; + if (isEmpty(newValues)) { + field.setValue(threatDefault); + } else { + field.setValue(newValues); + } + }, + [field] + ); + + const addItem = useCallback(() => { + const values = field.value as IMitreEnterpriseAttack[]; + if (!isEmpty(values[values.length - 1])) { + field.setValue([ + ...values, + { tactic: { id: 'none', name: 'none', reference: 'none' }, technique: [] }, + ]); + } else { + field.setValue([{ tactic: { id: 'none', name: 'none', reference: 'none' }, technique: [] }]); + } + }, [field]); + + const updateTactic = useCallback( + (index: number, value: string) => { + const values = field.value as IMitreEnterpriseAttack[]; + const { id, reference, name } = tacticsOptions.find((t) => t.value === value) || { + id: '', + name: '', + reference: '', + }; + field.setValue([ + ...values.slice(0, index), + { + ...values[index], + tactic: { id, reference, name }, + technique: [], + }, + ...values.slice(index + 1), + ]); + }, + [field] + ); + + const updateTechniques = useCallback( + (index: number, selectedOptions: unknown[]) => { + field.setValue([ + ...values.slice(0, index), + { + ...values[index], + technique: selectedOptions, + }, + ...values.slice(index + 1), + ]); + }, + [field] + ); + + const values = field.value as IMitreEnterpriseAttack[]; + + const getSelectTactic = (tacticName: string, index: number, disabled: boolean) => ( + {i18n.TACTIC_PLACEHOLDER}, + value: 'none', + disabled, + }, + ] + : []), + ...tacticsOptions.map((t) => ({ + inputDisplay: <>{t.text}, + value: t.value, + disabled, + })), + ]} + aria-label="" + onChange={updateTactic.bind(null, index)} + fullWidth={false} + valueOfSelected={camelCase(tacticName)} + data-test-subj="mitreTactic" + /> + ); + + const getSelectTechniques = (item: IMitreEnterpriseAttack, index: number, disabled: boolean) => { + const invalid = isMitreAttackInvalid(item.tactic.name, item.technique); + const options = techniquesOptions.filter((t) => + t.tactics.includes(kebabCase(item.tactic.name)) + ); + const selectedOptions = item.technique.map((technic) => ({ + ...technic, + label: `${technic.name} (${technic.id})`, // API doesn't allow for label field + })); + + return ( + + + setShowValidation(true)} + /> + {showValidation && invalid && ( + +

    {errorMessage}

    +
    + )} +
    + + removeItem(index)} + aria-label={Rulei18n.DELETE} + /> + +
    + ); + }; + + return ( + + {values.map((item, index) => ( +
    + + + {index === 0 ? ( + + <>{getSelectTactic(item.tactic.name, index, isDisabled)} + + ) : ( + getSelectTactic(item.tactic.name, index, isDisabled) + )} + + + {index === 0 ? ( + + <>{getSelectTechniques(item, index, isDisabled)} + + ) : ( + getSelectTechniques(item, index, isDisabled) + )} + + + {values.length - 1 !== index && } +
    + ))} + + {i18n.ADD_MITRE_ATTACK} + +
    + ); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/translations.ts new file mode 100644 index 0000000000000..704f950cfb4b9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/mitre/translations.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const TACTIC = i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttack.tacticsDescription', + { + defaultMessage: 'tactic', + } +); + +export const TECHNIQUE = i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttack.techniquesDescription', + { + defaultMessage: 'techniques', + } +); + +export const ADD_MITRE_ATTACK = i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttack.addTitle', + { + defaultMessage: 'Add MITRE ATT&CK\\u2122 threat', + } +); + +export const TECHNIQUES_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttack.techniquesPlaceHolderDescription', + { + defaultMessage: 'Select techniques ...', + } +); + +export const TACTIC_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttack.tacticPlaceHolderDescription', + { + defaultMessage: 'Select tactic ...', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/ml_job_select/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/ml_job_select/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/ml_job_select/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/ml_job_select/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/ml_job_select/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/ml_job_select/index.tsx new file mode 100644 index 0000000000000..cb084d4daa782 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/ml_job_select/index.tsx @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiLink, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; + +import styled from 'styled-components'; +import { isJobStarted } from '../../../../../common/machine_learning/helpers'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports'; +import { useSiemJobs } from '../../../../common/components/ml_popover/hooks/use_siem_jobs'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + ML_JOB_SELECT_PLACEHOLDER_TEXT, + ENABLE_ML_JOB_WARNING, +} from '../step_define_rule/translations'; + +const HelpTextWarningContainer = styled.div` + margin-top: 10px; +`; + +const MlJobSelectEuiFlexGroup = styled(EuiFlexGroup)` + margin-bottom: 5px; +`; + +const HelpText: React.FC<{ href: string; showEnableWarning: boolean }> = ({ + href, + showEnableWarning = false, +}) => ( + <> + + + + ), + }} + /> + {showEnableWarning && ( + + + + {ENABLE_ML_JOB_WARNING} + + + )} + +); + +const JobDisplay: React.FC<{ title: string; description: string }> = ({ title, description }) => ( + <> + {title} + +

    {description}

    +
    + +); + +interface MlJobSelectProps { + describedByIds: string[]; + field: FieldHook; +} + +export const MlJobSelect: React.FC = ({ describedByIds = [], field }) => { + const jobId = field.value as string; + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const [isLoading, siemJobs] = useSiemJobs(false); + const mlUrl = useKibana().services.application.getUrlForApp('ml'); + const handleJobChange = useCallback( + (machineLearningJobId: string) => { + field.setValue(machineLearningJobId); + }, + [field] + ); + const placeholderOption = { + value: 'placeholder', + inputDisplay: ML_JOB_SELECT_PLACEHOLDER_TEXT, + dropdownDisplay: ML_JOB_SELECT_PLACEHOLDER_TEXT, + disabled: true, + }; + + const jobOptions = siemJobs.map((job) => ({ + value: job.id, + inputDisplay: job.id, + dropdownDisplay: , + })); + + const options = [placeholderOption, ...jobOptions]; + + const isJobRunning = useMemo(() => { + // If the selected job is not found in the list, it means the placeholder is selected + // and so we don't want to show the warning, thus isJobRunning will be true when 'job == null' + const job = siemJobs.find((j) => j.id === jobId); + return job == null || isJobStarted(job.jobState, job.datafeedState); + }, [siemJobs, jobId]); + + return ( + + + } + isInvalid={isInvalid} + error={errorMessage} + data-test-subj="mlJobSelect" + describedByIds={describedByIds} + > + + + + + + + + + ); +}; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/next_step/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/alerts/components/rules/next_step/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/next_step/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/alerts/components/rules/next_step/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/siem/public/alerts/components/rules/next_step/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/next_step/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/next_step/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/next_step/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/next_step/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/next_step/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/next_step/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/next_step/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/optional_field_label/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/optional_field_label/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/optional_field_label/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/optional_field_label/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/optional_field_label/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/optional_field_label/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/optional_field_label/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/optional_field_label/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pick_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pick_timeline/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/pick_timeline/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/pick_timeline/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pick_timeline/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pick_timeline/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/pick_timeline/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/pick_timeline/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/translations.ts new file mode 100644 index 0000000000000..b43ef8c615003 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/translations.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PRE_BUILT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.prePackagedRules.emptyPromptTitle', + { + defaultMessage: 'Load Elastic prebuilt detection rules', + } +); + +export const PRE_BUILT_MSG = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.prePackagedRules.emptyPromptMessage', + { + defaultMessage: + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create alerts when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', + } +); + +export const PRE_BUILT_ACTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.prePackagedRules.loadPreBuiltButton', + { + defaultMessage: 'Load prebuilt detection rules', + } +); + +export const CREATE_RULE_ACTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.prePackagedRules.createOwnRuletButton', + { + defaultMessage: 'Create your own rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.updatePrePackagedRulesTitle', + { + defaultMessage: 'Update available for Elastic prebuilt rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_MSG = (updateRules: number) => + i18n.translate('xpack.securitySolution.detectionEngine.rules.updatePrePackagedRulesMsg', { + values: { updateRules }, + defaultMessage: + 'You can update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}}. Note that this will reload deleted Elastic prebuilt rules.', + }); + +export const UPDATE_PREPACKAGED_RULES = (updateRules: number) => + i18n.translate('xpack.securitySolution.detectionEngine.rules.updatePrePackagedRulesButton', { + values: { updateRules }, + defaultMessage: + 'Update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}} ', + }); + +export const RELEASE_NOTES_HELP = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.releaseNotesHelp', + { + defaultMessage: 'Release notes', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/update_callout.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/update_callout.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/update_callout.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/update_callout.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/update_callout.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/update_callout.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/update_callout.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/update_callout.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/query_bar/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/index.tsx new file mode 100644 index 0000000000000..82206b6ba5e3d --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/index.tsx @@ -0,0 +1,285 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Subscription } from 'rxjs'; +import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; + +import { + Filter, + IIndexPattern, + Query, + FilterManager, + SavedQuery, + SavedQueryTimeFilter, +} from '../../../../../../../../src/plugins/data/public'; + +import { BrowserFields } from '../../../../common/containers/source'; +import { OpenTimelineModal } from '../../../../timelines/components/open_timeline/open_timeline_modal'; +import { ActionTimelineToShow } from '../../../../timelines/components/open_timeline/types'; +import { QueryBar } from '../../../../common/components/query_bar'; +import { buildGlobalQuery } from '../../../../timelines/components/timeline/helpers'; +import { getDataProviderFilter } from '../../../../timelines/components/timeline/query_bar'; +import { convertKueryToElasticSearchQuery } from '../../../../common/lib/keury'; +import { useKibana } from '../../../../common/lib/kibana'; +import { TimelineModel } from '../../../../timelines/store/timeline/model'; +import { useSavedQueryServices } from '../../../../common/utils/saved_query_services'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports'; +import * as i18n from './translations'; + +export interface FieldValueQueryBar { + filters: Filter[]; + query: Query; + saved_id?: string; +} +interface QueryBarDefineRuleProps { + browserFields: BrowserFields; + dataTestSubj: string; + field: FieldHook; + idAria: string; + isLoading: boolean; + indexPattern: IIndexPattern; + onCloseTimelineSearch: () => void; + openTimelineSearch: boolean; + resizeParentContainer?: (height: number) => void; +} + +const StyledEuiFormRow = styled(EuiFormRow)` + .kbnTypeahead__items { + max-height: 45vh !important; + } + .globalQueryBar { + padding: 4px 0px 0px 0px; + .kbnQueryBar { + & > div:first-child { + margin: 0px 0px 0px 4px; + } + } + } +`; + +// TODO need to add disabled in the SearchBar + +export const QueryBarDefineRule = ({ + browserFields, + dataTestSubj, + field, + idAria, + indexPattern, + isLoading = false, + onCloseTimelineSearch, + openTimelineSearch = false, + resizeParentContainer, +}: QueryBarDefineRuleProps) => { + const [originalHeight, setOriginalHeight] = useState(-1); + const [loadingTimeline, setLoadingTimeline] = useState(false); + const [savedQuery, setSavedQuery] = useState(null); + const [queryDraft, setQueryDraft] = useState({ query: '', language: 'kuery' }); + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + const kibana = useKibana(); + const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); + + const savedQueryServices = useSavedQueryServices(); + + useEffect(() => { + let isSubscribed = true; + const subscriptions = new Subscription(); + filterManager.setFilters([]); + + subscriptions.add( + filterManager.getUpdates$().subscribe({ + next: () => { + if (isSubscribed) { + const newFilters = filterManager.getFilters(); + const { filters } = field.value as FieldValueQueryBar; + + if (!deepEqual(filters, newFilters)) { + field.setValue({ ...(field.value as FieldValueQueryBar), filters: newFilters }); + } + } + }, + }) + ); + + return () => { + isSubscribed = false; + subscriptions.unsubscribe(); + }; + }, [field.value]); + + useEffect(() => { + let isSubscribed = true; + async function updateFilterQueryFromValue() { + const { filters, query, saved_id: savedId } = field.value as FieldValueQueryBar; + if (!deepEqual(query, queryDraft)) { + setQueryDraft(query); + } + if (!deepEqual(filters, filterManager.getFilters())) { + filterManager.setFilters(filters); + } + if ( + (savedId != null && savedQuery != null && savedId !== savedQuery.id) || + (savedId != null && savedQuery == null) + ) { + try { + const mySavedQuery = await savedQueryServices.getSavedQuery(savedId); + if (isSubscribed && mySavedQuery != null) { + setSavedQuery(mySavedQuery); + } + } catch { + setSavedQuery(null); + } + } else if (savedId == null && savedQuery != null) { + setSavedQuery(null); + } + } + updateFilterQueryFromValue(); + return () => { + isSubscribed = false; + }; + }, [field.value]); + + const onSubmitQuery = useCallback( + (newQuery: Query, timefilter?: SavedQueryTimeFilter) => { + const { query } = field.value as FieldValueQueryBar; + if (!deepEqual(query, newQuery)) { + field.setValue({ ...(field.value as FieldValueQueryBar), query: newQuery }); + } + }, + [field] + ); + + const onChangedQuery = useCallback( + (newQuery: Query) => { + const { query } = field.value as FieldValueQueryBar; + if (!deepEqual(query, newQuery)) { + field.setValue({ ...(field.value as FieldValueQueryBar), query: newQuery }); + } + }, + [field] + ); + + const onSavedQuery = useCallback( + (newSavedQuery: SavedQuery | null) => { + if (newSavedQuery != null) { + const { saved_id: savedId } = field.value as FieldValueQueryBar; + if (newSavedQuery.id !== savedId) { + setSavedQuery(newSavedQuery); + field.setValue({ + filters: newSavedQuery.attributes.filters, + query: newSavedQuery.attributes.query, + saved_id: newSavedQuery.id, + }); + } + } + }, + [field.value] + ); + + const onCloseTimelineModal = useCallback(() => { + setLoadingTimeline(true); + onCloseTimelineSearch(); + }, [onCloseTimelineSearch]); + + const onOpenTimeline = useCallback( + (timeline: TimelineModel) => { + setLoadingTimeline(false); + const newQuery = { + query: timeline.kqlQuery.filterQuery?.kuery?.expression ?? '', + language: timeline.kqlQuery.filterQuery?.kuery?.kind ?? 'kuery', + }; + const dataProvidersDsl = + timeline.dataProviders != null && timeline.dataProviders.length > 0 + ? convertKueryToElasticSearchQuery( + buildGlobalQuery(timeline.dataProviders, browserFields), + indexPattern + ) + : ''; + const newFilters = timeline.filters ?? []; + field.setValue({ + filters: + dataProvidersDsl !== '' + ? [...newFilters, getDataProviderFilter(dataProvidersDsl)] + : newFilters, + query: newQuery, + saved_id: undefined, + }); + }, + [browserFields, field, indexPattern] + ); + + const onMutation = (event: unknown, observer: unknown) => { + if (resizeParentContainer != null) { + const suggestionContainer = document.getElementById('kbnTypeahead__items'); + if (suggestionContainer != null) { + const box = suggestionContainer.getBoundingClientRect(); + const accordionContainer = document.getElementById('define-rule'); + if (accordionContainer != null) { + const accordionBox = accordionContainer.getBoundingClientRect(); + if (originalHeight === -1 || accordionBox.height < originalHeight + box.height) { + resizeParentContainer(originalHeight + box.height - 100); + } + if (originalHeight === -1) { + setOriginalHeight(accordionBox.height); + } + } + } else { + resizeParentContainer(-1); + } + } + }; + + const actionTimelineToHide = useMemo(() => ['duplicate'], []); + + return ( + <> + + + {(mutationRef) => ( +
    + +
    + )} +
    +
    + {openTimelineSearch ? ( + + ) : null} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/translations.tsx new file mode 100644 index 0000000000000..756b0c05f435a --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/query_bar/translations.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const IMPORT_TIMELINE_MODAL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.importTimelineModalTitle', + { + defaultMessage: 'Import query from saved timeline', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/read_only_callout/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/read_only_callout/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/read_only_callout/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/read_only_callout/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/read_only_callout/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/read_only_callout/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/read_only_callout/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/read_only_callout/index.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/read_only_callout/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/read_only_callout/translations.ts new file mode 100644 index 0000000000000..26b6bb83a145d --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/read_only_callout/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const READ_ONLY_CALLOUT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.readOnlyCallOutTitle', + { + defaultMessage: 'Rule permissions required', + } +); + +export const READ_ONLY_CALLOUT_MSG = i18n.translate( + 'xpack.securitySolution.detectionEngine.readOnlyCallOutMsg', + { + defaultMessage: + 'You are currently missing the required permissions to create/edit detection engine rule. Please contact your administrator for further assistance.', + } +); + +export const DISMISS_CALLOUT = i18n.translate( + 'xpack.securitySolution.detectionEngine.dismissButton', + { + defaultMessage: 'Dismiss', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/index.tsx new file mode 100644 index 0000000000000..b77de683d5f20 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/index.tsx @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash/fp'; +import { EuiSpacer, EuiCallOut } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import deepMerge from 'deepmerge'; +import ReactMarkdown from 'react-markdown'; +import styled from 'styled-components'; + +import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../common/constants'; +import { SelectField } from '../../../../shared_imports'; +import { + ActionForm, + ActionType, + loadActionTypes, +} from '../../../../../../triggers_actions_ui/public'; +import { AlertAction } from '../../../../../../alerts/common'; +import { useKibana } from '../../../../common/lib/kibana'; +import { FORM_ERRORS_TITLE } from './translations'; + +type ThrottleSelectField = typeof SelectField; + +const DEFAULT_ACTION_GROUP_ID = 'default'; +const DEFAULT_ACTION_MESSAGE = + 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts'; + +const FieldErrorsContainer = styled.div` + p { + margin-bottom: 0; + } +`; + +export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables }) => { + const [fieldErrors, setFieldErrors] = useState(null); + const [supportedActionTypes, setSupportedActionTypes] = useState(); + const { + http, + triggers_actions_ui: { actionTypeRegistry }, + notifications, + docLinks, + application: { capabilities }, + } = useKibana().services; + + const actions: AlertAction[] = useMemo( + () => (!isEmpty(field.value) ? (field.value as AlertAction[]) : []), + [field.value] + ); + + const setActionIdByIndex = useCallback( + (id: string, index: number) => { + const updatedActions = [...(actions as Array>)]; + updatedActions[index] = deepMerge(updatedActions[index], { id }); + field.setValue(updatedActions); + }, + [field.setValue, actions] + ); + + const setAlertProperty = useCallback( + (updatedActions: AlertAction[]) => field.setValue(updatedActions), + [field] + ); + + const setActionParamsProperty = useCallback( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (key: string, value: any, index: number) => { + const updatedActions = [...actions]; + updatedActions[index].params[key] = value; + field.setValue(updatedActions); + }, + [field.setValue, actions] + ); + + useEffect(() => { + (async function () { + const actionTypes = await loadActionTypes({ http }); + const supportedTypes = actionTypes.filter((actionType) => + NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS.includes(actionType.id) + ); + setSupportedActionTypes(supportedTypes); + })(); + }, []); + + useEffect(() => { + if (field.form.isSubmitting || !field.errors.length) { + return setFieldErrors(null); + } + if ( + field.form.isSubmitted && + !field.form.isSubmitting && + field.form.isValid === false && + field.errors.length + ) { + const errorsString = field.errors.map(({ message }) => message).join('\n'); + return setFieldErrors(errorsString); + } + }, [ + field.form.isSubmitted, + field.form.isSubmitting, + field.isChangingValue, + field.form.isValid, + field.errors, + setFieldErrors, + ]); + + if (!supportedActionTypes) return <>; + + return ( + <> + {fieldErrors ? ( + <> + + + + + + + + ) : null} + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/translations.tsx new file mode 100644 index 0000000000000..8e467307cb6da --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_field/translations.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const FORM_ERRORS_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.ruleActionsField.ruleActionsFormErrorsTitle', + { + defaultMessage: 'Please fix issues listed below', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.test.tsx new file mode 100644 index 0000000000000..6365771acec79 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.test.tsx @@ -0,0 +1,271 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallow, mount } from 'enzyme'; +import React from 'react'; + +import { + deleteRulesAction, + duplicateRulesAction, +} from '../../../pages/detection_engine/rules/all/actions'; +import { RuleActionsOverflow } from './index'; +import { mockRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; + +jest.mock('react-router-dom', () => ({ + useHistory: () => ({ + push: jest.fn(), + }), +})); + +jest.mock('../../../pages/detection_engine/rules/all/actions', () => ({ + deleteRulesAction: jest.fn(), + duplicateRulesAction: jest.fn(), +})); + +describe('RuleActionsOverflow', () => { + describe('snapshots', () => { + test('renders correctly against snapshot', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + }); + }); + + describe('rules details menu panel', () => { + test('there is at least one item when there is a rule within the rules-details-menu-panel', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + const items: unknown[] = wrapper + .find('[data-test-subj="rules-details-menu-panel"]') + .first() + .prop('items'); + + expect(items.length).toBeGreaterThan(0); + }); + + test('items are empty when there is a null rule within the rules-details-menu-panel', () => { + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-menu-panel"]').first().prop('items') + ).toEqual([]); + }); + + test('items are empty when there is an undefined rule within the rules-details-menu-panel', () => { + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-menu-panel"]').first().prop('items') + ).toEqual([]); + }); + + test('it opens the popover when rules-details-popover-button-icon is clicked', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') + ).toEqual(true); + }); + }); + + describe('rules details pop over button icon', () => { + test('it does not open the popover when rules-details-popover-button-icon is clicked when the user does not have permission', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') + ).toEqual(false); + }); + }); + + describe('rules details duplicate rule', () => { + test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => { + const rule = mockRule('id'); + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect(wrapper.find('[data-test-subj="rules-details-delete-rule"] button').exists()).toEqual( + false + ); + }); + + test('it opens the popover when rules-details-popover-button-icon is clicked', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') + ).toEqual(true); + }); + + test('it closes the popover when rules-details-duplicate-rule is clicked', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') + ).toEqual(false); + }); + + test('it calls duplicateRulesAction when rules-details-duplicate-rule is clicked', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click'); + wrapper.update(); + expect(duplicateRulesAction).toHaveBeenCalled(); + }); + + test('it calls duplicateRulesAction with the rule and rule.id when rules-details-duplicate-rule is clicked', () => { + const rule = mockRule('id'); + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click'); + wrapper.update(); + expect(duplicateRulesAction).toHaveBeenCalledWith( + [rule], + [rule.id], + expect.anything(), + expect.anything() + ); + }); + }); + + describe('rules details export rule', () => { + test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => { + const rule = mockRule('id'); + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect(wrapper.find('[data-test-subj="rules-details-export-rule"] button').exists()).toEqual( + false + ); + }); + + test('it closes the popover when rules-details-export-rule is clicked', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') + ).toEqual(false); + }); + + test('it sets the rule.rule_id on the generic downloader when rules-details-export-rule is clicked', () => { + const rule = mockRule('id'); + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-generic-downloader"]').prop('ids') + ).toEqual([rule.rule_id]); + }); + + test('it does not close the pop over on rules-details-export-rule when the rule is an immutable rule and the user does a click', () => { + const rule = mockRule('id'); + rule.immutable = true; + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') + ).toEqual(true); + }); + + test('it does not set the rule.rule_id on rules-details-export-rule when the rule is an immutable rule', () => { + const rule = mockRule('id'); + rule.immutable = true; + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-generic-downloader"]').prop('ids') + ).toEqual([]); + }); + }); + + describe('rules details delete rule', () => { + test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => { + const rule = mockRule('id'); + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + expect(wrapper.find('[data-test-subj="rules-details-delete-rule"] button').exists()).toEqual( + false + ); + }); + + test('it closes the popover when rules-details-delete-rule is clicked', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-delete-rule"] button').simulate('click'); + wrapper.update(); + expect( + wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') + ).toEqual(false); + }); + + test('it calls deleteRulesAction when rules-details-delete-rule is clicked', () => { + const wrapper = mount( + + ); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-delete-rule"] button').simulate('click'); + wrapper.update(); + expect(deleteRulesAction).toHaveBeenCalled(); + }); + + test('it calls deleteRulesAction with the rule.id when rules-details-delete-rule is clicked', () => { + const rule = mockRule('id'); + const wrapper = mount(); + wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); + wrapper.update(); + wrapper.find('[data-test-subj="rules-details-delete-rule"] button').simulate('click'); + wrapper.update(); + expect(deleteRulesAction).toHaveBeenCalledWith( + [rule.id], + expect.anything(), + expect.anything(), + expect.anything() + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.tsx new file mode 100644 index 0000000000000..5a5156fa2b9a3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.tsx @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, + EuiToolTip, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import styled from 'styled-components'; + +import { noop } from 'lodash/fp'; +import { useHistory } from 'react-router-dom'; +import { Rule, exportRules } from '../../../../alerts/containers/detection_engine/rules'; +import * as i18n from './translations'; +import * as i18nActions from '../../../pages/detection_engine/rules/translations'; +import { displaySuccessToast, useStateToaster } from '../../../../common/components/toasters'; +import { + deleteRulesAction, + duplicateRulesAction, +} from '../../../pages/detection_engine/rules/all/actions'; +import { GenericDownloader } from '../../../../common/components/generic_downloader'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../common/components/link_to/redirect_to_detection_engine'; + +const MyEuiButtonIcon = styled(EuiButtonIcon)` + &.euiButtonIcon { + svg { + transform: rotate(90deg); + } + border: 1px solid  ${({ theme }) => theme.euiColorPrimary}; + width: 40px; + height: 40px; + } +`; + +interface RuleActionsOverflowComponentProps { + rule: Rule | null; + userHasNoPermissions: boolean; +} + +/** + * Overflow Actions for a Rule + */ +const RuleActionsOverflowComponent = ({ + rule, + userHasNoPermissions, +}: RuleActionsOverflowComponentProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [rulesToExport, setRulesToExport] = useState([]); + const history = useHistory(); + const [, dispatchToaster] = useStateToaster(); + + const onRuleDeletedCallback = useCallback(() => { + history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules`); + }, [history]); + + const actions = useMemo( + () => + rule != null + ? [ + { + setIsPopoverOpen(false); + await duplicateRulesAction([rule], [rule.id], noop, dispatchToaster); + }} + > + {i18nActions.DUPLICATE_RULE} + , + { + setIsPopoverOpen(false); + setRulesToExport([rule.rule_id]); + }} + > + {i18nActions.EXPORT_RULE} + , + { + setIsPopoverOpen(false); + await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback); + }} + > + {i18nActions.DELETE_RULE} + , + ] + : [], + [rule, userHasNoPermissions] + ); + + const handlePopoverOpen = useCallback(() => { + setIsPopoverOpen(!isPopoverOpen); + }, [setIsPopoverOpen, isPopoverOpen]); + + const button = useMemo( + () => ( + + + + ), + [handlePopoverOpen, userHasNoPermissions] + ); + + return ( + <> + setIsPopoverOpen(false)} + id="ruleActionsOverflow" + isOpen={isPopoverOpen} + data-test-subj="rules-details-popover" + ownFocus={true} + panelPaddingSize="none" + > + + + { + displaySuccessToast( + i18nActions.SUCCESSFULLY_EXPORTED_RULES(exportCount), + dispatchToaster + ); + }} + /> + + ); +}; + +export const RuleActionsOverflow = React.memo(RuleActionsOverflowComponent); + +RuleActionsOverflow.displayName = 'RuleActionsOverflow'; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/translations.ts new file mode 100644 index 0000000000000..862d78936e9d5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ALL_ACTIONS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.components.ruleActionsOverflow.allActionsTitle', + { + defaultMessage: 'All actions', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_status/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/helpers.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_status/helpers.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/helpers.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_status/helpers.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/helpers.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_status/helpers.ts rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/helpers.ts diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_status/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_status/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_status/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_status/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/index.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/translations.ts new file mode 100644 index 0000000000000..30a352a83a324 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_status/translations.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const STATUS = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleStatus.statusDescription', + { + defaultMessage: 'Last response', + } +); + +export const STATUS_AT = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleStatus.statusAtDescription', + { + defaultMessage: 'at', + } +); + +export const STATUS_DATE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleStatus.statusDateDescription', + { + defaultMessage: 'Status date', + } +); + +export const REFRESH = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleStatus.refreshButton', + { + defaultMessage: 'Refresh', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_switch/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_switch/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_switch/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_switch/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_switch/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_switch/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/rule_switch/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/rule_switch/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_switch/index.tsx new file mode 100644 index 0000000000000..79a16bfb386ec --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_switch/index.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiSwitch, + EuiSwitchEvent, +} from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import styled from 'styled-components'; +import React, { useCallback, useState, useEffect } from 'react'; + +import * as i18n from '../../../pages/detection_engine/rules/translations'; +import { enableRules } from '../../../../alerts/containers/detection_engine/rules'; +import { enableRulesAction } from '../../../pages/detection_engine/rules/all/actions'; +import { Action } from '../../../pages/detection_engine/rules/all/reducer'; +import { useStateToaster, displayErrorToast } from '../../../../common/components/toasters'; +import { bucketRulesResponse } from '../../../pages/detection_engine/rules/all/helpers'; + +const StaticSwitch = styled(EuiSwitch)` + .euiSwitch__thumb, + .euiSwitch__icon { + transition: none; + } +`; + +StaticSwitch.displayName = 'StaticSwitch'; + +export interface RuleSwitchProps { + dispatch?: React.Dispatch; + id: string; + enabled: boolean; + isDisabled?: boolean; + isLoading?: boolean; + optionLabel?: string; + onChange?: (enabled: boolean) => void; +} + +/** + * Basic switch component for displaying loader when enabled/disabled + */ +export const RuleSwitchComponent = ({ + dispatch, + id, + isDisabled, + isLoading, + enabled, + optionLabel, + onChange, +}: RuleSwitchProps) => { + const [myIsLoading, setMyIsLoading] = useState(false); + const [myEnabled, setMyEnabled] = useState(enabled ?? false); + const [, dispatchToaster] = useStateToaster(); + + const onRuleStateChange = useCallback( + async (event: EuiSwitchEvent) => { + setMyIsLoading(true); + if (dispatch != null) { + await enableRulesAction([id], event.target.checked!, dispatch, dispatchToaster); + } else { + try { + const enabling = event.target.checked!; + const response = await enableRules({ + ids: [id], + enabled: enabling, + }); + const { rules, errors } = bucketRulesResponse(response); + + if (errors.length > 0) { + setMyIsLoading(false); + const title = enabling + ? i18n.BATCH_ACTION_ACTIVATE_SELECTED_ERROR(1) + : i18n.BATCH_ACTION_DEACTIVATE_SELECTED_ERROR(1); + displayErrorToast( + title, + errors.map((e) => e.error.message), + dispatchToaster + ); + } else { + const [rule] = rules; + setMyEnabled(rule.enabled); + if (onChange != null) { + onChange(rule.enabled); + } + } + } catch { + setMyIsLoading(false); + } + } + setMyIsLoading(false); + }, + [dispatch, id] + ); + + useEffect(() => { + if (myEnabled !== enabled) { + setMyEnabled(enabled); + } + }, [enabled]); + + useEffect(() => { + if (myIsLoading !== isLoading) { + setMyIsLoading(isLoading ?? false); + } + }, [isLoading]); + + return ( + + + {myIsLoading ? ( + + ) : ( + + )} + + + ); +}; + +export const RuleSwitch = React.memo(RuleSwitchComponent); + +RuleSwitch.displayName = 'RuleSwitch'; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/schedule_item_form/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/schedule_item_form/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/index.tsx new file mode 100644 index 0000000000000..9a4f9b25e01f4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/index.tsx @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiFieldNumber, + EuiFormRow, + EuiSelect, + EuiFormControlLayout, +} from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import styled from 'styled-components'; + +import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports'; + +import * as I18n from './translations'; + +interface ScheduleItemProps { + field: FieldHook; + dataTestSubj: string; + idAria: string; + isDisabled: boolean; + minimumValue?: number; +} + +const timeTypeOptions = [ + { value: 's', text: I18n.SECONDS }, + { value: 'm', text: I18n.MINUTES }, + { value: 'h', text: I18n.HOURS }, +]; + +// move optional label to the end of input +const StyledLabelAppend = styled(EuiFlexItem)` + &.euiFlexItem.euiFlexItem--flexGrowZero { + margin-left: 31px; + } +`; + +const StyledEuiFormRow = styled(EuiFormRow)` + max-width: none; + + .euiFormControlLayout { + max-width: 200px !important; + } + + .euiFormControlLayout__childrenWrapper > *:first-child { + box-shadow: none; + height: 38px; + } + + .euiFormControlLayout:not(:first-child) { + border-left: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; + } +`; + +const MyEuiSelect = styled(EuiSelect)` + width: auto; +`; + +export const ScheduleItem = ({ + dataTestSubj, + field, + idAria, + isDisabled, + minimumValue = 0, +}: ScheduleItemProps) => { + const [timeType, setTimeType] = useState('s'); + const [timeVal, setTimeVal] = useState(0); + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + const onChangeTimeType = useCallback( + (e) => { + setTimeType(e.target.value); + field.setValue(`${timeVal}${e.target.value}`); + }, + [timeVal] + ); + + const onChangeTimeVal = useCallback( + (e) => { + const sanitizedValue: number = parseInt(e.target.value, 10); + setTimeVal(sanitizedValue); + field.setValue(`${sanitizedValue}${timeType}`); + }, + [timeType] + ); + + useEffect(() => { + if (field.value !== `${timeVal}${timeType}`) { + const filterTimeVal = (field.value as string).match(/\d+/g); + const filterTimeType = (field.value as string).match(/[a-zA-Z]+/g); + if ( + !isEmpty(filterTimeVal) && + filterTimeVal != null && + !isNaN(Number(filterTimeVal[0])) && + Number(filterTimeVal[0]) !== Number(timeVal) + ) { + setTimeVal(Number(filterTimeVal[0])); + } + if ( + !isEmpty(filterTimeType) && + filterTimeType != null && + ['s', 'm', 'h'].includes(filterTimeType[0]) && + filterTimeType[0] !== timeType + ) { + setTimeType(filterTimeType[0]); + } + } + }, [field.value]); + + // EUI missing some props + const rest = { disabled: isDisabled }; + const label = useMemo( + () => ( + + + {field.label} + + + {field.labelAppend} + + + ), + [field.label, field.labelAppend] + ); + + return ( + + + } + > + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/translations.ts new file mode 100644 index 0000000000000..66bc6ea900925 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/schedule_item_form/translations.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SECONDS = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.secondsOptionDescription', + { + defaultMessage: 'Seconds', + } +); + +export const MINUTES = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.minutesOptionDescription', + { + defaultMessage: 'Minutes', + } +); + +export const HOURS = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.hoursOptionDescription', + { + defaultMessage: 'Hours', + } +); + +export const INVALID_TIME = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.invalidTimeMessageDescription', + { + defaultMessage: 'A time is required.', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/index.tsx new file mode 100644 index 0000000000000..3dad53f532a5b --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/index.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiCard, + EuiFlexGrid, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiLink, + EuiText, +} from '@elastic/eui'; + +import { isMlRule } from '../../../../../common/machine_learning/helpers'; +import { RuleType } from '../../../../../common/detection_engine/types'; +import { FieldHook } from '../../../../shared_imports'; +import { useKibana } from '../../../../common/lib/kibana'; +import * as i18n from './translations'; + +const MlCardDescription = ({ + subscriptionUrl, + hasValidLicense = false, +}: { + subscriptionUrl: string; + hasValidLicense?: boolean; +}) => ( + + {hasValidLicense ? ( + i18n.ML_TYPE_DESCRIPTION + ) : ( + + + + ), + }} + /> + )} + +); + +interface SelectRuleTypeProps { + describedByIds?: string[]; + field: FieldHook; + hasValidLicense?: boolean; + isMlAdmin?: boolean; + isReadOnly?: boolean; +} + +export const SelectRuleType: React.FC = ({ + describedByIds = [], + field, + isReadOnly = false, + hasValidLicense = false, + isMlAdmin = false, +}) => { + const ruleType = field.value as RuleType; + const setType = useCallback( + (type: RuleType) => { + field.setValue(type); + }, + [field] + ); + const setMl = useCallback(() => setType('machine_learning'), [setType]); + const setQuery = useCallback(() => setType('query'), [setType]); + const mlCardDisabled = isReadOnly || !hasValidLicense || !isMlAdmin; + const licensingUrl = useKibana().services.application.getUrlForApp('kibana', { + path: '#/management/stack/license_management', + }); + + return ( + + + + } + selectable={{ + isDisabled: isReadOnly, + onClick: setQuery, + isSelected: !isMlRule(ruleType), + }} + /> + + + + } + icon={} + isDisabled={mlCardDisabled} + selectable={{ + isDisabled: mlCardDisabled, + onClick: setMl, + isSelected: isMlRule(ruleType), + }} + /> + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/translations.ts new file mode 100644 index 0000000000000..8b92d20616f7c --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/select_rule_type/translations.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const QUERY_TYPE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.queryTypeTitle', + { + defaultMessage: 'Custom query', + } +); + +export const QUERY_TYPE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.queryTypeDescription', + { + defaultMessage: 'Use KQL or Lucene to detect issues across indices.', + } +); + +export const ML_TYPE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.mlTypeTitle', + { + defaultMessage: 'Machine Learning', + } +); + +export const ML_TYPE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.mlTypeDescription', + { + defaultMessage: 'Select ML job to detect anomalous activity.', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/severity_badge/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/severity_badge/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/severity_badge/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/severity_badge/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/severity_badge/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/severity_badge/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/severity_badge/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/severity_badge/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/status_icon/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/status_icon/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/status_icon/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/status_icon/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/status_icon/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/status_icon/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/status_icon/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/status_icon/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/data.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/data.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/data.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/data.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/default_value.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/default_value.ts rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/default_value.ts diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.test.tsx new file mode 100644 index 0000000000000..3eedbcc97bbd1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.test.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import { ThemeProvider } from 'styled-components'; +import euiDarkVars from '@elastic/eui/dist/eui_theme_light.json'; + +import { StepAboutRule } from '.'; + +import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { StepRuleDescription } from '../description_step'; +import { stepAboutDefaultValue } from './default_value'; + +const theme = () => ({ eui: euiDarkVars, darkMode: true }); + +/* eslint-disable no-console */ +// Silence until enzyme fixed to use ReactTestUtils.act() +const originalError = console.error; +beforeAll(() => { + console.error = jest.fn(); +}); +afterAll(() => { + console.error = originalError; +}); +/* eslint-enable no-console */ + +describe('StepAboutRuleComponent', () => { + test('it renders StepRuleDescription if isReadOnlyView is true and "name" property exists', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(StepRuleDescription).exists()).toBeTruthy(); + }); + + test('it prevents user from clicking continue if no "description" defined', () => { + const wrapper = mount( + + + + ); + + const nameInput = wrapper + .find('input[aria-describedby="detectionEngineStepAboutRuleName"]') + .at(0); + nameInput.simulate('change', { target: { value: 'Test name text' } }); + + const descriptionInput = wrapper + .find('textarea[aria-describedby="detectionEngineStepAboutRuleDescription"]') + .at(0); + const nextButton = wrapper.find('button[data-test-subj="about-continue"]').at(0); + nextButton.simulate('click'); + + expect( + wrapper.find('input[aria-describedby="detectionEngineStepAboutRuleName"]').at(0).props().value + ).toEqual('Test name text'); + expect(descriptionInput.props().value).toEqual(''); + expect( + wrapper + .find('EuiFormRow[data-test-subj="detectionEngineStepAboutRuleDescription"] label') + .at(0) + .hasClass('euiFormLabel-isInvalid') + ).toBeTruthy(); + expect( + wrapper + .find('EuiFormRow[data-test-subj="detectionEngineStepAboutRuleDescription"] EuiTextArea') + .at(0) + .prop('isInvalid') + ).toBeTruthy(); + }); + + test('it prevents user from clicking continue if no "name" defined', () => { + const wrapper = mount( + + + + ); + + const descriptionInput = wrapper + .find('textarea[aria-describedby="detectionEngineStepAboutRuleDescription"]') + .at(0); + descriptionInput.simulate('change', { target: { value: 'Test description text' } }); + + const nameInput = wrapper + .find('input[aria-describedby="detectionEngineStepAboutRuleName"]') + .at(0); + const nextButton = wrapper.find('button[data-test-subj="about-continue"]').at(0); + nextButton.simulate('click'); + + expect( + wrapper + .find('textarea[aria-describedby="detectionEngineStepAboutRuleDescription"]') + .at(0) + .props().value + ).toEqual('Test description text'); + expect(nameInput.props().value).toEqual(''); + expect( + wrapper + .find('EuiFormRow[data-test-subj="detectionEngineStepAboutRuleName"] label') + .at(0) + .hasClass('euiFormLabel-isInvalid') + ).toBeTruthy(); + expect( + wrapper + .find('EuiFormRow[data-test-subj="detectionEngineStepAboutRuleName"] EuiFieldText') + .at(0) + .prop('isInvalid') + ).toBeTruthy(); + }); + + test('it allows user to click continue if "name" and "description" are defined', () => { + const wrapper = mount( + + + + ); + + const descriptionInput = wrapper + .find('textarea[aria-describedby="detectionEngineStepAboutRuleDescription"]') + .at(0); + descriptionInput.simulate('change', { target: { value: 'Test description text' } }); + + const nameInput = wrapper + .find('input[aria-describedby="detectionEngineStepAboutRuleName"]') + .at(0); + nameInput.simulate('change', { target: { value: 'Test name text' } }); + + const nextButton = wrapper.find('button[data-test-subj="about-continue"]').at(0); + nextButton.simulate('click'); + }); +}); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx new file mode 100644 index 0000000000000..42ffab10ff469 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { + FIELD_TYPES, + fieldValidators, + FormSchema, + ValidationFunc, + ERROR_CODE, +} from '../../../../shared_imports'; +import { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types'; +import { isMitreAttackInvalid } from '../mitre/helpers'; +import { OptionalFieldLabel } from '../optional_field_label'; +import { isUrlInvalid } from '../../../../common/utils/validators'; +import * as I18n from './translations'; + +const { emptyField } = fieldValidators; + +export const schema: FormSchema = { + name: { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNameLabel', + { + defaultMessage: 'Name', + } + ), + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.nameFieldRequiredError', + { + defaultMessage: 'A name is required.', + } + ) + ), + }, + ], + }, + description: { + type: FIELD_TYPES.TEXTAREA, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldDescriptionLabel', + { + defaultMessage: 'Description', + } + ), + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.descriptionFieldRequiredError', + { + defaultMessage: 'A description is required.', + } + ) + ), + }, + ], + }, + severity: { + type: FIELD_TYPES.SUPER_SELECT, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldSeverityLabel', + { + defaultMessage: 'Severity', + } + ), + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.severityFieldRequiredError', + { + defaultMessage: 'A severity is required.', + } + ) + ), + }, + ], + }, + riskScore: { + type: FIELD_TYPES.RANGE, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRiskScoreLabel', + { + defaultMessage: 'Risk score', + } + ), + }, + references: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel', + { + defaultMessage: 'Reference URLs', + } + ), + labelAppend: OptionalFieldLabel, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path }] = args; + let hasError = false; + (value as string[]).forEach((url) => { + if (isUrlInvalid(url)) { + hasError = true; + } + }); + return hasError + ? { + code: 'ERR_FIELD_FORMAT', + path, + message: I18n.URL_FORMAT_INVALID, + } + : undefined; + }, + }, + ], + }, + falsePositives: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldFalsePositiveLabel', + { + defaultMessage: 'False positive examples', + } + ), + labelAppend: OptionalFieldLabel, + }, + threat: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel', + { + defaultMessage: 'MITRE ATT&CK\\u2122', + } + ), + labelAppend: OptionalFieldLabel, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path }] = args; + let hasError = false; + (value as IMitreEnterpriseAttack[]).forEach((v) => { + if (isMitreAttackInvalid(v.tactic.name, v.technique)) { + hasError = true; + } + }); + return hasError + ? { + code: 'ERR_FIELD_MISSING', + path, + message: I18n.CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED, + } + : undefined; + }, + }, + ], + }, + tags: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTagsLabel', + { + defaultMessage: 'Tags', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTagsHelpText', + { + defaultMessage: + 'Type one or more custom identifying tags for this rule. Press enter after each tag to begin a new one.', + } + ), + labelAppend: OptionalFieldLabel, + }, + note: { + type: FIELD_TYPES.TEXTAREA, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.guideLabel', + { + defaultMessage: 'Investigation guide', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.guideHelpText', + { + defaultMessage: + 'Provide helpful information for analysts that are performing a signal investigation. This guide will appear on both the rule details page and in timelines created from alerts generated by this rule.', + } + ), + labelAppend: OptionalFieldLabel, + }, +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/translations.ts new file mode 100644 index 0000000000000..c179128c56d92 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/translations.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ADVANCED_SETTINGS = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.advancedSettingsButton', + { + defaultMessage: 'Advanced settings', + } +); + +export const ADD_REFERENCE = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.addReferenceDescription', + { + defaultMessage: 'Add reference URL', + } +); + +export const ADD_FALSE_POSITIVE = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.addFalsePositiveDescription', + { + defaultMessage: 'Add false positive example', + } +); + +export const LOW = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionLowDescription', + { + defaultMessage: 'Low', + } +); + +export const MEDIUM = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionMediumDescription', + { + defaultMessage: 'Medium', + } +); + +export const HIGH = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionHighDescription', + { + defaultMessage: 'High', + } +); + +export const CRITICAL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionCriticalDescription', + { + defaultMessage: 'Critical', + } +); + +export const CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customMitreAttackTechniquesFieldRequiredError', + { + defaultMessage: 'At least one Technique is required with a Tactic.', + } +); + +export const URL_FORMAT_INVALID = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError', + { + defaultMessage: 'Url is invalid format', + } +); + +export const ADD_RULE_NOTE_HELP_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutrule.noteHelpText', + { + defaultMessage: 'Add rule investigation guide...', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/index.test.tsx new file mode 100644 index 0000000000000..8e398e6236510 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/index.test.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import { EuiProgress, EuiButtonGroup } from '@elastic/eui'; +import { ThemeProvider } from 'styled-components'; +import euiDarkVars from '@elastic/eui/dist/eui_theme_light.json'; + +import { StepAboutRuleToggleDetails } from '.'; +import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { HeaderSection } from '../../../../common/components/header_section'; +import { StepAboutRule } from '../step_about_rule'; +import { AboutStepRule } from '../../../pages/detection_engine/rules/types'; + +jest.mock('../../../../common/lib/kibana'); + +const theme = () => ({ eui: euiDarkVars, darkMode: true }); + +describe('StepAboutRuleToggleDetails', () => { + let mockRule: AboutStepRule; + + beforeEach(() => { + mockRule = mockAboutStepRule(); + }); + + test('it renders loading component when "loading" is true', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(EuiProgress).exists()).toBeTruthy(); + expect(wrapper.find(HeaderSection).exists()).toBeTruthy(); + }); + + test('it does not render details if stepDataDetails is null', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(StepAboutRule).exists()).toBeFalsy(); + }); + + test('it does not render details if stepData is null', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(StepAboutRule).exists()).toBeFalsy(); + }); + + describe('note value is empty string', () => { + test('it does not render toggle buttons', () => { + const mockAboutStepWithoutNote = { + ...mockRule, + note: '', + }; + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="stepAboutDetailsToggle"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="stepAboutDetailsNoteContent"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="stepAboutDetailsContent"]').exists()).toBeTruthy(); + }); + }); + + describe('note value does exist', () => { + test('it renders toggle buttons, defaulted to "details"', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(EuiButtonGroup).exists()).toBeTruthy(); + expect(wrapper.find('EuiButtonToggle[id="details"]').at(0).prop('isSelected')).toBeTruthy(); + expect(wrapper.find('EuiButtonToggle[id="notes"]').at(0).prop('isSelected')).toBeFalsy(); + }); + + test('it allows users to toggle between "details" and "note"', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('EuiButtonGroup[idSelected="details"]').exists()).toBeTruthy(); + expect(wrapper.find('EuiButtonGroup[idSelected="notes"]').exists()).toBeFalsy(); + + wrapper + .find('input[title="Investigation guide"]') + .at(0) + .simulate('change', { target: { value: 'notes' } }); + + expect(wrapper.find('EuiButtonGroup[idSelected="details"]').exists()).toBeFalsy(); + expect(wrapper.find('EuiButtonGroup[idSelected="notes"]').exists()).toBeTruthy(); + }); + + test('it displays notes markdown when user toggles to "notes"', () => { + const wrapper = mount( + + + + ); + + wrapper + .find('input[title="Investigation guide"]') + .at(0) + .simulate('change', { target: { value: 'notes' } }); + + expect(wrapper.find('EuiButtonGroup[idSelected="notes"]').exists()).toBeTruthy(); + expect(wrapper.find('Markdown h1').text()).toEqual('this is some markdown documentation'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/index.tsx new file mode 100644 index 0000000000000..8604a5293a710 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/index.tsx @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiPanel, + EuiProgress, + EuiButtonGroup, + EuiButtonGroupOption, + EuiSpacer, + EuiFlexItem, + EuiText, + EuiFlexGroup, + EuiResizeObserver, +} from '@elastic/eui'; +import React, { memo, useCallback, useState } from 'react'; +import styled from 'styled-components'; +import { isEmpty } from 'lodash/fp'; + +import { HeaderSection } from '../../../../common/components/header_section'; +import { Markdown } from '../../../../common/components/markdown'; +import { AboutStepRule, AboutStepRuleDetails } from '../../../pages/detection_engine/rules/types'; +import * as i18n from './translations'; +import { StepAboutRule } from '../step_about_rule'; + +const MyPanel = styled(EuiPanel)` + position: relative; +`; + +const FlexGroupFullHeight = styled(EuiFlexGroup)` + height: 100%; +`; + +const VerticalOverflowContainer = styled.div((props: { maxHeight: number }) => ({ + 'max-height': `${props.maxHeight}px`, + 'overflow-y': 'hidden', +})); + +const VerticalOverflowContent = styled.div((props: { maxHeight: number }) => ({ + 'max-height': `${props.maxHeight}px`, +})); + +const AboutContent = styled.div` + height: 100%; +`; + +const toggleOptions: EuiButtonGroupOption[] = [ + { + id: 'details', + label: i18n.ABOUT_PANEL_DETAILS_TAB, + }, + { + id: 'notes', + label: i18n.ABOUT_PANEL_NOTES_TAB, + }, +]; + +interface StepPanelProps { + stepData: AboutStepRule | null; + stepDataDetails: AboutStepRuleDetails | null; + loading: boolean; +} + +const StepAboutRuleToggleDetailsComponent: React.FC = ({ + stepData, + stepDataDetails, + loading, +}) => { + const [selectedToggleOption, setToggleOption] = useState('details'); + const [aboutPanelHeight, setAboutPanelHeight] = useState(0); + + const onResize = useCallback( + (e: { height: number; width: number }) => { + setAboutPanelHeight(e.height); + }, + [setAboutPanelHeight] + ); + + return ( + + {loading && ( + <> + + + + )} + {stepData != null && stepDataDetails != null && ( + + + + {!isEmpty(stepDataDetails.note) && stepDataDetails.note.trim() !== '' && ( + { + setToggleOption(val); + }} + data-test-subj="stepAboutDetailsToggle" + /> + )} + + + + {selectedToggleOption === 'details' ? ( + + {(resizeRef) => ( + + + + + {stepDataDetails.description} + + + + + + + )} + + ) : ( + + + + + + )} + + + )} + + ); +}; + +export const StepAboutRuleToggleDetails = memo(StepAboutRuleToggleDetailsComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/translations.ts new file mode 100644 index 0000000000000..e1b89b1ec8ce2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule_details/translations.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; + +export const ABOUT_PANEL_DETAILS_TAB = i18n.translate( + 'xpack.securitySolution.detectionEngine.details.stepAboutRule.detailsLabel', + { + defaultMessage: 'Details', + } +); + +export const ABOUT_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.details.stepAboutRule.aboutText', + { + defaultMessage: 'About', + } +); + +export const ABOUT_PANEL_NOTES_TAB = i18n.translate( + 'xpack.securitySolution.detectionEngine.details.stepAboutRule.investigationGuideLabel', + { + defaultMessage: 'Investigation guide', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_content_wrapper/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_content_wrapper/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_content_wrapper/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_content_wrapper/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_content_wrapper/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_content_wrapper/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_content_wrapper/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_content_wrapper/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/index.tsx new file mode 100644 index 0000000000000..fc875908bd4ef --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/index.tsx @@ -0,0 +1,286 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; +import React, { FC, memo, useCallback, useState, useEffect } from 'react'; +import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { isMlRule } from '../../../../../common/machine_learning/helpers'; +import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; +import { useFetchIndexPatterns } from '../../../../alerts/containers/detection_engine/rules'; +import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; +import { useMlCapabilities } from '../../../../common/components/ml_popover/hooks/use_ml_capabilities'; +import { useUiSetting$ } from '../../../../common/lib/kibana'; +import { setFieldValue } from '../../../pages/detection_engine/rules/helpers'; +import { + filterRuleFieldsForType, + RuleFields, +} from '../../../pages/detection_engine/rules/create/helpers'; +import { + DefineStepRule, + RuleStep, + RuleStepProps, +} from '../../../pages/detection_engine/rules/types'; +import { StepRuleDescription } from '../description_step'; +import { QueryBarDefineRule } from '../query_bar'; +import { SelectRuleType } from '../select_rule_type'; +import { AnomalyThresholdSlider } from '../anomaly_threshold_slider'; +import { MlJobSelect } from '../ml_job_select'; +import { PickTimeline } from '../pick_timeline'; +import { StepContentWrapper } from '../step_content_wrapper'; +import { NextStep } from '../next_step'; +import { + Field, + Form, + FormDataProvider, + getUseField, + UseField, + useForm, + FormSchema, +} from '../../../../shared_imports'; +import { schema } from './schema'; +import * as i18n from './translations'; + +const CommonUseField = getUseField({ component: Field }); + +interface StepDefineRuleProps extends RuleStepProps { + defaultValues?: DefineStepRule | null; +} + +const stepDefineDefaultValue: DefineStepRule = { + anomalyThreshold: 50, + index: [], + isNew: true, + machineLearningJobId: '', + ruleType: 'query', + queryBar: { + query: { query: '', language: 'kuery' }, + filters: [], + saved_id: undefined, + }, + timeline: { + id: null, + title: DEFAULT_TIMELINE_TITLE, + }, +}; + +const MyLabelButton = styled(EuiButtonEmpty)` + height: 18px; + font-size: 12px; + + .euiIcon { + width: 14px; + height: 14px; + } +`; + +MyLabelButton.defaultProps = { + flush: 'right', +}; + +const StepDefineRuleComponent: FC = ({ + addPadding = false, + defaultValues, + descriptionColumns = 'singleSplit', + isReadOnlyView, + isLoading, + isUpdateView = false, + setForm, + setStepData, +}) => { + const mlCapabilities = useMlCapabilities(); + const [openTimelineSearch, setOpenTimelineSearch] = useState(false); + const [indexModified, setIndexModified] = useState(false); + const [localIsMlRule, setIsMlRule] = useState(false); + const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); + const [myStepData, setMyStepData] = useState({ + ...stepDefineDefaultValue, + index: indicesConfig ?? [], + }); + const [ + { browserFields, indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, + ] = useFetchIndexPatterns(myStepData.index); + + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); + const clearErrors = useCallback(() => form.reset({ resetValues: false }), [form]); + + const onSubmit = useCallback(async () => { + if (setStepData) { + setStepData(RuleStep.defineRule, null, false); + const { isValid, data } = await form.submit(); + if (isValid && setStepData) { + setStepData(RuleStep.defineRule, data, isValid); + setMyStepData({ ...data, isNew: false } as DefineStepRule); + } + } + }, [form]); + + useEffect(() => { + const { isNew, ...values } = myStepData; + if (defaultValues != null && !deepEqual(values, defaultValues)) { + const newValues = { ...values, ...defaultValues, isNew: false }; + setMyStepData(newValues); + setFieldValue(form, schema, newValues); + } + }, [defaultValues, setMyStepData, setFieldValue]); + + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.defineRule, form); + } + }, [form]); + + const handleResetIndices = useCallback(() => { + const indexField = form.getFields().index; + indexField.setValue(indicesConfig); + }, [form, indicesConfig]); + + const handleOpenTimelineSearch = useCallback(() => { + setOpenTimelineSearch(true); + }, []); + + const handleCloseTimelineSearch = useCallback(() => { + setOpenTimelineSearch(false); + }, []); + + return isReadOnlyView ? ( + + + + ) : ( + <> + + + + + <> + + {i18n.RESET_DEFAULT_INDEX} + + ) : null, + }} + componentProps={{ + idAria: 'detectionEngineStepDefineRuleIndices', + 'data-test-subj': 'detectionEngineStepDefineRuleIndices', + euiFieldProps: { + fullWidth: true, + isDisabled: isLoading, + placeholder: '', + }, + }} + /> + + {i18n.IMPORT_TIMELINE_QUERY} + + ), + }} + component={QueryBarDefineRule} + componentProps={{ + browserFields, + idAria: 'detectionEngineStepDefineRuleQueryBar', + indexPattern: indexPatternQueryBar, + isDisabled: isLoading, + isLoading: indexPatternLoadingQueryBar, + dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + openTimelineSearch, + onCloseTimelineSearch: handleCloseTimelineSearch, + }} + /> + + + + <> + + + + + + + {({ index, ruleType }) => { + if (index != null) { + if (deepEqual(index, indicesConfig) && indexModified) { + setIndexModified(false); + } else if (!deepEqual(index, indicesConfig) && !indexModified) { + setIndexModified(true); + } + } + + if (isMlRule(ruleType) && !localIsMlRule) { + setIsMlRule(true); + clearErrors(); + } else if (!isMlRule(ruleType) && localIsMlRule) { + setIsMlRule(false); + clearErrors(); + } + + return null; + }} + + + + + {!isUpdateView && ( + + )} + + ); +}; + +export const StepDefineRule = memo(StepDefineRuleComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/schema.tsx new file mode 100644 index 0000000000000..190d4484b156b --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/schema.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { EuiText } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React from 'react'; + +import { isMlRule } from '../../../../../common/machine_learning/helpers'; +import { esKuery } from '../../../../../../../../src/plugins/data/public'; +import { FieldValueQueryBar } from '../query_bar'; +import { + ERROR_CODE, + FIELD_TYPES, + fieldValidators, + FormSchema, + ValidationFunc, +} from '../../../../shared_imports'; +import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY, INDEX_HELPER_TEXT } from './translations'; + +export const schema: FormSchema = { + index: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fiedIndexPatternsLabel', + { + defaultMessage: 'Index patterns', + } + ), + helpText: {INDEX_HELPER_TEXT}, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ formData }] = args; + const needsValidation = !isMlRule(formData.ruleType); + + if (!needsValidation) { + return; + } + + return fieldValidators.emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.outputIndiceNameFieldRequiredError', + { + defaultMessage: 'A minimum of one index pattern is required.', + } + ) + )(...args); + }, + }, + ], + }, + queryBar: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldQuerBarLabel', + { + defaultMessage: 'Custom query', + } + ), + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path, formData }] = args; + const { query, filters } = value as FieldValueQueryBar; + const needsValidation = !isMlRule(formData.ruleType); + if (!needsValidation) { + return; + } + + return isEmpty(query.query as string) && isEmpty(filters) + ? { + code: 'ERR_FIELD_MISSING', + path, + message: CUSTOM_QUERY_REQUIRED, + } + : undefined; + }, + }, + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path, formData }] = args; + const { query } = value as FieldValueQueryBar; + const needsValidation = !isMlRule(formData.ruleType); + if (!needsValidation) { + return; + } + + if (!isEmpty(query.query as string) && query.language === 'kuery') { + try { + esKuery.fromKueryExpression(query.query); + } catch (err) { + return { + code: 'ERR_FIELD_FORMAT', + path, + message: INVALID_CUSTOM_QUERY, + }; + } + } + }, + }, + ], + }, + ruleType: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldRuleTypeLabel', + { + defaultMessage: 'Rule type', + } + ), + validations: [], + }, + anomalyThreshold: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldAnomalyThresholdLabel', + { + defaultMessage: 'Anomaly score threshold', + } + ), + validations: [], + }, + machineLearningJobId: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldMachineLearningJobIdLabel', + { + defaultMessage: 'Machine Learning job', + } + ), + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ formData }] = args; + const needsValidation = isMlRule(formData.ruleType); + + if (!needsValidation) { + return; + } + + return fieldValidators.emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.machineLearningJobIdRequired', + { + defaultMessage: 'A Machine Learning job is required.', + } + ) + )(...args); + }, + }, + ], + }, + timeline: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateLabel', + { + defaultMessage: 'Timeline template', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateHelpText', + { + defaultMessage: 'Select which timeline to use when investigating generated alerts.', + } + ), + }, +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/translations.tsx new file mode 100644 index 0000000000000..a371db72d2ee1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/translations.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const CUSTOM_QUERY_REQUIRED = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError', + { + defaultMessage: 'A custom query is required.', + } +); + +export const INVALID_CUSTOM_QUERY = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError', + { + defaultMessage: 'The KQL is invalid', + } +); + +export const CONFIG_INDICES = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesFromConfigDescription', + { + defaultMessage: 'Use Elasticsearch indices from SIEM advanced settings', + } +); + +export const CUSTOM_INDICES = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesCustomDescription', + { + defaultMessage: 'Provide custom list of indices', + } +); + +export const INDEX_HELPER_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesHelperDescription', + { + defaultMessage: + 'Enter the pattern of Elasticsearch indices where you would like this rule to run. By default, these will include index patterns defined in SIEM advanced settings.', + } +); + +export const RESET_DEFAULT_INDEX = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton', + { + defaultMessage: 'Reset to default index patterns', + } +); + +export const IMPORT_TIMELINE_QUERY = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.importTimelineQueryButton', + { + defaultMessage: 'Import query from saved timeline', + } +); + +export const ML_JOB_SELECT_PLACEHOLDER_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.mlJobSelectPlaceholderText', + { + defaultMessage: 'Select a job', + } +); + +export const ENABLE_ML_JOB_WARNING = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.mlEnableJobWarningTitle', + { + defaultMessage: + 'This ML job is not currently running. Please set this job to run via "ML job settings" before activating this rule.', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/types.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/types.ts rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_define_rule/types.ts diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_panel/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_panel/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_panel/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_panel/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_panel/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_panel/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_panel/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_panel/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx new file mode 100644 index 0000000000000..778c6bd92bc73 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiHorizontalRule, + EuiForm, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiSpacer, +} from '@elastic/eui'; +import { findIndex } from 'lodash/fp'; +import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; + +import { setFieldValue } from '../../../pages/detection_engine/rules/helpers'; +import { + RuleStep, + RuleStepProps, + ActionsStepRule, +} from '../../../pages/detection_engine/rules/types'; +import { StepRuleDescription } from '../description_step'; +import { Form, UseField, useForm } from '../../../../shared_imports'; +import { StepContentWrapper } from '../step_content_wrapper'; +import { + ThrottleSelectField, + THROTTLE_OPTIONS, + DEFAULT_THROTTLE_OPTION, +} from '../throttle_select_field'; +import { RuleActionsField } from '../rule_actions_field'; +import { useKibana } from '../../../../common/lib/kibana'; +import { getSchema } from './schema'; +import * as I18n from './translations'; + +interface StepRuleActionsProps extends RuleStepProps { + defaultValues?: ActionsStepRule | null; + actionMessageParams: string[]; +} + +const stepActionsDefaultValue = { + enabled: true, + isNew: true, + actions: [], + kibanaSiemAppUrl: '', + throttle: DEFAULT_THROTTLE_OPTION.value, +}; + +const GhostFormField = () => <>; + +const getThrottleOptions = (throttle?: string | null) => { + // Add support for throttle options set by the API + if (throttle && findIndex(['value', throttle], THROTTLE_OPTIONS) < 0) { + return [...THROTTLE_OPTIONS, { value: throttle, text: throttle }]; + } + + return THROTTLE_OPTIONS; +}; + +const StepRuleActionsComponent: FC = ({ + addPadding = false, + defaultValues, + isReadOnlyView, + isLoading, + isUpdateView = false, + setStepData, + setForm, + actionMessageParams, +}) => { + const [myStepData, setMyStepData] = useState(stepActionsDefaultValue); + const { + services: { + application, + triggers_actions_ui: { actionTypeRegistry }, + }, + } = useKibana(); + const schema = useMemo(() => getSchema({ actionTypeRegistry }), [actionTypeRegistry]); + + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); + + const kibanaAbsoluteUrl = useMemo(() => application.getUrlForApp('siem', { absolute: true }), [ + application, + ]); + + const onSubmit = useCallback( + async (enabled: boolean) => { + if (setStepData) { + setStepData(RuleStep.ruleActions, null, false); + const { isValid: newIsValid, data } = await form.submit(); + if (newIsValid) { + setStepData(RuleStep.ruleActions, { ...data, enabled }, newIsValid); + setMyStepData({ ...data, isNew: false } as ActionsStepRule); + } + } + }, + [form] + ); + + useEffect(() => { + const { isNew, ...initDefaultValue } = myStepData; + if (defaultValues != null && !deepEqual(initDefaultValue, defaultValues)) { + const myDefaultValues = { + ...defaultValues, + isNew: false, + }; + setMyStepData(myDefaultValues); + setFieldValue(form, schema, myDefaultValues); + } + }, [defaultValues]); + + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.ruleActions, form); + } + }, [form]); + + const updateThrottle = useCallback((throttle) => setMyStepData({ ...myStepData, throttle }), [ + myStepData, + setMyStepData, + ]); + + const throttleOptions = useMemo(() => { + const throttle = myStepData.throttle; + + return getThrottleOptions(throttle); + }, [myStepData]); + + const throttleFieldComponentProps = useMemo( + () => ({ + idAria: 'detectionEngineStepRuleActionsThrottle', + isDisabled: isLoading, + dataTestSubj: 'detectionEngineStepRuleActionsThrottle', + hasNoInitialSelection: false, + handleChange: updateThrottle, + euiFieldProps: { + options: throttleOptions, + }, + }), + [isLoading, updateThrottle] + ); + + return isReadOnlyView && myStepData != null ? ( + + + + ) : ( + <> + +
    + + + {myStepData.throttle !== stepActionsDefaultValue.throttle ? ( + <> + + + + ) : ( + + )} + + + +
    +
    + + {!isUpdateView && ( + <> + + + + + {I18n.COMPLETE_WITHOUT_ACTIVATING} + + + + + {I18n.COMPLETE_WITH_ACTIVATING} + + + + + )} + + ); +}; + +export const StepRuleActions = memo(StepRuleActionsComponent); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/schema.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/schema.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/schema.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/schema.tsx new file mode 100644 index 0000000000000..a093f991afaf7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/schema.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* istanbul ignore file */ + +import { i18n } from '@kbn/i18n'; + +import { + AlertAction, + ActionTypeRegistryContract, +} from '../../../../../../triggers_actions_ui/public'; +import { FormSchema, FormData, ValidationFunc, ERROR_CODE } from '../../../../shared_imports'; +import * as I18n from './translations'; +import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; + +export const validateSingleAction = ( + actionItem: AlertAction, + actionTypeRegistry: ActionTypeRegistryContract +): string[] => { + if (!isUuidv4(actionItem.id)) { + return [I18n.NO_CONNECTOR_SELECTED]; + } + + const actionParamsErrors = validateActionParams(actionItem, actionTypeRegistry); + const mustacheErrors = validateMustache(actionItem.params); + + return [...actionParamsErrors, ...mustacheErrors]; +}; + +export const validateRuleActionsField = (actionTypeRegistry: ActionTypeRegistryContract) => ( + ...data: Parameters +): ReturnType> | undefined => { + const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; + + const errors = value.reduce((acc, actionItem) => { + const errorsArray = validateSingleAction(actionItem, actionTypeRegistry); + + if (errorsArray.length) { + const actionTypeName = getActionTypeName(actionItem.actionTypeId); + const errorsListItems = errorsArray.map((error) => `* ${error}\n`); + + return [...acc, `\n**${actionTypeName}:**\n${errorsListItems.join('')}`]; + } + + return acc; + }, [] as string[]); + + if (errors.length) { + return { + code: 'ERR_FIELD_FORMAT', + path, + message: `${errors.join('\n')}`, + }; + } +}; + +export const getSchema = ({ + actionTypeRegistry, +}: { + actionTypeRegistry: ActionTypeRegistryContract; +}): FormSchema => ({ + actions: { + validations: [ + { + validator: validateRuleActionsField(actionTypeRegistry), + }, + ], + }, + enabled: {}, + kibanaSiemAppUrl: {}, + throttle: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleLabel', + { + defaultMessage: 'Actions frequency', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpText', + { + defaultMessage: + 'Select when automated actions should be performed if a rule evaluates as true.', + } + ), + }, +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/translations.tsx new file mode 100644 index 0000000000000..a276cb17e95f5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/translations.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { startCase } from 'lodash/fp'; + +export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithoutActivatingTitle', + { + defaultMessage: 'Create rule without activating it', + } +); + +export const COMPLETE_WITH_ACTIVATING = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithActivatingTitle', + { + defaultMessage: 'Create & activate rule', + } +); + +export const NO_CONNECTOR_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.noConnectorSelectedErrorMessage', + { + defaultMessage: 'No connector selected', + } +); + +export const INVALID_MUSTACHE_TEMPLATE = (paramKey: string) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.invalidMustacheTemplateErrorMessage', + { + defaultMessage: '{key} is not valid mustache template', + values: { + key: startCase(paramKey), + }, + } + ); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/utils.test.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/utils.test.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/utils.test.ts rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/utils.test.ts diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/utils.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/utils.ts new file mode 100644 index 0000000000000..2d58f3b8f26a6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/utils.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import mustache from 'mustache'; +import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; + +import { + AlertAction, + ActionTypeRegistryContract, +} from '../../../../../../triggers_actions_ui/public'; +import * as I18n from './translations'; + +const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; + +export const isUuidv4 = (id: AlertAction['id']) => !!id.match(UUID_V4_REGEX); + +export const getActionTypeName = (actionTypeId: AlertAction['actionTypeId']) => { + if (!actionTypeId) return ''; + const actionType = actionTypeId.split('.')[1]; + + if (!actionType) return ''; + + return startCase(actionType); +}; + +export const validateMustache = (params: AlertAction['params']) => { + const errors: string[] = []; + Object.entries(params).forEach(([paramKey, paramValue]) => { + if (!isString(paramValue)) return; + try { + mustache.render(paramValue, {}); + } catch (e) { + errors.push(I18n.INVALID_MUSTACHE_TEMPLATE(paramKey)); + } + }); + + return errors; +}; + +export const validateActionParams = ( + actionItem: AlertAction, + actionTypeRegistry: ActionTypeRegistryContract +): string[] => { + const actionErrors = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); + + if (actionErrors) { + const actionErrorsValues = Object.values(actionErrors.errors); + + if (actionErrorsValues.length) { + const filteredObjects: Array = actionErrorsValues.filter( + (item) => isString(item) || isArray(item) + ) as Array; + const uniqActionErrors = uniq(flattenDeep(filteredObjects)); + + if (uniqActionErrors.length) { + return uniqActionErrors; + } + } + } + + return []; +}; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/index.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/schema.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/schema.tsx new file mode 100644 index 0000000000000..f4c371a2364f6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/schema.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* istanbul ignore file */ + +import { i18n } from '@kbn/i18n'; + +import { OptionalFieldLabel } from '../optional_field_label'; +import { FormSchema } from '../../../../shared_imports'; + +export const schema: FormSchema = { + interval: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.fieldIntervalLabel', + { + defaultMessage: 'Runs every', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText', + { + defaultMessage: 'Rules run periodically and detect alerts within the specified time frame.', + } + ), + }, + from: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackLabel', + { + defaultMessage: 'Additional look-back time', + } + ), + labelAppend: OptionalFieldLabel, + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', + { + defaultMessage: 'Adds time to the look-back period to prevent missed alerts.', + } + ), + }, +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/translations.tsx new file mode 100644 index 0000000000000..d49e7396f4c87 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_schedule_rule/translations.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle', + { + defaultMessage: 'Create rule without activating it', + } +); + +export const COMPLETE_WITH_ACTIVATING = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle', + { + defaultMessage: 'Create & activate rule', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/throttle_select_field/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/throttle_select_field/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/rules/throttle_select_field/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/rules/throttle_select_field/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/throttle_select_field/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/throttle_select_field/index.tsx new file mode 100644 index 0000000000000..133f25ef3a03f --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/throttle_select_field/index.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; + +import { + NOTIFICATION_THROTTLE_RULE, + NOTIFICATION_THROTTLE_NO_ACTIONS, +} from '../../../../../common/constants'; +import { SelectField } from '../../../../shared_imports'; + +export const THROTTLE_OPTIONS = [ + { value: NOTIFICATION_THROTTLE_NO_ACTIONS, text: 'Perform no actions' }, + { value: NOTIFICATION_THROTTLE_RULE, text: 'On each rule execution' }, + { value: '1h', text: 'Hourly' }, + { value: '1d', text: 'Daily' }, + { value: '7d', text: 'Weekly' }, +]; + +export const DEFAULT_THROTTLE_OPTION = THROTTLE_OPTIONS[0]; + +type ThrottleSelectField = typeof SelectField; + +export const ThrottleSelectField: ThrottleSelectField = (props) => { + const onChange = useCallback( + (e) => { + const throttle = e.target.value; + props.field.setValue(throttle); + props.handleChange(throttle); + }, + [props.field.setValue, props.handleChange] + ); + const newEuiFieldProps = { ...props.euiFieldProps, onChange }; + return ; +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/user_info/index.test.tsx new file mode 100644 index 0000000000000..1d0f0e2e24f77 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/user_info/index.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useUserInfo } from './index'; + +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; +import { useKibana } from '../../../common/lib/kibana'; +jest.mock('../../containers/detection_engine/alerts/use_privilege_user'); +jest.mock('../../containers/detection_engine/alerts/use_signal_index'); +jest.mock('../../../common/lib/kibana'); + +describe('useUserInfo', () => { + beforeAll(() => { + (usePrivilegeUser as jest.Mock).mockReturnValue({}); + (useSignalIndex as jest.Mock).mockReturnValue({}); + (useKibana as jest.Mock).mockReturnValue({ + services: { + application: { + capabilities: { + securitySolution: { + crud: true, + }, + }, + }, + }, + }); + }); + it('returns default state', () => { + const { result } = renderHook(() => useUserInfo()); + + expect(result).toEqual({ + current: { + canUserCRUD: null, + hasEncryptionKey: null, + hasIndexManage: null, + hasIndexWrite: null, + isAuthenticated: null, + isSignalIndexExists: null, + loading: true, + signalIndexName: null, + }, + error: undefined, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/user_info/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/user_info/index.tsx new file mode 100644 index 0000000000000..8753064751f76 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/user_info/index.tsx @@ -0,0 +1,245 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { noop } from 'lodash/fp'; +import React, { useEffect, useReducer, Dispatch, createContext, useContext } from 'react'; + +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; +import { useKibana } from '../../../common/lib/kibana'; + +export interface State { + canUserCRUD: boolean | null; + hasIndexManage: boolean | null; + hasIndexWrite: boolean | null; + isSignalIndexExists: boolean | null; + isAuthenticated: boolean | null; + hasEncryptionKey: boolean | null; + loading: boolean; + signalIndexName: string | null; +} + +const initialState: State = { + canUserCRUD: null, + hasIndexManage: null, + hasIndexWrite: null, + isSignalIndexExists: null, + isAuthenticated: null, + hasEncryptionKey: null, + loading: true, + signalIndexName: null, +}; + +export type Action = + | { type: 'updateLoading'; loading: boolean } + | { + type: 'updateHasIndexManage'; + hasIndexManage: boolean | null; + } + | { + type: 'updateHasIndexWrite'; + hasIndexWrite: boolean | null; + } + | { + type: 'updateIsSignalIndexExists'; + isSignalIndexExists: boolean | null; + } + | { + type: 'updateIsAuthenticated'; + isAuthenticated: boolean | null; + } + | { + type: 'updateHasEncryptionKey'; + hasEncryptionKey: boolean | null; + } + | { + type: 'updateCanUserCRUD'; + canUserCRUD: boolean | null; + } + | { + type: 'updateSignalIndexName'; + signalIndexName: string | null; + }; + +export const userInfoReducer = (state: State, action: Action): State => { + switch (action.type) { + case 'updateLoading': { + return { + ...state, + loading: action.loading, + }; + } + case 'updateHasIndexManage': { + return { + ...state, + hasIndexManage: action.hasIndexManage, + }; + } + case 'updateHasIndexWrite': { + return { + ...state, + hasIndexWrite: action.hasIndexWrite, + }; + } + case 'updateIsSignalIndexExists': { + return { + ...state, + isSignalIndexExists: action.isSignalIndexExists, + }; + } + case 'updateIsAuthenticated': { + return { + ...state, + isAuthenticated: action.isAuthenticated, + }; + } + case 'updateHasEncryptionKey': { + return { + ...state, + hasEncryptionKey: action.hasEncryptionKey, + }; + } + case 'updateCanUserCRUD': { + return { + ...state, + canUserCRUD: action.canUserCRUD, + }; + } + case 'updateSignalIndexName': { + return { + ...state, + signalIndexName: action.signalIndexName, + }; + } + default: + return state; + } +}; + +const StateUserInfoContext = createContext<[State, Dispatch]>([initialState, () => noop]); + +const useUserData = () => useContext(StateUserInfoContext); + +interface ManageUserInfoProps { + children: React.ReactNode; +} + +export const ManageUserInfo = ({ children }: ManageUserInfoProps) => ( + + {children} + +); + +export const useUserInfo = (): State => { + const [ + { + canUserCRUD, + hasIndexManage, + hasIndexWrite, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + loading, + signalIndexName, + }, + dispatch, + ] = useUserData(); + const { + loading: privilegeLoading, + isAuthenticated: isApiAuthenticated, + hasEncryptionKey: isApiEncryptionKey, + hasIndexManage: hasApiIndexManage, + hasIndexWrite: hasApiIndexWrite, + } = usePrivilegeUser(); + const { + loading: indexNameLoading, + signalIndexExists: isApiSignalIndexExists, + signalIndexName: apiSignalIndexName, + createDeSignalIndex: createSignalIndex, + } = useSignalIndex(); + + const uiCapabilities = useKibana().services.application.capabilities; + const capabilitiesCanUserCRUD: boolean = + typeof uiCapabilities.securitySolution.crud === 'boolean' + ? uiCapabilities.securitySolution.crud + : false; + + useEffect(() => { + if (loading !== privilegeLoading || indexNameLoading) { + dispatch({ type: 'updateLoading', loading: privilegeLoading || indexNameLoading }); + } + }, [loading, privilegeLoading, indexNameLoading]); + + useEffect(() => { + if (!loading && hasIndexManage !== hasApiIndexManage && hasApiIndexManage != null) { + dispatch({ type: 'updateHasIndexManage', hasIndexManage: hasApiIndexManage }); + } + }, [loading, hasIndexManage, hasApiIndexManage]); + + useEffect(() => { + if (!loading && hasIndexWrite !== hasApiIndexWrite && hasApiIndexWrite != null) { + dispatch({ type: 'updateHasIndexWrite', hasIndexWrite: hasApiIndexWrite }); + } + }, [loading, hasIndexWrite, hasApiIndexWrite]); + + useEffect(() => { + if ( + !loading && + isSignalIndexExists !== isApiSignalIndexExists && + isApiSignalIndexExists != null + ) { + dispatch({ type: 'updateIsSignalIndexExists', isSignalIndexExists: isApiSignalIndexExists }); + } + }, [loading, isSignalIndexExists, isApiSignalIndexExists]); + + useEffect(() => { + if (!loading && isAuthenticated !== isApiAuthenticated && isApiAuthenticated != null) { + dispatch({ type: 'updateIsAuthenticated', isAuthenticated: isApiAuthenticated }); + } + }, [loading, isAuthenticated, isApiAuthenticated]); + + useEffect(() => { + if (!loading && hasEncryptionKey !== isApiEncryptionKey && isApiEncryptionKey != null) { + dispatch({ type: 'updateHasEncryptionKey', hasEncryptionKey: isApiEncryptionKey }); + } + }, [loading, hasEncryptionKey, isApiEncryptionKey]); + + useEffect(() => { + if (!loading && canUserCRUD !== capabilitiesCanUserCRUD && capabilitiesCanUserCRUD != null) { + dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD }); + } + }, [loading, canUserCRUD, capabilitiesCanUserCRUD]); + + useEffect(() => { + if (!loading && signalIndexName !== apiSignalIndexName && apiSignalIndexName != null) { + dispatch({ type: 'updateSignalIndexName', signalIndexName: apiSignalIndexName }); + } + }, [loading, signalIndexName, apiSignalIndexName]); + + useEffect(() => { + if ( + isAuthenticated && + hasEncryptionKey && + hasIndexManage && + isSignalIndexExists != null && + !isSignalIndexExists && + createSignalIndex != null + ) { + createSignalIndex(); + } + }, [createSignalIndex, isAuthenticated, hasEncryptionKey, isSignalIndexExists, hasIndexManage]); + + return { + loading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + hasIndexManage, + hasIndexWrite, + signalIndexName, + }; +}; diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts new file mode 100644 index 0000000000000..64a55a8ec6eba --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { QueryAlerts, AlertSearchResponse, BasicSignals, AlertsIndex, Privilege } from '../types'; +import { alertsMock, mockSignalIndex, mockUserPrivilege } from '../mock'; + +export const fetchQueryAlerts = async ({ + query, + signal, +}: QueryAlerts): Promise> => + Promise.resolve(alertsMock as AlertSearchResponse); + +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => + Promise.resolve(mockSignalIndex); + +export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => + Promise.resolve(mockUserPrivilege); + +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => + Promise.resolve(mockSignalIndex); diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/api.test.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/api.test.ts new file mode 100644 index 0000000000000..3cd819b55685c --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/api.test.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaServices } from '../../../../common/lib/kibana'; +import { + alertsMock, + mockAlertsQuery, + mockStatusAlertQuery, + mockSignalIndex, + mockUserPrivilege, +} from './mock'; +import { + fetchQueryAlerts, + updateAlertStatus, + getSignalIndex, + getUserPrivilege, + createSignalIndex, +} from './api'; + +const abortCtrl = new AbortController(); +const mockKibanaServices = KibanaServices.get as jest.Mock; +jest.mock('../../../../common/lib/kibana'); + +const fetchMock = jest.fn(); +mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); + +describe('Detections Alerts API', () => { + describe('fetchQueryAlerts', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(alertsMock); + }); + + test('check parameter url, body', async () => { + await fetchQueryAlerts({ query: mockAlertsQuery, signal: abortCtrl.signal }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/search', { + body: + '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', + method: 'POST', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const signalsResp = await fetchQueryAlerts({ + query: mockAlertsQuery, + signal: abortCtrl.signal, + }); + expect(signalsResp).toEqual(alertsMock); + }); + }); + + describe('updateAlertStatus', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue({}); + }); + + test('check parameter url, body when closing an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, + signal: abortCtrl.signal, + status: 'closed', + }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { + body: + '{"status":"closed","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + method: 'POST', + signal: abortCtrl.signal, + }); + }); + + test('check parameter url, body when opening an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, + signal: abortCtrl.signal, + status: 'open', + }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { + body: + '{"status":"open","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + method: 'POST', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const alertsResp = await updateAlertStatus({ + query: mockStatusAlertQuery, + signal: abortCtrl.signal, + status: 'open', + }); + expect(alertsResp).toEqual({}); + }); + }); + + describe('getSignalIndex', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(mockSignalIndex); + }); + + test('check parameter url', async () => { + await getSignalIndex({ signal: abortCtrl.signal }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/index', { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const alertsResp = await getSignalIndex({ + signal: abortCtrl.signal, + }); + expect(alertsResp).toEqual(mockSignalIndex); + }); + }); + + describe('getUserPrivilege', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(mockUserPrivilege); + }); + + test('check parameter url', async () => { + await getUserPrivilege({ signal: abortCtrl.signal }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/privileges', { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const alertsResp = await getUserPrivilege({ + signal: abortCtrl.signal, + }); + expect(alertsResp).toEqual(mockUserPrivilege); + }); + }); + + describe('createSignalIndex', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(mockSignalIndex); + }); + + test('check parameter url', async () => { + await createSignalIndex({ signal: abortCtrl.signal }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/index', { + method: 'POST', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const alertsResp = await createSignalIndex({ + signal: abortCtrl.signal, + }); + expect(alertsResp).toEqual(mockSignalIndex); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/api.ts new file mode 100644 index 0000000000000..ccf35c9671836 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/api.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UpdateDocumentByQueryResponse } from 'elasticsearch'; +import { + DETECTION_ENGINE_QUERY_SIGNALS_URL, + DETECTION_ENGINE_SIGNALS_STATUS_URL, + DETECTION_ENGINE_INDEX_URL, + DETECTION_ENGINE_PRIVILEGES_URL, +} from '../../../../../common/constants'; +import { KibanaServices } from '../../../../common/lib/kibana'; +import { + BasicSignals, + Privilege, + QueryAlerts, + AlertSearchResponse, + AlertsIndex, + UpdateAlertStatusProps, +} from './types'; + +/** + * Fetch Alerts by providing a query + * + * @param query String to match a dsl + * @param signal to cancel request + * + * @throws An error if response is not OK + */ +export const fetchQueryAlerts = async ({ + query, + signal, +}: QueryAlerts): Promise> => + KibanaServices.get().http.fetch>( + DETECTION_ENGINE_QUERY_SIGNALS_URL, + { + method: 'POST', + body: JSON.stringify(query), + signal, + } + ); + +/** + * Update alert status by query + * + * @param query of alerts to update + * @param status to update to('open' / 'closed') + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const updateAlertStatus = async ({ + query, + status, + signal, +}: UpdateAlertStatusProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { + method: 'POST', + body: JSON.stringify({ status, ...query }), + signal, + }); + +/** + * Fetch Signal Index + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { + method: 'GET', + signal, + }); + +/** + * Get User Privileges + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_PRIVILEGES_URL, { + method: 'GET', + signal, + }); + +/** + * Create Signal Index if needed it + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { + method: 'POST', + signal, + }); diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/mock.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/mock.ts new file mode 100644 index 0000000000000..cd2cc1fe390ba --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/mock.ts @@ -0,0 +1,1036 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertSearchResponse, AlertsIndex, Privilege } from './types'; + +export const alertsMock: AlertSearchResponse = { + took: 7, + timeout: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 10000, + relation: 'gte', + }, + hits: [ + { + _index: '.siem-signals-default-000001', + _id: '820e05ab0a10a2110d6f0ab2e1864402724a88680d5b49840ecc17dd069d7646', + _score: 0, + _source: { + '@timestamp': '2020-02-15T00:15:19.231Z', + event: { + kind: 'signal', + code: 4625, + created: '2020-02-15T00:09:19.454Z', + module: 'security', + type: 'authentication_failure', + outcome: 'failure', + provider: 'Microsoft-Windows-Security-Auditing', + action: 'logon-failed', + category: 'authentication', + }, + winlog: { + record_id: 4864460, + task: 'Logon', + logon: { + failure: { + reason: 'Unknown user name or bad password.', + status: 'This is either due to a bad username or authentication information', + sub_status: 'User logon with misspelled or bad user account', + }, + type: 'Network', + }, + channel: 'Security', + event_id: 4625, + process: { + pid: 548, + thread: { + id: 292, + }, + }, + api: 'wineventlog', + opcode: 'Info', + computer_name: 'siem-windows', + keywords: ['Audit Failure'], + activity_id: '{96816605-032c-0000-eaad-4c5f58e1d501}', + provider_guid: '{54849625-5478-4994-a5ba-3e3b0328c30d}', + event_data: { + Status: '0xc000006d', + LmPackageName: '-', + SubjectUserSid: 'S-1-0-0', + SubjectLogonId: '0x0', + TransmittedServices: '-', + SubjectDomainName: '-', + LogonProcessName: 'NtLmSsp ', + AuthenticationPackageName: 'NTLM', + KeyLength: '0', + SubjectUserName: '-', + TargetUserSid: 'S-1-0-0', + FailureReason: '%%2313', + SubStatus: '0xc0000064', + LogonType: '3', + TargetUserName: 'ADMIN', + }, + provider_name: 'Microsoft-Windows-Security-Auditing', + }, + process: { + pid: 0, + executable: '-', + name: '-', + }, + agent: { + type: 'winlogbeat', + ephemeral_id: 'cbee8ae0-2c75-4999-ba16-71d482247f52', + hostname: 'siem-windows', + id: '19b2de73-7b9a-4e92-b3e7-82383ac5f389', + version: '7.5.1', + }, + cloud: { + availability_zone: 'us-east1-b', + project: { + id: 'elastic-beats', + }, + provider: 'gcp', + instance: { + id: '3849238371046563697', + name: 'siem-windows', + }, + machine: { + type: 'g1-small', + }, + }, + log: { + level: 'information', + }, + message: + 'An account failed to log on.\n\nSubject:\n\tSecurity ID:\t\tS-1-0-0\n\tAccount Name:\t\t-\n\tAccount Domain:\t\t-\n\tLogon ID:\t\t0x0\n\nLogon Type:\t\t\t3\n\nAccount For Which Logon Failed:\n\tSecurity ID:\t\tS-1-0-0\n\tAccount Name:\t\tADMIN\n\tAccount Domain:\t\t\n\nFailure Information:\n\tFailure Reason:\t\tUnknown user name or bad password.\n\tStatus:\t\t\t0xC000006D\n\tSub Status:\t\t0xC0000064\n\nProcess Information:\n\tCaller Process ID:\t0x0\n\tCaller Process Name:\t-\n\nNetwork Information:\n\tWorkstation Name:\t-\n\tSource Network Address:\t185.209.0.96\n\tSource Port:\t\t0\n\nDetailed Authentication Information:\n\tLogon Process:\t\tNtLmSsp \n\tAuthentication Package:\tNTLM\n\tTransited Services:\t-\n\tPackage Name (NTLM only):\t-\n\tKey Length:\t\t0\n\nThis event is generated when a logon request fails. It is generated on the computer where access was attempted.\n\nThe Subject fields indicate the account on the local system which requested the logon. This is most commonly a service such as the Server service, or a local process such as Winlogon.exe or Services.exe.\n\nThe Logon Type field indicates the kind of logon that was requested. The most common types are 2 (interactive) and 3 (network).\n\nThe Process Information fields indicate which account and process on the system requested the logon.\n\nThe Network Information fields indicate where a remote logon request originated. Workstation name is not always available and may be left blank in some cases.\n\nThe authentication information fields provide detailed information about this specific logon request.\n\t- Transited services indicate which intermediate services have participated in this logon request.\n\t- Package name indicates which sub-protocol was used among the NTLM protocols.\n\t- Key length indicates the length of the generated session key. This will be 0 if no session key was requested.', + user: { + name: 'ADMIN', + id: 'S-1-0-0', + }, + source: { + ip: '185.209.0.96', + port: 0, + domain: '-', + }, + ecs: { + version: '1.1.0', + }, + host: { + name: 'siem-windows', + os: { + name: 'Windows Server 2019 Datacenter', + kernel: '10.0.17763.1039 (WinBuild.160101.0800)', + build: '17763.1039', + platform: 'windows', + version: '10.0', + family: 'windows', + }, + id: 'ae32054e-0d4a-4c4d-88ec-b840f992e1c2', + hostname: 'siem-windows', + architecture: 'x86_64', + }, + signal: { + parent: { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: 'AdctRnABMQha2n6boR1M', + type: 'event', + index: 'winlogbeat-7.5.1-2020.01.15-000001', + depth: 1, + }, + ancestors: [ + { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: 'AdctRnABMQha2n6boR1M', + type: 'event', + index: 'winlogbeat-7.5.1-2020.01.15-000001', + depth: 1, + }, + ], + original_time: '2020-02-15T00:09:18.714Z', + status: 'open', + rule: { + id: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + rule_id: '82b2b065-a2ee-49fc-9d6d-781a75c3d280', + false_positives: [], + meta: { + from: '1m', + }, + max_signals: 100, + risk_score: 79, + output_index: '.siem-signals-default', + description: 'matches most events', + from: 'now-360s', + immutable: false, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + interval: '5m', + language: 'kuery', + name: 'matches host.name exists', + query: 'host.name : *', + references: ['https://google.com'], + severity: 'high', + tags: [ + 'host.name exists', + 'for testing', + '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', + '__internal_immutable:false', + ], + type: 'query', + to: 'now', + enabled: true, + filters: [], + created_by: 'elastic', + updated_by: 'elastic', + threat: [ + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1110', + name: 'Brute Force', + id: 'T1110', + }, + { + reference: 'https://attack.mitre.org/techniques/T1098', + name: 'Account Manipulation', + id: 'T1098', + }, + { + reference: 'https://attack.mitre.org/techniques/T1081', + name: 'Credentials in Files', + id: 'T1081', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0006', + name: 'Credential Access', + id: 'TA0006', + }, + }, + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1530', + name: 'Data from Cloud Storage Object', + id: 'T1530', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0009', + name: 'Collection', + id: 'TA0009', + }, + }, + ], + version: 1, + created_at: '2020-02-12T19:49:29.417Z', + updated_at: '2020-02-14T23:15:06.186Z', + }, + original_event: { + kind: 'event', + code: 4625, + created: '2020-02-15T00:09:19.454Z', + module: 'security', + type: 'authentication_failure', + outcome: 'failure', + provider: 'Microsoft-Windows-Security-Auditing', + action: 'logon-failed', + category: 'authentication', + }, + }, + }, + }, + { + _index: '.siem-signals-default-000001', + _id: 'f461e2132bdf3926ef1fe10c83e671707ff3f12348ce600b8490c97a0c704086', + _score: 0, + _source: { + '@timestamp': '2020-02-15T00:15:19.231Z', + source: { + ip: '10.142.0.7', + port: 42774, + packets: 2, + bytes: 80, + }, + server: { + bytes: 10661, + ip: '169.254.169.254', + port: 80, + packets: 3, + }, + service: { + type: 'system', + }, + system: { + audit: { + socket: { + egid: 0, + kernel_sock_address: '0xffff8dd0103d2000', + uid: 0, + gid: 0, + euid: 0, + }, + }, + }, + destination: { + bytes: 10661, + ip: '169.254.169.254', + port: 80, + packets: 3, + }, + host: { + architecture: 'x86_64', + os: { + name: 'Debian GNU/Linux', + kernel: '4.9.0-8-amd64', + codename: 'stretch', + platform: 'debian', + version: '9 (stretch)', + family: 'debian', + }, + id: 'aa7ca589f1b8220002f2fc61c64cfbf1', + containerized: false, + hostname: 'siem-kibana', + name: 'siem-kibana', + }, + agent: { + type: 'auditbeat', + ephemeral_id: '60adc2c2-ab48-4e5c-b557-e73549400a79', + hostname: 'siem-kibana', + id: '03ccb0ce-f65c-4279-a619-05f1d5bb000b', + version: '7.5.0', + }, + client: { + ip: '10.142.0.7', + port: 42774, + packets: 2, + bytes: 80, + }, + cloud: { + machine: { + type: 'n1-standard-2', + }, + availability_zone: 'us-east1-b', + instance: { + name: 'siem-kibana', + id: '5412578377715150143', + }, + project: { + id: 'elastic-beats', + }, + provider: 'gcp', + }, + network: { + type: 'ipv4', + transport: 'tcp', + packets: 5, + bytes: 10741, + community_id: '1:qTY0+fxFYZvNHSUM4xTnCKjq8hM=', + direction: 'outbound', + }, + group: { + name: 'root', + id: '0', + }, + tags: ['7.5.0-bc2'], + ecs: { + version: '1.1.0', + }, + user: { + id: '0', + name: 'root', + }, + event: { + dataset: 'socket', + kind: 'signal', + action: 'network_flow', + category: 'network_traffic', + start: '2020-02-15T00:09:18.360Z', + end: '2020-02-15T00:09:18.361Z', + duration: 746181, + module: 'system', + }, + process: { + pid: 746, + name: 'google_accounts', + args: ['/usr/bin/python3', '/usr/bin/google_accounts_daemon'], + executable: '/usr/bin/python3.5', + created: '2020-02-14T18:31:08.280Z', + }, + flow: { + final: true, + complete: false, + }, + signal: { + parent: { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: '59ctRnABMQha2n6bmhzN', + type: 'event', + index: 'auditbeat-7.5.0-2020.01.14-000002', + depth: 1, + }, + ancestors: [ + { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: '59ctRnABMQha2n6bmhzN', + type: 'event', + index: 'auditbeat-7.5.0-2020.01.14-000002', + depth: 1, + }, + ], + original_time: '2020-02-15T00:09:18.795Z', + status: 'open', + rule: { + id: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + rule_id: '82b2b065-a2ee-49fc-9d6d-781a75c3d280', + false_positives: [], + meta: { + from: '1m', + }, + max_signals: 100, + risk_score: 79, + output_index: '.siem-signals-default', + description: 'matches most events', + from: 'now-360s', + immutable: false, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + interval: '5m', + language: 'kuery', + name: 'matches host.name exists', + query: 'host.name : *', + references: ['https://google.com'], + severity: 'high', + tags: [ + 'host.name exists', + 'for testing', + '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', + '__internal_immutable:false', + ], + type: 'query', + to: 'now', + enabled: true, + filters: [], + created_by: 'elastic', + updated_by: 'elastic', + threat: [ + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1110', + name: 'Brute Force', + id: 'T1110', + }, + { + reference: 'https://attack.mitre.org/techniques/T1098', + name: 'Account Manipulation', + id: 'T1098', + }, + { + reference: 'https://attack.mitre.org/techniques/T1081', + name: 'Credentials in Files', + id: 'T1081', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0006', + name: 'Credential Access', + id: 'TA0006', + }, + }, + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1530', + name: 'Data from Cloud Storage Object', + id: 'T1530', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0009', + name: 'Collection', + id: 'TA0009', + }, + }, + ], + version: 1, + created_at: '2020-02-12T19:49:29.417Z', + updated_at: '2020-02-14T23:15:06.186Z', + }, + original_event: { + dataset: 'socket', + kind: 'event', + action: 'network_flow', + category: 'network_traffic', + start: '2020-02-15T00:09:18.360Z', + end: '2020-02-15T00:09:18.361Z', + duration: 746181, + module: 'system', + }, + }, + }, + }, + { + _index: '.siem-signals-default-000001', + _id: '428551fed9382740e808f27ea64ce53b4d3b8cc82401d83afd47969339a0f6e3', + _score: 0, + _source: { + '@timestamp': '2020-02-15T00:15:19.231Z', + service: { + type: 'system', + }, + message: 'Process sleep (PID: 317535) by user root STARTED', + ecs: { + version: '1.0.0', + }, + host: { + name: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + hostname: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + architecture: 'x86_64', + os: { + platform: 'ubuntu', + version: '16.04.6 LTS (Xenial Xerus)', + family: 'debian', + name: 'Ubuntu', + kernel: '4.15.0-1052-gcp', + codename: 'xenial', + }, + id: 'c428794c81ade2eb0633d2bbea7ecf51', + containerized: false, + }, + cloud: { + machine: { + type: 'n1-highmem-4', + }, + availability_zone: 'us-central1-b', + project: { + id: 'elastic-ci-prod', + }, + provider: 'gcp', + instance: { + id: '5167639562480685129', + name: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + }, + }, + event: { + kind: 'signal', + action: 'process_started', + module: 'system', + dataset: 'process', + }, + process: { + executable: '/bin/sleep', + start: '2020-02-15T00:09:17.850Z', + args: ['sleep', '1'], + working_directory: '/', + name: 'sleep', + ppid: 239348, + pid: 317535, + hash: { + sha1: '9dc3644a028d1a4c853924c427f5e7d668c38ef7', + }, + entity_id: 'vtgDN10edfL0mX5p', + }, + user: { + id: '0', + group: { + id: '0', + name: 'root', + }, + effective: { + id: '0', + group: { + id: '0', + }, + }, + saved: { + id: '0', + group: { + id: '0', + }, + }, + name: 'root', + }, + agent: { + id: '4ae34f08-4770-4e5b-bd5b-c8b13741eafa', + version: '7.2.0', + type: 'auditbeat', + ephemeral_id: '3b3939af-dc90-4be8-b20b-a3d9f555d379', + hostname: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + }, + signal: { + parent: { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: '7tctRnABMQha2n6bnxxQ', + type: 'event', + index: 'auditbeat-7.2.0', + depth: 1, + }, + ancestors: [ + { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: '7tctRnABMQha2n6bnxxQ', + type: 'event', + index: 'auditbeat-7.2.0', + depth: 1, + }, + ], + original_time: '2020-02-15T00:09:18.860Z', + status: 'open', + rule: { + id: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + rule_id: '82b2b065-a2ee-49fc-9d6d-781a75c3d280', + false_positives: [], + meta: { + from: '1m', + }, + max_signals: 100, + risk_score: 79, + output_index: '.siem-signals-default', + description: 'matches most events', + from: 'now-360s', + immutable: false, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + interval: '5m', + language: 'kuery', + name: 'matches host.name exists', + query: 'host.name : *', + references: ['https://google.com'], + severity: 'high', + tags: [ + 'host.name exists', + 'for testing', + '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', + '__internal_immutable:false', + ], + type: 'query', + to: 'now', + enabled: true, + filters: [], + created_by: 'elastic', + updated_by: 'elastic', + threat: [ + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1110', + name: 'Brute Force', + id: 'T1110', + }, + { + reference: 'https://attack.mitre.org/techniques/T1098', + name: 'Account Manipulation', + id: 'T1098', + }, + { + reference: 'https://attack.mitre.org/techniques/T1081', + name: 'Credentials in Files', + id: 'T1081', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0006', + name: 'Credential Access', + id: 'TA0006', + }, + }, + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1530', + name: 'Data from Cloud Storage Object', + id: 'T1530', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0009', + name: 'Collection', + id: 'TA0009', + }, + }, + ], + version: 1, + created_at: '2020-02-12T19:49:29.417Z', + updated_at: '2020-02-14T23:15:06.186Z', + }, + original_event: { + kind: 'event', + action: 'process_started', + module: 'system', + dataset: 'process', + }, + }, + }, + }, + { + _index: '.siem-signals-default-000001', + _id: '9f6d771532d8f2b314c65b5007b1b9e2fcd206dca352b9b244c971341a09f5ce', + _score: 0, + _source: { + '@timestamp': '2020-02-15T00:15:19.231Z', + service: { + type: 'system', + }, + event: { + dataset: 'process', + kind: 'signal', + action: 'process_error', + module: 'system', + }, + message: + 'ERROR for PID 317759: failed to hash executable / for PID 317759: failed to calculate file hashes: read /: is a directory', + cloud: { + instance: { + id: '5167639562480685129', + name: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + }, + machine: { + type: 'n1-highmem-4', + }, + availability_zone: 'us-central1-b', + project: { + id: 'elastic-ci-prod', + }, + provider: 'gcp', + }, + host: { + architecture: 'x86_64', + os: { + platform: 'ubuntu', + version: '16.04.6 LTS (Xenial Xerus)', + family: 'debian', + name: 'Ubuntu', + kernel: '4.15.0-1052-gcp', + codename: 'xenial', + }, + name: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + id: 'c428794c81ade2eb0633d2bbea7ecf51', + containerized: false, + hostname: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + }, + agent: { + ephemeral_id: '3b3939af-dc90-4be8-b20b-a3d9f555d379', + hostname: 'beats-ci-immutable-ubuntu-1604-1581723302100990071', + id: '4ae34f08-4770-4e5b-bd5b-c8b13741eafa', + version: '7.2.0', + type: 'auditbeat', + }, + error: { + message: + 'failed to hash executable / for PID 317759: failed to calculate file hashes: read /: is a directory', + }, + process: { + entity_id: 'ahsj04Ppla09U8Q2', + name: 'runc:[2:INIT]', + args: ['runc', 'init'], + pid: 317759, + ppid: 317706, + working_directory: '/', + executable: '/', + start: '2020-02-15T00:09:18.360Z', + }, + user: { + name: 'root', + id: '0', + group: { + id: '0', + name: 'root', + }, + effective: { + id: '0', + group: { + id: '0', + }, + }, + saved: { + id: '0', + group: { + id: '0', + }, + }, + }, + ecs: { + version: '1.0.0', + }, + signal: { + parent: { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: '79ctRnABMQha2n6bnxxQ', + type: 'event', + index: 'auditbeat-7.2.0', + depth: 1, + }, + ancestors: [ + { + rule: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + id: '79ctRnABMQha2n6bnxxQ', + type: 'event', + index: 'auditbeat-7.2.0', + depth: 1, + }, + ], + original_time: '2020-02-15T00:09:18.860Z', + status: 'open', + rule: { + id: '2df3a613-f5a8-4b55-bf6a-487fc820b842', + rule_id: '82b2b065-a2ee-49fc-9d6d-781a75c3d280', + false_positives: [], + meta: { + from: '1m', + }, + max_signals: 100, + risk_score: 79, + output_index: '.siem-signals-default', + description: 'matches most events', + from: 'now-360s', + immutable: false, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + interval: '5m', + language: 'kuery', + name: 'matches host.name exists', + query: 'host.name : *', + references: ['https://google.com'], + severity: 'high', + tags: [ + 'host.name exists', + 'for testing', + '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', + '__internal_immutable:false', + ], + type: 'query', + to: 'now', + enabled: true, + filters: [], + created_by: 'elastic', + updated_by: 'elastic', + threat: [ + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1110', + name: 'Brute Force', + id: 'T1110', + }, + { + reference: 'https://attack.mitre.org/techniques/T1098', + name: 'Account Manipulation', + id: 'T1098', + }, + { + reference: 'https://attack.mitre.org/techniques/T1081', + name: 'Credentials in Files', + id: 'T1081', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0006', + name: 'Credential Access', + id: 'TA0006', + }, + }, + { + framework: 'MITRE ATT&CK', + technique: [ + { + reference: 'https://attack.mitre.org/techniques/T1530', + name: 'Data from Cloud Storage Object', + id: 'T1530', + }, + ], + tactic: { + reference: 'https://attack.mitre.org/tactics/TA0009', + name: 'Collection', + id: 'TA0009', + }, + }, + ], + version: 1, + created_at: '2020-02-12T19:49:29.417Z', + updated_at: '2020-02-14T23:15:06.186Z', + }, + original_event: { + dataset: 'process', + kind: 'error', + action: 'process_error', + module: 'system', + }, + }, + }, + }, + ], + }, + aggregations: { + alertsByGrouping: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: '4', + doc_count: 12600, + alerts: { + buckets: [ + { + key_as_string: '2020-01-21T04:30:00.000Z', + key: 1579581000000, + doc_count: 0, + }, + { + key_as_string: '2020-01-22T03:00:00.000Z', + key: 1579662000000, + doc_count: 0, + }, + { + key_as_string: '2020-01-23T01:30:00.000Z', + key: 1579743000000, + doc_count: 0, + }, + { + key_as_string: '2020-01-24T00:00:00.000Z', + key: 1579824000000, + doc_count: 0, + }, + ], + }, + }, + ], + }, + }, +}; + +export const mockAlertsQuery: object = { + aggs: { + alertsByGrouping: { + terms: { + field: 'signal.rule.risk_score', + missing: 'All others', + order: { _count: 'desc' }, + size: 10, + }, + aggs: { + alerts: { + date_histogram: { + field: '@timestamp', + fixed_interval: '81000000ms', + min_doc_count: 0, + extended_bounds: { min: 1579644343954, max: 1582236343955 }, + }, + }, + }, + }, + }, + query: { + bool: { + filter: [ + { bool: { must: [], filter: [{ match_all: {} }], should: [], must_not: [] } }, + { range: { '@timestamp': { gte: 1579644343954, lte: 1582236343955 } } }, + ], + }, + }, +}; + +export const mockStatusAlertQuery: object = { + bool: { + filter: { + terms: { _id: ['b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5'] }, + }, + }, +}; + +export const mockSignalIndex: AlertsIndex = { + name: 'mock-signal-index', +}; + +export const mockUserPrivilege: Privilege = { + username: 'elastic', + has_all_requested: false, + cluster: { + monitor_ml: true, + manage_ccr: true, + manage_index_templates: true, + monitor_watcher: true, + monitor_transform: true, + read_ilm: true, + manage_security: true, + manage_own_api_key: false, + manage_saml: true, + all: true, + manage_ilm: true, + manage_ingest_pipelines: true, + read_ccr: true, + manage_rollup: true, + monitor: true, + manage_watcher: true, + manage: true, + manage_transform: true, + manage_token: true, + manage_ml: true, + manage_pipeline: true, + monitor_rollup: true, + transport_client: true, + create_snapshot: true, + }, + index: { + '.siem-signals-default': { + all: true, + manage_ilm: true, + read: true, + create_index: true, + read_cross_cluster: true, + index: true, + monitor: true, + delete: true, + manage: true, + delete_index: true, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: true, + manage_leader_index: true, + write: true, + }, + }, + is_authenticated: true, + has_encryption_key: true, +}; diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/translations.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/translations.ts new file mode 100644 index 0000000000000..41f6a129d1b5e --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/translations.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ALERT_FETCH_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.alerts.errorFetchingAlertsDescription', + { + defaultMessage: 'Failed to query alerts', + } +); + +export const PRIVILEGE_FETCH_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.alerts.errorFetchingAlertsDescription', + { + defaultMessage: 'Failed to query alerts', + } +); + +export const SIGNAL_GET_NAME_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.alerts.errorGetAlertDescription', + { + defaultMessage: 'Failed to get signal index name', + } +); + +export const SIGNAL_POST_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.alerts.errorPostAlertDescription', + { + defaultMessage: 'Failed to create signal index', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/types.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/types.ts new file mode 100644 index 0000000000000..b425cfd54a7fd --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/types.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface BasicSignals { + signal: AbortSignal; +} +export interface QueryAlerts extends BasicSignals { + query: object; +} + +export interface AlertsResponse { + took: number; + timeout: boolean; +} + +export interface AlertSearchResponse + extends AlertsResponse { + _shards: { + total: number; + successful: number; + skipped: number; + failed: number; + }; + aggregations?: Aggregations; + hits: { + total: { + value: number; + relation: string; + }; + hits: Hit[]; + }; +} + +export interface UpdateAlertStatusProps { + query: object; + status: 'open' | 'closed'; + signal?: AbortSignal; // TODO: implement cancelling +} + +export interface AlertsIndex { + name: string; +} + +export interface Privilege { + username: string; + has_all_requested: boolean; + cluster: { + monitor_ml: boolean; + manage_ccr: boolean; + manage_index_templates: boolean; + monitor_watcher: boolean; + monitor_transform: boolean; + read_ilm: boolean; + manage_security: boolean; + manage_own_api_key: boolean; + manage_saml: boolean; + all: boolean; + manage_ilm: boolean; + manage_ingest_pipelines: boolean; + read_ccr: boolean; + manage_rollup: boolean; + monitor: boolean; + manage_watcher: boolean; + manage: boolean; + manage_transform: boolean; + manage_token: boolean; + manage_ml: boolean; + manage_pipeline: boolean; + monitor_rollup: boolean; + transport_client: boolean; + create_snapshot: boolean; + }; + index: { + [indexName: string]: { + all: boolean; + manage_ilm: boolean; + read: boolean; + create_index: boolean; + read_cross_cluster: boolean; + index: boolean; + monitor: boolean; + delete: boolean; + manage: boolean; + delete_index: boolean; + create_doc: boolean; + view_index_metadata: boolean; + create: boolean; + manage_follow_index: boolean; + manage_leader_index: boolean; + write: boolean; + }; + }; + is_authenticated: boolean; + has_encryption_key: boolean; +} diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_query.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_query.test.tsx new file mode 100644 index 0000000000000..8627b953c8dac --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_query.test.tsx @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useQueryAlerts, ReturnQueryAlerts } from './use_query'; +import * as api from './api'; +import { mockAlertsQuery, alertsMock } from './mock'; + +jest.mock('./api'); + +describe('useQueryAlerts', () => { + const indexName = 'mock-index-name'; + beforeEach(() => { + jest.resetAllMocks(); + }); + test('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + [object, string], + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: true, + data: null, + response: '', + request: '', + setQuery: result.current.setQuery, + refetch: null, + }); + }); + }); + + test('fetch alerts data', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + [object, string], + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: false, + data: alertsMock, + response: JSON.stringify(alertsMock, null, 2), + request: JSON.stringify({ index: [indexName] ?? [''], body: mockAlertsQuery }, null, 2), + setQuery: result.current.setQuery, + refetch: result.current.refetch, + }); + }); + }); + + test('re-fetch alerts data', async () => { + const spyOnfetchQueryAlerts = jest.spyOn(api, 'fetchQueryAlerts'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + [object, string], + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); + await waitForNextUpdate(); + await waitForNextUpdate(); + if (result.current.refetch) { + result.current.refetch(); + } + await waitForNextUpdate(); + expect(spyOnfetchQueryAlerts).toHaveBeenCalledTimes(2); + }); + }); + + test('fetch alert when index name changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); + await act(async () => { + const { rerender, waitForNextUpdate } = renderHook< + [object, string], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], + }); + await waitForNextUpdate(); + await waitForNextUpdate(); + rerender([mockAlertsQuery, 'new-mock-index-name']); + await waitForNextUpdate(); + expect(spyOnfetchRules).toHaveBeenCalledTimes(2); + }); + }); + + test('fetch alert when query object changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + [object, string], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], + }); + await waitForNextUpdate(); + await waitForNextUpdate(); + if (result.current.setQuery) { + result.current.setQuery({ ...mockAlertsQuery }); + } + await waitForNextUpdate(); + expect(spyOnfetchRules).toHaveBeenCalledTimes(2); + }); + }); + + test('if there is an error when fetching data, we should get back the init value for every properties', async () => { + const spyOnGetUserPrivilege = jest.spyOn(api, 'fetchQueryAlerts'); + spyOnGetUserPrivilege.mockImplementation(() => { + throw new Error('Something went wrong, let see what happen'); + }); + await act(async () => { + const { result, waitForNextUpdate } = renderHook>( + () => useQueryAlerts(mockAlertsQuery, 'mock-index-name') + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: false, + data: null, + response: '', + request: '', + setQuery: result.current.setQuery, + refetch: result.current.refetch, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_query.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_query.tsx new file mode 100644 index 0000000000000..9c992fa872705 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_query.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { SetStateAction, useEffect, useState } from 'react'; + +import { fetchQueryAlerts } from './api'; +import { AlertSearchResponse } from './types'; + +type Func = () => void; + +export interface ReturnQueryAlerts { + loading: boolean; + data: AlertSearchResponse | null; + setQuery: React.Dispatch>; + response: string; + request: string; + refetch: Func | null; +} + +/** + * Hook for fetching Alerts from the Detection Engine API + * + * @param initialQuery query dsl object + * + */ +export const useQueryAlerts = ( + initialQuery: object, + indexName?: string | null +): ReturnQueryAlerts => { + const [query, setQuery] = useState(initialQuery); + const [alerts, setAlerts] = useState< + Pick, 'data' | 'setQuery' | 'response' | 'request' | 'refetch'> + >({ + data: null, + response: '', + request: '', + setQuery, + refetch: null, + }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + async function fetchData() { + try { + setLoading(true); + const alertResponse = await fetchQueryAlerts({ + query, + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setAlerts({ + data: alertResponse, + response: JSON.stringify(alertResponse, null, 2), + request: JSON.stringify({ index: [indexName] ?? [''], body: query }, null, 2), + setQuery, + refetch: fetchData, + }); + } + } catch (error) { + if (isSubscribed) { + setAlerts({ + data: null, + response: '', + request: '', + setQuery, + refetch: fetchData, + }); + } + } + if (isSubscribed) { + setLoading(false); + } + } + + fetchData(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [query, indexName]); + + return { loading, ...alerts }; +}; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx similarity index 96% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx index c834e4ab14be2..d0571bfca5b2b 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -30,7 +30,7 @@ describe('useSignalIndex', () => { }); }); - test('fetch signals info', async () => { + test('fetch alerts info', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useSignalIndex() @@ -105,7 +105,7 @@ describe('useSignalIndex', () => { }); }); - test('if there is an error when fetching signals info, signalIndexExists === false && signalIndexName == null', async () => { + test('if there is an error when fetching alerts info, signalIndexExists === false && signalIndexName == null', async () => { const spyOnGetSignalIndex = jest.spyOn(api, 'getSignalIndex'); spyOnGetSignalIndex.mockImplementation(() => { throw new Error('Something went wrong, let see what happen'); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/__mocks__/api.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/__mocks__/api.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/__mocks__/api.ts rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/__mocks__/api.ts diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.test.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/api.test.ts rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.test.ts diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.ts new file mode 100644 index 0000000000000..d59f709bbafc7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.ts @@ -0,0 +1,333 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + DETECTION_ENGINE_RULES_URL, + DETECTION_ENGINE_PREPACKAGED_URL, + DETECTION_ENGINE_RULES_STATUS_URL, + DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, + DETECTION_ENGINE_TAGS_URL, +} from '../../../../../common/constants'; +import { + AddRulesProps, + DeleteRulesProps, + DuplicateRulesProps, + EnableRulesProps, + FetchRulesProps, + FetchRulesResponse, + NewRule, + Rule, + FetchRuleProps, + BasicFetchProps, + ImportDataProps, + ExportDocumentsProps, + RuleStatusResponse, + ImportDataResponse, + PrePackagedRulesStatusResponse, + BulkRuleResponse, +} from './types'; +import { KibanaServices } from '../../../../common/lib/kibana'; +import * as i18n from '../../../pages/detection_engine/rules/translations'; + +/** + * Add provided Rule + * + * @param rule to add + * @param signal to cancel request + * + * @throws An error if response is not OK + */ +export const addRule = async ({ rule, signal }: AddRulesProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { + method: rule.id != null ? 'PUT' : 'POST', + body: JSON.stringify(rule), + signal, + }); + +/** + * Fetches all rules from the Detection Engine API + * + * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) + * @param pagination desired pagination options (e.g. page/perPage) + * @param signal to cancel request + * + * @throws An error if response is not OK + */ +export const fetchRules = async ({ + filterOptions = { + filter: '', + sortField: 'enabled', + sortOrder: 'desc', + showCustomRules: false, + showElasticRules: false, + tags: [], + }, + pagination = { + page: 1, + perPage: 20, + total: 0, + }, + signal, +}: FetchRulesProps): Promise => { + const filters = [ + ...(filterOptions.filter.length ? [`alert.attributes.name: ${filterOptions.filter}`] : []), + ...(filterOptions.showCustomRules + ? [`alert.attributes.tags: "__internal_immutable:false"`] + : []), + ...(filterOptions.showElasticRules + ? [`alert.attributes.tags: "__internal_immutable:true"`] + : []), + ...(filterOptions.tags?.map((t) => `alert.attributes.tags: ${t}`) ?? []), + ]; + + const query = { + page: pagination.page, + per_page: pagination.perPage, + sort_field: filterOptions.sortField, + sort_order: filterOptions.sortOrder, + ...(filters.length ? { filter: filters.join(' AND ') } : {}), + }; + + return KibanaServices.get().http.fetch( + `${DETECTION_ENGINE_RULES_URL}/_find`, + { + method: 'GET', + query, + signal, + } + ); +}; + +/** + * Fetch a Rule by providing a Rule ID + * + * @param id Rule ID's (not rule_id) + * @param signal to cancel request + * + * @throws An error if response is not OK + */ +export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { + method: 'GET', + query: { id }, + signal, + }); + +/** + * Enables/Disables provided Rule ID's + * + * @param ids array of Rule ID's (not rule_id) to enable/disable + * @param enabled to enable or disable + * + * @throws An error if response is not OK + */ +export const enableRules = async ({ ids, enabled }: EnableRulesProps): Promise => + KibanaServices.get().http.fetch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`, { + method: 'PATCH', + body: JSON.stringify(ids.map((id) => ({ id, enabled }))), + }); + +/** + * Deletes provided Rule ID's + * + * @param ids array of Rule ID's (not rule_id) to delete + * + * @throws An error if response is not OK + */ +export const deleteRules = async ({ ids }: DeleteRulesProps): Promise => + KibanaServices.get().http.fetch(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, { + method: 'DELETE', + body: JSON.stringify(ids.map((id) => ({ id }))), + }); + +/** + * Duplicates provided Rules + * + * @param rules to duplicate + * + * @throws An error if response is not OK + */ +export const duplicateRules = async ({ rules }: DuplicateRulesProps): Promise => + KibanaServices.get().http.fetch(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`, { + method: 'POST', + body: JSON.stringify( + rules.map((rule) => ({ + ...rule, + name: `${rule.name} [${i18n.DUPLICATE}]`, + created_at: undefined, + created_by: undefined, + id: undefined, + rule_id: undefined, + updated_at: undefined, + updated_by: undefined, + enabled: rule.enabled, + immutable: undefined, + last_success_at: undefined, + last_success_message: undefined, + last_failure_at: undefined, + last_failure_message: undefined, + status: undefined, + status_date: undefined, + })) + ), + }); + +/** + * Create Prepackaged Rules + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const createPrepackagedRules = async ({ signal }: BasicFetchProps): Promise => { + await KibanaServices.get().http.fetch(DETECTION_ENGINE_PREPACKAGED_URL, { + method: 'PUT', + signal, + }); + + return true; +}; + +/** + * Imports rules in the same format as exported via the _export API + * + * @param fileToImport File to upload containing rules to import + * @param overwrite whether or not to overwrite rules with the same ruleId + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const importRules = async ({ + fileToImport, + overwrite = false, + signal, +}: ImportDataProps): Promise => { + const formData = new FormData(); + formData.append('file', fileToImport); + + return KibanaServices.get().http.fetch( + `${DETECTION_ENGINE_RULES_URL}/_import`, + { + method: 'POST', + headers: { 'Content-Type': undefined }, + query: { overwrite }, + body: formData, + signal, + } + ); +}; + +/** + * Export rules from the server as a file download + * + * @param excludeExportDetails whether or not to exclude additional details at bottom of exported file (defaults to false) + * @param filename of exported rules. Be sure to include `.ndjson` extension! (defaults to localized `rules_export.ndjson`) + * @param ruleIds array of rule_id's (not id!) to export (empty array exports _all_ rules) + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const exportRules = async ({ + excludeExportDetails = false, + filename = `${i18n.EXPORT_FILENAME}.ndjson`, + ids = [], + signal, +}: ExportDocumentsProps): Promise => { + const body = + ids.length > 0 + ? JSON.stringify({ objects: ids.map((rule) => ({ rule_id: rule })) }) + : undefined; + + return KibanaServices.get().http.fetch(`${DETECTION_ENGINE_RULES_URL}/_export`, { + method: 'POST', + body, + query: { + exclude_export_details: excludeExportDetails, + file_name: filename, + }, + signal, + }); +}; + +/** + * Get Rule Status provided Rule ID + * + * @param id string of Rule ID's (not rule_id) + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getRuleStatusById = async ({ + id, + signal, +}: { + id: string; + signal: AbortSignal; +}): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_STATUS_URL, { + method: 'POST', + body: JSON.stringify({ ids: [id] }), + signal, + }); + +/** + * Return rule statuses given list of alert ids + * + * @param ids array of string of Rule ID's (not rule_id) + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getRulesStatusByIds = async ({ + ids, + signal, +}: { + ids: string[]; + signal: AbortSignal; +}): Promise => { + const res = await KibanaServices.get().http.fetch( + DETECTION_ENGINE_RULES_STATUS_URL, + { + method: 'POST', + body: JSON.stringify({ ids }), + signal, + } + ); + return res; +}; + +/** + * Fetch all unique Tags used by Rules + * + * @param signal to cancel request + * + * @throws An error if response is not OK + */ +export const fetchTags = async ({ signal }: { signal: AbortSignal }): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_TAGS_URL, { + method: 'GET', + signal, + }); + +/** + * Get pre packaged rules Status + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getPrePackagedRulesStatus = async ({ + signal, +}: { + signal: AbortSignal; +}): Promise => + KibanaServices.get().http.fetch( + DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, + { + method: 'GET', + signal, + } + ); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/fetch_index_patterns.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/fetch_index_patterns.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/fetch_index_patterns.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/fetch_index_patterns.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/fetch_index_patterns.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/fetch_index_patterns.tsx similarity index 98% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/fetch_index_patterns.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/fetch_index_patterns.tsx index dec9f344e16b8..157f1490971f1 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/fetch_index_patterns.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/fetch_index_patterns.tsx @@ -68,7 +68,7 @@ export const useFetchIndexPatterns = (defaultIndices: string[] = []): Return => }, }) .then( - result => { + (result) => { if (isSubscribed) { setIsLoading(false); setIndicesExists(get('data.source.status.indicesExist', result)); @@ -80,7 +80,7 @@ export const useFetchIndexPatterns = (defaultIndices: string[] = []): Return => ); } }, - error => { + (error) => { if (isSubscribed) { setIsLoading(false); errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster }); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/index.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/index.ts rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/index.ts diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/mock.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/mock.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/mock.ts rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/mock.ts diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/persist_rule.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/persist_rule.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/persist_rule.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/persist_rule.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/persist_rule.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/persist_rule.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/persist_rule.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/persist_rule.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/translations.ts new file mode 100644 index 0000000000000..2b4b32bce9c7b --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/translations.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const RULE_FETCH_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.rules', + { + defaultMessage: 'Failed to fetch Rules', + } +); + +export const RULE_ADD_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.addRuleFailDescription', + { + defaultMessage: 'Failed to add Rule', + } +); + +export const RULE_PREPACKAGED_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleFailDescription', + { + defaultMessage: 'Failed to installed pre-packaged rules from elastic', + } +); + +export const RULE_PREPACKAGED_SUCCESS = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleSuccesDescription', + { + defaultMessage: 'Installed pre-packaged rules from elastic', + } +); + +export const TAG_FETCH_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription', + { + defaultMessage: 'Failed to fetch Tags', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts new file mode 100644 index 0000000000000..ab9b88fb81fa7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; + +import { RuleTypeSchema } from '../../../../../common/detection_engine/types'; + +/** + * Params is an "record", since it is a type of AlertActionParams which is action templates. + * @see x-pack/plugins/alerts/common/alert.ts + */ +export const action = t.exact( + t.type({ + group: t.string, + id: t.string, + action_type_id: t.string, + params: t.record(t.string, t.any), + }) +); + +export const NewRuleSchema = t.intersection([ + t.type({ + description: t.string, + enabled: t.boolean, + interval: t.string, + name: t.string, + risk_score: t.number, + severity: t.string, + type: RuleTypeSchema, + }), + t.partial({ + actions: t.array(action), + anomaly_threshold: t.number, + created_by: t.string, + false_positives: t.array(t.string), + filters: t.array(t.unknown), + from: t.string, + id: t.string, + index: t.array(t.string), + language: t.string, + machine_learning_job_id: t.string, + max_signals: t.number, + query: t.string, + references: t.array(t.string), + rule_id: t.string, + saved_id: t.string, + tags: t.array(t.string), + threat: t.array(t.unknown), + throttle: t.union([t.string, t.null]), + to: t.string, + updated_by: t.string, + note: t.string, + }), +]); + +export const NewRulesSchema = t.array(NewRuleSchema); +export type NewRule = t.TypeOf; + +export interface AddRulesProps { + rule: NewRule; + signal: AbortSignal; +} + +const MetaRule = t.intersection([ + t.type({ + from: t.string, + }), + t.partial({ + throttle: t.string, + kibana_siem_app_url: t.string, + }), +]); + +export const RuleSchema = t.intersection([ + t.type({ + created_at: t.string, + created_by: t.string, + description: t.string, + enabled: t.boolean, + false_positives: t.array(t.string), + from: t.string, + id: t.string, + interval: t.string, + immutable: t.boolean, + name: t.string, + max_signals: t.number, + references: t.array(t.string), + risk_score: t.number, + rule_id: t.string, + severity: t.string, + tags: t.array(t.string), + type: RuleTypeSchema, + to: t.string, + threat: t.array(t.unknown), + updated_at: t.string, + updated_by: t.string, + actions: t.array(action), + throttle: t.union([t.string, t.null]), + }), + t.partial({ + anomaly_threshold: t.number, + filters: t.array(t.unknown), + index: t.array(t.string), + language: t.string, + last_failure_at: t.string, + last_failure_message: t.string, + meta: MetaRule, + machine_learning_job_id: t.string, + output_index: t.string, + query: t.string, + saved_id: t.string, + status: t.string, + status_date: t.string, + timeline_id: t.string, + timeline_title: t.string, + note: t.string, + version: t.number, + }), +]); + +export const RulesSchema = t.array(RuleSchema); + +export type Rule = t.TypeOf; +export type Rules = t.TypeOf; + +export interface RuleError { + id?: string; + rule_id?: string; + error: { status_code: number; message: string }; +} + +export type BulkRuleResponse = Array; + +export interface RuleResponseBuckets { + rules: Rule[]; + errors: RuleError[]; +} + +export interface PaginationOptions { + page: number; + perPage: number; + total: number; +} + +export interface FetchRulesProps { + pagination?: PaginationOptions; + filterOptions?: FilterOptions; + signal: AbortSignal; +} + +export interface FilterOptions { + filter: string; + sortField: string; + sortOrder: 'asc' | 'desc'; + showCustomRules?: boolean; + showElasticRules?: boolean; + tags?: string[]; +} + +export interface FetchRulesResponse { + page: number; + perPage: number; + total: number; + data: Rule[]; +} + +export interface FetchRuleProps { + id: string; + signal: AbortSignal; +} + +export interface EnableRulesProps { + ids: string[]; + enabled: boolean; +} + +export interface DeleteRulesProps { + ids: string[]; +} + +export interface DuplicateRulesProps { + rules: Rule[]; +} + +export interface BasicFetchProps { + signal: AbortSignal; +} + +export interface ImportDataProps { + fileToImport: File; + overwrite?: boolean; + signal: AbortSignal; +} + +export interface ImportRulesResponseError { + rule_id: string; + error: { + status_code: number; + message: string; + }; +} + +export interface ImportDataResponse { + success: boolean; + success_count: number; + errors: ImportRulesResponseError[]; +} + +export interface ExportDocumentsProps { + ids: string[]; + filename?: string; + excludeExportDetails?: boolean; + signal: AbortSignal; +} + +export interface RuleStatus { + current_status: RuleInfoStatus; + failures: RuleInfoStatus[]; +} + +export type RuleStatusType = 'executing' | 'failed' | 'going to run' | 'succeeded'; +export interface RuleInfoStatus { + alert_id: string; + status_date: string; + status: RuleStatusType | null; + last_failure_at: string | null; + last_success_at: string | null; + last_failure_message: string | null; + last_success_message: string | null; + last_look_back_date: string | null | undefined; + gap: string | null | undefined; + bulk_create_time_durations: string[] | null | undefined; + search_after_time_durations: string[] | null | undefined; +} + +export type RuleStatusResponse = Record; + +export interface PrePackagedRulesStatusResponse { + rules_custom_installed: number; + rules_installed: number; + rules_not_installed: number; + rules_not_updated: number; +} diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.tsx similarity index 99% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.tsx index f1897002e13cd..de4037ce7134c 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -113,7 +113,7 @@ export const usePrePackagedRules = ({ }; const createElasticRules = async (): Promise => { - return new Promise(async resolve => { + return new Promise(async (resolve) => { try { if ( canUserCRUD && diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.test.tsx similarity index 98% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.test.tsx index ab09f796ad49b..9bfbade060303 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.test.tsx @@ -73,7 +73,7 @@ describe('useRule', () => { test('fetch a new rule', async () => { const spyOnfetchRuleById = jest.spyOn(api, 'fetchRuleById'); await act(async () => { - const { rerender, waitForNextUpdate } = renderHook(id => useRule(id), { + const { rerender, waitForNextUpdate } = renderHook((id) => useRule(id), { initialProps: 'myOwnRuleID', }); await waitForNextUpdate(); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule_status.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.tsx similarity index 97% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule_status.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.tsx index 9164f38d2ac28..b9f5e1f152c21 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rule_status.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.tsx @@ -94,7 +94,7 @@ export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { if (isSubscribed) { setRuleStatuses( - rules.map(rule => ({ + rules.map((rule) => ({ id: rule.id, activate: rule.enabled, name: rule.name, @@ -113,7 +113,7 @@ export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { } }; if (rules != null && rules.length > 0) { - fetchData(rules.map(r => r.id)); + fetchData(rules.map((r) => r.id)); } return () => { isSubscribed = false; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rules.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.test.tsx similarity index 98% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rules.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.test.tsx index 5d13b57f862bc..ad34c39272bbf 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.test.tsx @@ -16,7 +16,7 @@ describe('useRules', () => { }); test('init', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(props => + const { result, waitForNextUpdate } = renderHook((props) => useRules({ pagination: { page: 1, @@ -136,7 +136,7 @@ describe('useRules', () => { test('re-fetch rules', async () => { const spyOnfetchRules = jest.spyOn(api, 'fetchRules'); await act(async () => { - const { result, waitForNextUpdate } = renderHook(id => + const { result, waitForNextUpdate } = renderHook((id) => useRules({ pagination: { page: 1, @@ -164,7 +164,7 @@ describe('useRules', () => { const spyOnfetchRules = jest.spyOn(api, 'fetchRules'); await act(async () => { const { rerender, waitForNextUpdate } = renderHook( - args => useRules(args), + (args) => useRules(args), { initialProps: { pagination: { diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rules.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_rules.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_tags.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_tags.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_tags.test.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_tags.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_tags.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_tags.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/use_tags.tsx rename to x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_tags.tsx diff --git a/x-pack/plugins/siem/public/alerts/index.ts b/x-pack/plugins/security_solution/public/alerts/index.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/index.ts rename to x-pack/plugins/security_solution/public/alerts/index.ts diff --git a/x-pack/plugins/siem/public/alerts/mitre/mitre_tactics_techniques.ts b/x-pack/plugins/security_solution/public/alerts/mitre/mitre_tactics_techniques.ts similarity index 79% rename from x-pack/plugins/siem/public/alerts/mitre/mitre_tactics_techniques.ts rename to x-pack/plugins/security_solution/public/alerts/mitre/mitre_tactics_techniques.ts index 16ab73365222b..fb8deeec8309c 100644 --- a/x-pack/plugins/siem/public/alerts/mitre/mitre_tactics_techniques.ts +++ b/x-pack/plugins/security_solution/public/alerts/mitre/mitre_tactics_techniques.ts @@ -76,9 +76,12 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0009', name: 'Collection', reference: 'https://attack.mitre.org/tactics/TA0009', - text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.collectionDescription', { - defaultMessage: 'Collection (TA0009)', - }), + text: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.collectionDescription', + { + defaultMessage: 'Collection (TA0009)', + } + ), value: 'collection', }, { @@ -86,7 +89,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ name: 'Command and Control', reference: 'https://attack.mitre.org/tactics/TA0011', text: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTactics.commandAndControlDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.commandAndControlDescription', { defaultMessage: 'Command and Control (TA0011)' } ), value: 'commandAndControl', @@ -96,7 +99,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ name: 'Credential Access', reference: 'https://attack.mitre.org/tactics/TA0006', text: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTactics.credentialAccessDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.credentialAccessDescription', { defaultMessage: 'Credential Access (TA0006)' } ), value: 'credentialAccess', @@ -106,7 +109,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ name: 'Defense Evasion', reference: 'https://attack.mitre.org/tactics/TA0005', text: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTactics.defenseEvasionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.defenseEvasionDescription', { defaultMessage: 'Defense Evasion (TA0005)' } ), value: 'defenseEvasion', @@ -115,45 +118,60 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0007', name: 'Discovery', reference: 'https://attack.mitre.org/tactics/TA0007', - text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.discoveryDescription', { - defaultMessage: 'Discovery (TA0007)', - }), + text: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.discoveryDescription', + { + defaultMessage: 'Discovery (TA0007)', + } + ), value: 'discovery', }, { id: 'TA0002', name: 'Execution', reference: 'https://attack.mitre.org/tactics/TA0002', - text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.executionDescription', { - defaultMessage: 'Execution (TA0002)', - }), + text: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.executionDescription', + { + defaultMessage: 'Execution (TA0002)', + } + ), value: 'execution', }, { id: 'TA0010', name: 'Exfiltration', reference: 'https://attack.mitre.org/tactics/TA0010', - text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.exfiltrationDescription', { - defaultMessage: 'Exfiltration (TA0010)', - }), + text: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.exfiltrationDescription', + { + defaultMessage: 'Exfiltration (TA0010)', + } + ), value: 'exfiltration', }, { id: 'TA0040', name: 'Impact', reference: 'https://attack.mitre.org/tactics/TA0040', - text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.impactDescription', { - defaultMessage: 'Impact (TA0040)', - }), + text: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.impactDescription', + { + defaultMessage: 'Impact (TA0040)', + } + ), value: 'impact', }, { id: 'TA0001', name: 'Initial Access', reference: 'https://attack.mitre.org/tactics/TA0001', - text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.initialAccessDescription', { - defaultMessage: 'Initial Access (TA0001)', - }), + text: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.initialAccessDescription', + { + defaultMessage: 'Initial Access (TA0001)', + } + ), value: 'initialAccess', }, { @@ -161,7 +179,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ name: 'Lateral Movement', reference: 'https://attack.mitre.org/tactics/TA0008', text: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTactics.lateralMovementDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.lateralMovementDescription', { defaultMessage: 'Lateral Movement (TA0008)' } ), value: 'lateralMovement', @@ -170,9 +188,12 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0003', name: 'Persistence', reference: 'https://attack.mitre.org/tactics/TA0003', - text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.persistenceDescription', { - defaultMessage: 'Persistence (TA0003)', - }), + text: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.persistenceDescription', + { + defaultMessage: 'Persistence (TA0003)', + } + ), value: 'persistence', }, { @@ -180,7 +201,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ name: 'Privilege Escalation', reference: 'https://attack.mitre.org/tactics/TA0004', text: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTactics.privilegeEscalationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTactics.privilegeEscalationDescription', { defaultMessage: 'Privilege Escalation (TA0004)' } ), value: 'privilegeEscalation', @@ -1789,7 +1810,7 @@ export const technique = [ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.bashProfileAndBashrcDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bashProfileAndBashrcDescription', { defaultMessage: '.bash_profile and .bashrc (T1156)' } ), id: 'T1156', @@ -1800,7 +1821,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.accessTokenManipulationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accessTokenManipulationDescription', { defaultMessage: 'Access Token Manipulation (T1134)' } ), id: 'T1134', @@ -1811,7 +1832,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.accessibilityFeaturesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accessibilityFeaturesDescription', { defaultMessage: 'Accessibility Features (T1015)' } ), id: 'T1015', @@ -1822,7 +1843,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.accountAccessRemovalDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountAccessRemovalDescription', { defaultMessage: 'Account Access Removal (T1531)' } ), id: 'T1531', @@ -1833,7 +1854,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.accountDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountDiscoveryDescription', { defaultMessage: 'Account Discovery (T1087)' } ), id: 'T1087', @@ -1844,7 +1865,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.accountManipulationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountManipulationDescription', { defaultMessage: 'Account Manipulation (T1098)' } ), id: 'T1098', @@ -1855,7 +1876,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.appCertDlLsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appCertDlLsDescription', { defaultMessage: 'AppCert DLLs (T1182)' } ), id: 'T1182', @@ -1866,7 +1887,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.appInitDlLsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appInitDlLsDescription', { defaultMessage: 'AppInit DLLs (T1103)' } ), id: 'T1103', @@ -1877,7 +1898,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.appleScriptDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appleScriptDescription', { defaultMessage: 'AppleScript (T1155)' } ), id: 'T1155', @@ -1888,7 +1909,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationAccessTokenDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationAccessTokenDescription', { defaultMessage: 'Application Access Token (T1527)' } ), id: 'T1527', @@ -1899,7 +1920,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationDeploymentSoftwareDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationDeploymentSoftwareDescription', { defaultMessage: 'Application Deployment Software (T1017)' } ), id: 'T1017', @@ -1910,7 +1931,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationShimmingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationShimmingDescription', { defaultMessage: 'Application Shimming (T1138)' } ), id: 'T1138', @@ -1921,7 +1942,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationWindowDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationWindowDiscoveryDescription', { defaultMessage: 'Application Window Discovery (T1010)' } ), id: 'T1010', @@ -1932,7 +1953,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.audioCaptureDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.audioCaptureDescription', { defaultMessage: 'Audio Capture (T1123)' } ), id: 'T1123', @@ -1943,7 +1964,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.authenticationPackageDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.authenticationPackageDescription', { defaultMessage: 'Authentication Package (T1131)' } ), id: 'T1131', @@ -1954,7 +1975,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.automatedCollectionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.automatedCollectionDescription', { defaultMessage: 'Automated Collection (T1119)' } ), id: 'T1119', @@ -1965,7 +1986,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.automatedExfiltrationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.automatedExfiltrationDescription', { defaultMessage: 'Automated Exfiltration (T1020)' } ), id: 'T1020', @@ -1975,9 +1996,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'automatedExfiltration', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.bitsJobsDescription', { - defaultMessage: 'BITS Jobs (T1197)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bitsJobsDescription', + { + defaultMessage: 'BITS Jobs (T1197)', + } + ), id: 'T1197', name: 'BITS Jobs', reference: 'https://attack.mitre.org/techniques/T1197', @@ -1986,7 +2010,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.bashHistoryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bashHistoryDescription', { defaultMessage: 'Bash History (T1139)' } ), id: 'T1139', @@ -1997,7 +2021,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.binaryPaddingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.binaryPaddingDescription', { defaultMessage: 'Binary Padding (T1009)' } ), id: 'T1009', @@ -2007,9 +2031,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'binaryPadding', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.bootkitDescription', { - defaultMessage: 'Bootkit (T1067)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootkitDescription', + { + defaultMessage: 'Bootkit (T1067)', + } + ), id: 'T1067', name: 'Bootkit', reference: 'https://attack.mitre.org/techniques/T1067', @@ -2018,7 +2045,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription', { defaultMessage: 'Browser Bookmark Discovery (T1217)' } ), id: 'T1217', @@ -2029,7 +2056,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.browserExtensionsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserExtensionsDescription', { defaultMessage: 'Browser Extensions (T1176)' } ), id: 'T1176', @@ -2040,7 +2067,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.bruteForceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bruteForceDescription', { defaultMessage: 'Brute Force (T1110)' } ), id: 'T1110', @@ -2051,7 +2078,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.bypassUserAccountControlDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bypassUserAccountControlDescription', { defaultMessage: 'Bypass User Account Control (T1088)' } ), id: 'T1088', @@ -2061,9 +2088,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'bypassUserAccountControl', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.cmstpDescription', { - defaultMessage: 'CMSTP (T1191)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cmstpDescription', + { + defaultMessage: 'CMSTP (T1191)', + } + ), id: 'T1191', name: 'CMSTP', reference: 'https://attack.mitre.org/techniques/T1191', @@ -2072,7 +2102,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.changeDefaultFileAssociationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.changeDefaultFileAssociationDescription', { defaultMessage: 'Change Default File Association (T1042)' } ), id: 'T1042', @@ -2083,7 +2113,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.clearCommandHistoryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.clearCommandHistoryDescription', { defaultMessage: 'Clear Command History (T1146)' } ), id: 'T1146', @@ -2094,7 +2124,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.clipboardDataDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.clipboardDataDescription', { defaultMessage: 'Clipboard Data (T1115)' } ), id: 'T1115', @@ -2105,7 +2135,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudInstanceMetadataApiDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudInstanceMetadataApiDescription', { defaultMessage: 'Cloud Instance Metadata API (T1522)' } ), id: 'T1522', @@ -2116,7 +2146,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudServiceDashboardDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudServiceDashboardDescription', { defaultMessage: 'Cloud Service Dashboard (T1538)' } ), id: 'T1538', @@ -2127,7 +2157,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudServiceDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudServiceDiscoveryDescription', { defaultMessage: 'Cloud Service Discovery (T1526)' } ), id: 'T1526', @@ -2138,7 +2168,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.codeSigningDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.codeSigningDescription', { defaultMessage: 'Code Signing (T1116)' } ), id: 'T1116', @@ -2149,7 +2179,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.commandLineInterfaceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.commandLineInterfaceDescription', { defaultMessage: 'Command-Line Interface (T1059)' } ), id: 'T1059', @@ -2160,7 +2190,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.commonlyUsedPortDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.commonlyUsedPortDescription', { defaultMessage: 'Commonly Used Port (T1043)' } ), id: 'T1043', @@ -2171,7 +2201,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.communicationThroughRemovableMediaDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.communicationThroughRemovableMediaDescription', { defaultMessage: 'Communication Through Removable Media (T1092)' } ), id: 'T1092', @@ -2182,7 +2212,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.compileAfterDeliveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compileAfterDeliveryDescription', { defaultMessage: 'Compile After Delivery (T1500)' } ), id: 'T1500', @@ -2193,7 +2223,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.compiledHtmlFileDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compiledHtmlFileDescription', { defaultMessage: 'Compiled HTML File (T1223)' } ), id: 'T1223', @@ -2204,7 +2234,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.componentFirmwareDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentFirmwareDescription', { defaultMessage: 'Component Firmware (T1109)' } ), id: 'T1109', @@ -2215,7 +2245,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.componentObjectModelHijackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentObjectModelHijackingDescription', { defaultMessage: 'Component Object Model Hijacking (T1122)' } ), id: 'T1122', @@ -2226,7 +2256,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.componentObjectModelAndDistributedComDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentObjectModelAndDistributedComDescription', { defaultMessage: 'Component Object Model and Distributed COM (T1175)' } ), id: 'T1175', @@ -2237,7 +2267,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.connectionProxyDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.connectionProxyDescription', { defaultMessage: 'Connection Proxy (T1090)' } ), id: 'T1090', @@ -2248,7 +2278,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.controlPanelItemsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.controlPanelItemsDescription', { defaultMessage: 'Control Panel Items (T1196)' } ), id: 'T1196', @@ -2259,7 +2289,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.createAccountDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.createAccountDescription', { defaultMessage: 'Create Account (T1136)' } ), id: 'T1136', @@ -2270,7 +2300,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialDumpingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialDumpingDescription', { defaultMessage: 'Credential Dumping (T1003)' } ), id: 'T1003', @@ -2281,7 +2311,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsFromWebBrowsersDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsFromWebBrowsersDescription', { defaultMessage: 'Credentials from Web Browsers (T1503)' } ), id: 'T1503', @@ -2292,7 +2322,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsInFilesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsInFilesDescription', { defaultMessage: 'Credentials in Files (T1081)' } ), id: 'T1081', @@ -2303,7 +2333,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsInRegistryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsInRegistryDescription', { defaultMessage: 'Credentials in Registry (T1214)' } ), id: 'T1214', @@ -2314,7 +2344,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.customCommandAndControlProtocolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.customCommandAndControlProtocolDescription', { defaultMessage: 'Custom Command and Control Protocol (T1094)' } ), id: 'T1094', @@ -2325,7 +2355,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.customCryptographicProtocolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.customCryptographicProtocolDescription', { defaultMessage: 'Custom Cryptographic Protocol (T1024)' } ), id: 'T1024', @@ -2335,9 +2365,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'customCryptographicProtocol', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.dcShadowDescription', { - defaultMessage: 'DCShadow (T1207)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dcShadowDescription', + { + defaultMessage: 'DCShadow (T1207)', + } + ), id: 'T1207', name: 'DCShadow', reference: 'https://attack.mitre.org/techniques/T1207', @@ -2346,7 +2379,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dllSearchOrderHijackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dllSearchOrderHijackingDescription', { defaultMessage: 'DLL Search Order Hijacking (T1038)' } ), id: 'T1038', @@ -2357,7 +2390,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dllSideLoadingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dllSideLoadingDescription', { defaultMessage: 'DLL Side-Loading (T1073)' } ), id: 'T1073', @@ -2368,7 +2401,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataCompressedDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataCompressedDescription', { defaultMessage: 'Data Compressed (T1002)' } ), id: 'T1002', @@ -2379,7 +2412,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataDestructionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataDestructionDescription', { defaultMessage: 'Data Destruction (T1485)' } ), id: 'T1485', @@ -2390,7 +2423,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncodingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncodingDescription', { defaultMessage: 'Data Encoding (T1132)' } ), id: 'T1132', @@ -2401,7 +2434,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncryptedDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncryptedDescription', { defaultMessage: 'Data Encrypted (T1022)' } ), id: 'T1022', @@ -2412,7 +2445,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncryptedForImpactDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncryptedForImpactDescription', { defaultMessage: 'Data Encrypted for Impact (T1486)' } ), id: 'T1486', @@ -2423,7 +2456,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataObfuscationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataObfuscationDescription', { defaultMessage: 'Data Obfuscation (T1001)' } ), id: 'T1001', @@ -2434,7 +2467,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataStagedDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataStagedDescription', { defaultMessage: 'Data Staged (T1074)' } ), id: 'T1074', @@ -2445,7 +2478,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataTransferSizeLimitsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataTransferSizeLimitsDescription', { defaultMessage: 'Data Transfer Size Limits (T1030)' } ), id: 'T1030', @@ -2456,7 +2489,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromCloudStorageObjectDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromCloudStorageObjectDescription', { defaultMessage: 'Data from Cloud Storage Object (T1530)' } ), id: 'T1530', @@ -2467,7 +2500,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromInformationRepositoriesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromInformationRepositoriesDescription', { defaultMessage: 'Data from Information Repositories (T1213)' } ), id: 'T1213', @@ -2478,7 +2511,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromLocalSystemDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromLocalSystemDescription', { defaultMessage: 'Data from Local System (T1005)' } ), id: 'T1005', @@ -2489,7 +2522,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromNetworkSharedDriveDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromNetworkSharedDriveDescription', { defaultMessage: 'Data from Network Shared Drive (T1039)' } ), id: 'T1039', @@ -2500,7 +2533,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromRemovableMediaDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromRemovableMediaDescription', { defaultMessage: 'Data from Removable Media (T1025)' } ), id: 'T1025', @@ -2511,7 +2544,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.defacementDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.defacementDescription', { defaultMessage: 'Defacement (T1491)' } ), id: 'T1491', @@ -2522,7 +2555,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.deobfuscateDecodeFilesOrInformationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.deobfuscateDecodeFilesOrInformationDescription', { defaultMessage: 'Deobfuscate/Decode Files or Information (T1140)' } ), id: 'T1140', @@ -2533,7 +2566,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.disablingSecurityToolsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.disablingSecurityToolsDescription', { defaultMessage: 'Disabling Security Tools (T1089)' } ), id: 'T1089', @@ -2544,7 +2577,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.diskContentWipeDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskContentWipeDescription', { defaultMessage: 'Disk Content Wipe (T1488)' } ), id: 'T1488', @@ -2555,7 +2588,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.diskStructureWipeDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskStructureWipeDescription', { defaultMessage: 'Disk Structure Wipe (T1487)' } ), id: 'T1487', @@ -2566,7 +2599,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.domainFrontingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainFrontingDescription', { defaultMessage: 'Domain Fronting (T1172)' } ), id: 'T1172', @@ -2577,7 +2610,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.domainGenerationAlgorithmsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainGenerationAlgorithmsDescription', { defaultMessage: 'Domain Generation Algorithms (T1483)' } ), id: 'T1483', @@ -2588,7 +2621,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.domainTrustDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainTrustDiscoveryDescription', { defaultMessage: 'Domain Trust Discovery (T1482)' } ), id: 'T1482', @@ -2599,7 +2632,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.driveByCompromiseDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.driveByCompromiseDescription', { defaultMessage: 'Drive-by Compromise (T1189)' } ), id: 'T1189', @@ -2610,7 +2643,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dylibHijackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dylibHijackingDescription', { defaultMessage: 'Dylib Hijacking (T1157)' } ), id: 'T1157', @@ -2621,7 +2654,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.dynamicDataExchangeDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dynamicDataExchangeDescription', { defaultMessage: 'Dynamic Data Exchange (T1173)' } ), id: 'T1173', @@ -2632,7 +2665,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.elevatedExecutionWithPromptDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.elevatedExecutionWithPromptDescription', { defaultMessage: 'Elevated Execution with Prompt (T1514)' } ), id: 'T1514', @@ -2643,7 +2676,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.emailCollectionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.emailCollectionDescription', { defaultMessage: 'Email Collection (T1114)' } ), id: 'T1114', @@ -2653,9 +2686,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'emailCollection', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.emondDescription', { - defaultMessage: 'Emond (T1519)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.emondDescription', + { + defaultMessage: 'Emond (T1519)', + } + ), id: 'T1519', name: 'Emond', reference: 'https://attack.mitre.org/techniques/T1519', @@ -2664,7 +2700,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.endpointDenialOfServiceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.endpointDenialOfServiceDescription', { defaultMessage: 'Endpoint Denial of Service (T1499)' } ), id: 'T1499', @@ -2675,7 +2711,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.executionGuardrailsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.executionGuardrailsDescription', { defaultMessage: 'Execution Guardrails (T1480)' } ), id: 'T1480', @@ -2686,7 +2722,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.executionThroughApiDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.executionThroughApiDescription', { defaultMessage: 'Execution through API (T1106)' } ), id: 'T1106', @@ -2697,7 +2733,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.executionThroughModuleLoadDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.executionThroughModuleLoadDescription', { defaultMessage: 'Execution through Module Load (T1129)' } ), id: 'T1129', @@ -2708,7 +2744,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverAlternativeProtocolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverAlternativeProtocolDescription', { defaultMessage: 'Exfiltration Over Alternative Protocol (T1048)' } ), id: 'T1048', @@ -2719,7 +2755,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverCommandAndControlChannelDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverCommandAndControlChannelDescription', { defaultMessage: 'Exfiltration Over Command and Control Channel (T1041)' } ), id: 'T1041', @@ -2730,7 +2766,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverOtherNetworkMediumDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverOtherNetworkMediumDescription', { defaultMessage: 'Exfiltration Over Other Network Medium (T1011)' } ), id: 'T1011', @@ -2741,7 +2777,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverPhysicalMediumDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverPhysicalMediumDescription', { defaultMessage: 'Exfiltration Over Physical Medium (T1052)' } ), id: 'T1052', @@ -2752,7 +2788,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitPublicFacingApplicationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitPublicFacingApplicationDescription', { defaultMessage: 'Exploit Public-Facing Application (T1190)' } ), id: 'T1190', @@ -2763,7 +2799,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForClientExecutionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForClientExecutionDescription', { defaultMessage: 'Exploitation for Client Execution (T1203)' } ), id: 'T1203', @@ -2774,7 +2810,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForCredentialAccessDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForCredentialAccessDescription', { defaultMessage: 'Exploitation for Credential Access (T1212)' } ), id: 'T1212', @@ -2785,7 +2821,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForDefenseEvasionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForDefenseEvasionDescription', { defaultMessage: 'Exploitation for Defense Evasion (T1211)' } ), id: 'T1211', @@ -2796,7 +2832,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForPrivilegeEscalationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForPrivilegeEscalationDescription', { defaultMessage: 'Exploitation for Privilege Escalation (T1068)' } ), id: 'T1068', @@ -2807,7 +2843,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationOfRemoteServicesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationOfRemoteServicesDescription', { defaultMessage: 'Exploitation of Remote Services (T1210)' } ), id: 'T1210', @@ -2818,7 +2854,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.externalRemoteServicesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.externalRemoteServicesDescription', { defaultMessage: 'External Remote Services (T1133)' } ), id: 'T1133', @@ -2829,7 +2865,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.extraWindowMemoryInjectionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.extraWindowMemoryInjectionDescription', { defaultMessage: 'Extra Window Memory Injection (T1181)' } ), id: 'T1181', @@ -2840,7 +2876,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.fallbackChannelsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fallbackChannelsDescription', { defaultMessage: 'Fallback Channels (T1008)' } ), id: 'T1008', @@ -2851,7 +2887,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.fileDeletionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileDeletionDescription', { defaultMessage: 'File Deletion (T1107)' } ), id: 'T1107', @@ -2862,7 +2898,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.fileSystemLogicalOffsetsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileSystemLogicalOffsetsDescription', { defaultMessage: 'File System Logical Offsets (T1006)' } ), id: 'T1006', @@ -2873,7 +2909,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.fileSystemPermissionsWeaknessDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileSystemPermissionsWeaknessDescription', { defaultMessage: 'File System Permissions Weakness (T1044)' } ), id: 'T1044', @@ -2884,7 +2920,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.fileAndDirectoryDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileAndDirectoryDiscoveryDescription', { defaultMessage: 'File and Directory Discovery (T1083)' } ), id: 'T1083', @@ -2895,7 +2931,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.fileAndDirectoryPermissionsModificationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileAndDirectoryPermissionsModificationDescription', { defaultMessage: 'File and Directory Permissions Modification (T1222)' } ), id: 'T1222', @@ -2906,7 +2942,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.firmwareCorruptionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.firmwareCorruptionDescription', { defaultMessage: 'Firmware Corruption (T1495)' } ), id: 'T1495', @@ -2917,7 +2953,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.forcedAuthenticationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.forcedAuthenticationDescription', { defaultMessage: 'Forced Authentication (T1187)' } ), id: 'T1187', @@ -2928,7 +2964,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.gatekeeperBypassDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatekeeperBypassDescription', { defaultMessage: 'Gatekeeper Bypass (T1144)' } ), id: 'T1144', @@ -2939,7 +2975,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription', { defaultMessage: 'Graphical User Interface (T1061)' } ), id: 'T1061', @@ -2950,7 +2986,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.groupPolicyModificationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.groupPolicyModificationDescription', { defaultMessage: 'Group Policy Modification (T1484)' } ), id: 'T1484', @@ -2961,7 +2997,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.histcontrolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.histcontrolDescription', { defaultMessage: 'HISTCONTROL (T1148)' } ), id: 'T1148', @@ -2972,7 +3008,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription', { defaultMessage: 'Hardware Additions (T1200)' } ), id: 'T1200', @@ -2983,7 +3019,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenFilesAndDirectoriesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenFilesAndDirectoriesDescription', { defaultMessage: 'Hidden Files and Directories (T1158)' } ), id: 'T1158', @@ -2994,7 +3030,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenUsersDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenUsersDescription', { defaultMessage: 'Hidden Users (T1147)' } ), id: 'T1147', @@ -3005,7 +3041,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenWindowDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenWindowDescription', { defaultMessage: 'Hidden Window (T1143)' } ), id: 'T1143', @@ -3015,9 +3051,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'hiddenWindow', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.hookingDescription', { - defaultMessage: 'Hooking (T1179)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hookingDescription', + { + defaultMessage: 'Hooking (T1179)', + } + ), id: 'T1179', name: 'Hooking', reference: 'https://attack.mitre.org/techniques/T1179', @@ -3026,7 +3065,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.hypervisorDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hypervisorDescription', { defaultMessage: 'Hypervisor (T1062)' } ), id: 'T1062', @@ -3037,7 +3076,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.imageFileExecutionOptionsInjectionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.imageFileExecutionOptionsInjectionDescription', { defaultMessage: 'Image File Execution Options Injection (T1183)' } ), id: 'T1183', @@ -3048,7 +3087,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.implantContainerImageDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.implantContainerImageDescription', { defaultMessage: 'Implant Container Image (T1525)' } ), id: 'T1525', @@ -3059,7 +3098,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorBlockingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorBlockingDescription', { defaultMessage: 'Indicator Blocking (T1054)' } ), id: 'T1054', @@ -3070,7 +3109,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorRemovalFromToolsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorRemovalFromToolsDescription', { defaultMessage: 'Indicator Removal from Tools (T1066)' } ), id: 'T1066', @@ -3081,7 +3120,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorRemovalOnHostDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorRemovalOnHostDescription', { defaultMessage: 'Indicator Removal on Host (T1070)' } ), id: 'T1070', @@ -3092,7 +3131,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.indirectCommandExecutionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indirectCommandExecutionDescription', { defaultMessage: 'Indirect Command Execution (T1202)' } ), id: 'T1202', @@ -3103,7 +3142,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.inhibitSystemRecoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inhibitSystemRecoveryDescription', { defaultMessage: 'Inhibit System Recovery (T1490)' } ), id: 'T1490', @@ -3114,7 +3153,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.inputCaptureDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inputCaptureDescription', { defaultMessage: 'Input Capture (T1056)' } ), id: 'T1056', @@ -3125,7 +3164,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.inputPromptDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inputPromptDescription', { defaultMessage: 'Input Prompt (T1141)' } ), id: 'T1141', @@ -3136,7 +3175,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.installRootCertificateDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.installRootCertificateDescription', { defaultMessage: 'Install Root Certificate (T1130)' } ), id: 'T1130', @@ -3147,7 +3186,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.installUtilDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.installUtilDescription', { defaultMessage: 'InstallUtil (T1118)' } ), id: 'T1118', @@ -3158,7 +3197,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.internalSpearphishingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.internalSpearphishingDescription', { defaultMessage: 'Internal Spearphishing (T1534)' } ), id: 'T1534', @@ -3169,7 +3208,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.kerberoastingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.kerberoastingDescription', { defaultMessage: 'Kerberoasting (T1208)' } ), id: 'T1208', @@ -3180,7 +3219,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.kernelModulesAndExtensionsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.kernelModulesAndExtensionsDescription', { defaultMessage: 'Kernel Modules and Extensions (T1215)' } ), id: 'T1215', @@ -3190,9 +3229,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'kernelModulesAndExtensions', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.keychainDescription', { - defaultMessage: 'Keychain (T1142)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.keychainDescription', + { + defaultMessage: 'Keychain (T1142)', + } + ), id: 'T1142', name: 'Keychain', reference: 'https://attack.mitre.org/techniques/T1142', @@ -3201,7 +3243,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.lcLoadDylibAdditionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lcLoadDylibAdditionDescription', { defaultMessage: 'LC_LOAD_DYLIB Addition (T1161)' } ), id: 'T1161', @@ -3212,7 +3254,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.lcMainHijackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lcMainHijackingDescription', { defaultMessage: 'LC_MAIN Hijacking (T1149)' } ), id: 'T1149', @@ -3223,7 +3265,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.llmnrNbtNsPoisoningAndRelayDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.llmnrNbtNsPoisoningAndRelayDescription', { defaultMessage: 'LLMNR/NBT-NS Poisoning and Relay (T1171)' } ), id: 'T1171', @@ -3234,7 +3276,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.lsassDriverDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lsassDriverDescription', { defaultMessage: 'LSASS Driver (T1177)' } ), id: 'T1177', @@ -3245,7 +3287,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.launchAgentDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchAgentDescription', { defaultMessage: 'Launch Agent (T1159)' } ), id: 'T1159', @@ -3256,7 +3298,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.launchDaemonDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchDaemonDescription', { defaultMessage: 'Launch Daemon (T1160)' } ), id: 'T1160', @@ -3266,9 +3308,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'launchDaemon', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.launchctlDescription', { - defaultMessage: 'Launchctl (T1152)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchctlDescription', + { + defaultMessage: 'Launchctl (T1152)', + } + ), id: 'T1152', name: 'Launchctl', reference: 'https://attack.mitre.org/techniques/T1152', @@ -3277,7 +3322,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.localJobSchedulingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.localJobSchedulingDescription', { defaultMessage: 'Local Job Scheduling (T1168)' } ), id: 'T1168', @@ -3287,9 +3332,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'localJobScheduling', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.loginItemDescription', { - defaultMessage: 'Login Item (T1162)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.loginItemDescription', + { + defaultMessage: 'Login Item (T1162)', + } + ), id: 'T1162', name: 'Login Item', reference: 'https://attack.mitre.org/techniques/T1162', @@ -3298,7 +3346,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.logonScriptsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.logonScriptsDescription', { defaultMessage: 'Logon Scripts (T1037)' } ), id: 'T1037', @@ -3309,7 +3357,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.manInTheBrowserDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.manInTheBrowserDescription', { defaultMessage: 'Man in the Browser (T1185)' } ), id: 'T1185', @@ -3320,7 +3368,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.masqueradingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.masqueradingDescription', { defaultMessage: 'Masquerading (T1036)' } ), id: 'T1036', @@ -3331,7 +3379,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.modifyExistingServiceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyExistingServiceDescription', { defaultMessage: 'Modify Existing Service (T1031)' } ), id: 'T1031', @@ -3342,7 +3390,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.modifyRegistryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyRegistryDescription', { defaultMessage: 'Modify Registry (T1112)' } ), id: 'T1112', @@ -3352,9 +3400,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'modifyRegistry', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.mshtaDescription', { - defaultMessage: 'Mshta (T1170)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.mshtaDescription', + { + defaultMessage: 'Mshta (T1170)', + } + ), id: 'T1170', name: 'Mshta', reference: 'https://attack.mitre.org/techniques/T1170', @@ -3363,7 +3414,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.multiStageChannelsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiStageChannelsDescription', { defaultMessage: 'Multi-Stage Channels (T1104)' } ), id: 'T1104', @@ -3374,7 +3425,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.multiHopProxyDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiHopProxyDescription', { defaultMessage: 'Multi-hop Proxy (T1188)' } ), id: 'T1188', @@ -3385,7 +3436,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.multibandCommunicationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multibandCommunicationDescription', { defaultMessage: 'Multiband Communication (T1026)' } ), id: 'T1026', @@ -3396,7 +3447,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.multilayerEncryptionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multilayerEncryptionDescription', { defaultMessage: 'Multilayer Encryption (T1079)' } ), id: 'T1079', @@ -3407,7 +3458,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.ntfsFileAttributesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.ntfsFileAttributesDescription', { defaultMessage: 'NTFS File Attributes (T1096)' } ), id: 'T1096', @@ -3418,7 +3469,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.netshHelperDllDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.netshHelperDllDescription', { defaultMessage: 'Netsh Helper DLL (T1128)' } ), id: 'T1128', @@ -3429,7 +3480,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.networkDenialOfServiceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkDenialOfServiceDescription', { defaultMessage: 'Network Denial of Service (T1498)' } ), id: 'T1498', @@ -3440,7 +3491,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.networkServiceScanningDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkServiceScanningDescription', { defaultMessage: 'Network Service Scanning (T1046)' } ), id: 'T1046', @@ -3451,7 +3502,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.networkShareConnectionRemovalDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkShareConnectionRemovalDescription', { defaultMessage: 'Network Share Connection Removal (T1126)' } ), id: 'T1126', @@ -3462,7 +3513,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.networkShareDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkShareDiscoveryDescription', { defaultMessage: 'Network Share Discovery (T1135)' } ), id: 'T1135', @@ -3473,7 +3524,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.networkSniffingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkSniffingDescription', { defaultMessage: 'Network Sniffing (T1040)' } ), id: 'T1040', @@ -3484,7 +3535,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.newServiceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.newServiceDescription', { defaultMessage: 'New Service (T1050)' } ), id: 'T1050', @@ -3495,7 +3546,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.obfuscatedFilesOrInformationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.obfuscatedFilesOrInformationDescription', { defaultMessage: 'Obfuscated Files or Information (T1027)' } ), id: 'T1027', @@ -3506,7 +3557,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.officeApplicationStartupDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.officeApplicationStartupDescription', { defaultMessage: 'Office Application Startup (T1137)' } ), id: 'T1137', @@ -3517,7 +3568,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.parentPidSpoofingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.parentPidSpoofingDescription', { defaultMessage: 'Parent PID Spoofing (T1502)' } ), id: 'T1502', @@ -3528,7 +3579,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.passTheHashDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passTheHashDescription', { defaultMessage: 'Pass the Hash (T1075)' } ), id: 'T1075', @@ -3539,7 +3590,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.passTheTicketDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passTheTicketDescription', { defaultMessage: 'Pass the Ticket (T1097)' } ), id: 'T1097', @@ -3550,7 +3601,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.passwordFilterDllDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passwordFilterDllDescription', { defaultMessage: 'Password Filter DLL (T1174)' } ), id: 'T1174', @@ -3561,7 +3612,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.passwordPolicyDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passwordPolicyDiscoveryDescription', { defaultMessage: 'Password Policy Discovery (T1201)' } ), id: 'T1201', @@ -3572,7 +3623,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.pathInterceptionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.pathInterceptionDescription', { defaultMessage: 'Path Interception (T1034)' } ), id: 'T1034', @@ -3583,7 +3634,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.peripheralDeviceDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.peripheralDeviceDiscoveryDescription', { defaultMessage: 'Peripheral Device Discovery (T1120)' } ), id: 'T1120', @@ -3594,7 +3645,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.permissionGroupsDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.permissionGroupsDiscoveryDescription', { defaultMessage: 'Permission Groups Discovery (T1069)' } ), id: 'T1069', @@ -3605,7 +3656,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.plistModificationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.plistModificationDescription', { defaultMessage: 'Plist Modification (T1150)' } ), id: 'T1150', @@ -3616,7 +3667,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.portKnockingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.portKnockingDescription', { defaultMessage: 'Port Knocking (T1205)' } ), id: 'T1205', @@ -3627,7 +3678,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.portMonitorsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.portMonitorsDescription', { defaultMessage: 'Port Monitors (T1013)' } ), id: 'T1013', @@ -3638,7 +3689,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.powerShellDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.powerShellDescription', { defaultMessage: 'PowerShell (T1086)' } ), id: 'T1086', @@ -3649,7 +3700,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.powerShellProfileDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.powerShellProfileDescription', { defaultMessage: 'PowerShell Profile (T1504)' } ), id: 'T1504', @@ -3660,7 +3711,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.privateKeysDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.privateKeysDescription', { defaultMessage: 'Private Keys (T1145)' } ), id: 'T1145', @@ -3671,7 +3722,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.processDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processDiscoveryDescription', { defaultMessage: 'Process Discovery (T1057)' } ), id: 'T1057', @@ -3682,7 +3733,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.processDoppelgangingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processDoppelgangingDescription', { defaultMessage: 'Process Doppelgänging (T1186)' } ), id: 'T1186', @@ -3693,7 +3744,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.processHollowingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processHollowingDescription', { defaultMessage: 'Process Hollowing (T1093)' } ), id: 'T1093', @@ -3704,7 +3755,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.processInjectionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processInjectionDescription', { defaultMessage: 'Process Injection (T1055)' } ), id: 'T1055', @@ -3715,7 +3766,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.queryRegistryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.queryRegistryDescription', { defaultMessage: 'Query Registry (T1012)' } ), id: 'T1012', @@ -3725,9 +3776,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'queryRegistry', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rcCommonDescription', { - defaultMessage: 'Rc.common (T1163)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rcCommonDescription', + { + defaultMessage: 'Rc.common (T1163)', + } + ), id: 'T1163', name: 'Rc.common', reference: 'https://attack.mitre.org/techniques/T1163', @@ -3736,7 +3790,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.reOpenedApplicationsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.reOpenedApplicationsDescription', { defaultMessage: 'Re-opened Applications (T1164)' } ), id: 'T1164', @@ -3747,7 +3801,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.redundantAccessDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.redundantAccessDescription', { defaultMessage: 'Redundant Access (T1108)' } ), id: 'T1108', @@ -3758,7 +3812,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.registryRunKeysStartupFolderDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.registryRunKeysStartupFolderDescription', { defaultMessage: 'Registry Run Keys / Startup Folder (T1060)' } ), id: 'T1060', @@ -3769,7 +3823,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.regsvcsRegasmDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.regsvcsRegasmDescription', { defaultMessage: 'Regsvcs/Regasm (T1121)' } ), id: 'T1121', @@ -3779,9 +3833,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'regsvcsRegasm', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.regsvr32Description', { - defaultMessage: 'Regsvr32 (T1117)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.regsvr32Description', + { + defaultMessage: 'Regsvr32 (T1117)', + } + ), id: 'T1117', name: 'Regsvr32', reference: 'https://attack.mitre.org/techniques/T1117', @@ -3790,7 +3847,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteAccessToolsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteAccessToolsDescription', { defaultMessage: 'Remote Access Tools (T1219)' } ), id: 'T1219', @@ -3801,7 +3858,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteDesktopProtocolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteDesktopProtocolDescription', { defaultMessage: 'Remote Desktop Protocol (T1076)' } ), id: 'T1076', @@ -3812,7 +3869,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteFileCopyDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteFileCopyDescription', { defaultMessage: 'Remote File Copy (T1105)' } ), id: 'T1105', @@ -3823,7 +3880,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteServicesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteServicesDescription', { defaultMessage: 'Remote Services (T1021)' } ), id: 'T1021', @@ -3834,7 +3891,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteSystemDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteSystemDiscoveryDescription', { defaultMessage: 'Remote System Discovery (T1018)' } ), id: 'T1018', @@ -3845,7 +3902,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.replicationThroughRemovableMediaDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.replicationThroughRemovableMediaDescription', { defaultMessage: 'Replication Through Removable Media (T1091)' } ), id: 'T1091', @@ -3856,7 +3913,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.resourceHijackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.resourceHijackingDescription', { defaultMessage: 'Resource Hijacking (T1496)' } ), id: 'T1496', @@ -3867,7 +3924,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.revertCloudInstanceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.revertCloudInstanceDescription', { defaultMessage: 'Revert Cloud Instance (T1536)' } ), id: 'T1536', @@ -3877,9 +3934,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'revertCloudInstance', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rootkitDescription', { - defaultMessage: 'Rootkit (T1014)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rootkitDescription', + { + defaultMessage: 'Rootkit (T1014)', + } + ), id: 'T1014', name: 'Rootkit', reference: 'https://attack.mitre.org/techniques/T1014', @@ -3887,9 +3947,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'rootkit', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rundll32Description', { - defaultMessage: 'Rundll32 (T1085)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rundll32Description', + { + defaultMessage: 'Rundll32 (T1085)', + } + ), id: 'T1085', name: 'Rundll32', reference: 'https://attack.mitre.org/techniques/T1085', @@ -3898,7 +3961,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.runtimeDataManipulationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.runtimeDataManipulationDescription', { defaultMessage: 'Runtime Data Manipulation (T1494)' } ), id: 'T1494', @@ -3909,7 +3972,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.sidHistoryInjectionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sidHistoryInjectionDescription', { defaultMessage: 'SID-History Injection (T1178)' } ), id: 'T1178', @@ -3920,7 +3983,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.sipAndTrustProviderHijackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sipAndTrustProviderHijackingDescription', { defaultMessage: 'SIP and Trust Provider Hijacking (T1198)' } ), id: 'T1198', @@ -3931,7 +3994,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.sshHijackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sshHijackingDescription', { defaultMessage: 'SSH Hijacking (T1184)' } ), id: 'T1184', @@ -3942,7 +4005,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.scheduledTaskDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scheduledTaskDescription', { defaultMessage: 'Scheduled Task (T1053)' } ), id: 'T1053', @@ -3953,7 +4016,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.scheduledTransferDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scheduledTransferDescription', { defaultMessage: 'Scheduled Transfer (T1029)' } ), id: 'T1029', @@ -3964,7 +4027,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.screenCaptureDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.screenCaptureDescription', { defaultMessage: 'Screen Capture (T1113)' } ), id: 'T1113', @@ -3975,7 +4038,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.screensaverDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.screensaverDescription', { defaultMessage: 'Screensaver (T1180)' } ), id: 'T1180', @@ -3985,9 +4048,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'screensaver', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.scriptingDescription', { - defaultMessage: 'Scripting (T1064)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scriptingDescription', + { + defaultMessage: 'Scripting (T1064)', + } + ), id: 'T1064', name: 'Scripting', reference: 'https://attack.mitre.org/techniques/T1064', @@ -3996,7 +4062,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.securitySoftwareDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitySoftwareDiscoveryDescription', { defaultMessage: 'Security Software Discovery (T1063)' } ), id: 'T1063', @@ -4007,7 +4073,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.securitySupportProviderDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitySupportProviderDescription', { defaultMessage: 'Security Support Provider (T1101)' } ), id: 'T1101', @@ -4018,7 +4084,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.securitydMemoryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitydMemoryDescription', { defaultMessage: 'Securityd Memory (T1167)' } ), id: 'T1167', @@ -4029,7 +4095,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.serverSoftwareComponentDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serverSoftwareComponentDescription', { defaultMessage: 'Server Software Component (T1505)' } ), id: 'T1505', @@ -4040,7 +4106,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceExecutionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceExecutionDescription', { defaultMessage: 'Service Execution (T1035)' } ), id: 'T1035', @@ -4051,7 +4117,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceRegistryPermissionsWeaknessDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceRegistryPermissionsWeaknessDescription', { defaultMessage: 'Service Registry Permissions Weakness (T1058)' } ), id: 'T1058', @@ -4062,7 +4128,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceStopDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceStopDescription', { defaultMessage: 'Service Stop (T1489)' } ), id: 'T1489', @@ -4073,7 +4139,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.setuidAndSetgidDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.setuidAndSetgidDescription', { defaultMessage: 'Setuid and Setgid (T1166)' } ), id: 'T1166', @@ -4084,7 +4150,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.sharedWebrootDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sharedWebrootDescription', { defaultMessage: 'Shared Webroot (T1051)' } ), id: 'T1051', @@ -4095,7 +4161,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.shortcutModificationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.shortcutModificationDescription', { defaultMessage: 'Shortcut Modification (T1023)' } ), id: 'T1023', @@ -4106,7 +4172,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.signedBinaryProxyExecutionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.signedBinaryProxyExecutionDescription', { defaultMessage: 'Signed Binary Proxy Execution (T1218)' } ), id: 'T1218', @@ -4117,7 +4183,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.signedScriptProxyExecutionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.signedScriptProxyExecutionDescription', { defaultMessage: 'Signed Script Proxy Execution (T1216)' } ), id: 'T1216', @@ -4128,7 +4194,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.softwareDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwareDiscoveryDescription', { defaultMessage: 'Software Discovery (T1518)' } ), id: 'T1518', @@ -4139,7 +4205,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.softwarePackingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwarePackingDescription', { defaultMessage: 'Software Packing (T1045)' } ), id: 'T1045', @@ -4149,9 +4215,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'softwarePacking', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.sourceDescription', { - defaultMessage: 'Source (T1153)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sourceDescription', + { + defaultMessage: 'Source (T1153)', + } + ), id: 'T1153', name: 'Source', reference: 'https://attack.mitre.org/techniques/T1153', @@ -4160,7 +4229,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.spaceAfterFilenameDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spaceAfterFilenameDescription', { defaultMessage: 'Space after Filename (T1151)' } ), id: 'T1151', @@ -4171,7 +4240,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingAttachmentDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingAttachmentDescription', { defaultMessage: 'Spearphishing Attachment (T1193)' } ), id: 'T1193', @@ -4182,7 +4251,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingLinkDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingLinkDescription', { defaultMessage: 'Spearphishing Link (T1192)' } ), id: 'T1192', @@ -4193,7 +4262,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingViaServiceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingViaServiceDescription', { defaultMessage: 'Spearphishing via Service (T1194)' } ), id: 'T1194', @@ -4204,7 +4273,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.standardApplicationLayerProtocolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.standardApplicationLayerProtocolDescription', { defaultMessage: 'Standard Application Layer Protocol (T1071)' } ), id: 'T1071', @@ -4215,7 +4284,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.standardCryptographicProtocolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.standardCryptographicProtocolDescription', { defaultMessage: 'Standard Cryptographic Protocol (T1032)' } ), id: 'T1032', @@ -4226,7 +4295,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.standardNonApplicationLayerProtocolDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.standardNonApplicationLayerProtocolDescription', { defaultMessage: 'Standard Non-Application Layer Protocol (T1095)' } ), id: 'T1095', @@ -4237,7 +4306,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.startupItemsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.startupItemsDescription', { defaultMessage: 'Startup Items (T1165)' } ), id: 'T1165', @@ -4248,7 +4317,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.stealApplicationAccessTokenDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealApplicationAccessTokenDescription', { defaultMessage: 'Steal Application Access Token (T1528)' } ), id: 'T1528', @@ -4259,7 +4328,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.stealWebSessionCookieDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealWebSessionCookieDescription', { defaultMessage: 'Steal Web Session Cookie (T1539)' } ), id: 'T1539', @@ -4270,7 +4339,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.storedDataManipulationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.storedDataManipulationDescription', { defaultMessage: 'Stored Data Manipulation (T1492)' } ), id: 'T1492', @@ -4280,9 +4349,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'storedDataManipulation', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.sudoDescription', { - defaultMessage: 'Sudo (T1169)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sudoDescription', + { + defaultMessage: 'Sudo (T1169)', + } + ), id: 'T1169', name: 'Sudo', reference: 'https://attack.mitre.org/techniques/T1169', @@ -4291,7 +4363,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.sudoCachingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sudoCachingDescription', { defaultMessage: 'Sudo Caching (T1206)' } ), id: 'T1206', @@ -4302,7 +4374,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.supplyChainCompromiseDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.supplyChainCompromiseDescription', { defaultMessage: 'Supply Chain Compromise (T1195)' } ), id: 'T1195', @@ -4313,7 +4385,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemFirmwareDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemFirmwareDescription', { defaultMessage: 'System Firmware (T1019)' } ), id: 'T1019', @@ -4324,7 +4396,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemInformationDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemInformationDiscoveryDescription', { defaultMessage: 'System Information Discovery (T1082)' } ), id: 'T1082', @@ -4335,7 +4407,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemNetworkConfigurationDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemNetworkConfigurationDiscoveryDescription', { defaultMessage: 'System Network Configuration Discovery (T1016)' } ), id: 'T1016', @@ -4346,7 +4418,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemNetworkConnectionsDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemNetworkConnectionsDiscoveryDescription', { defaultMessage: 'System Network Connections Discovery (T1049)' } ), id: 'T1049', @@ -4357,7 +4429,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemOwnerUserDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemOwnerUserDiscoveryDescription', { defaultMessage: 'System Owner/User Discovery (T1033)' } ), id: 'T1033', @@ -4368,7 +4440,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemServiceDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemServiceDiscoveryDescription', { defaultMessage: 'System Service Discovery (T1007)' } ), id: 'T1007', @@ -4379,7 +4451,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemShutdownRebootDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemShutdownRebootDescription', { defaultMessage: 'System Shutdown/Reboot (T1529)' } ), id: 'T1529', @@ -4390,7 +4462,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemTimeDiscoveryDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemTimeDiscoveryDescription', { defaultMessage: 'System Time Discovery (T1124)' } ), id: 'T1124', @@ -4401,7 +4473,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.systemdServiceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemdServiceDescription', { defaultMessage: 'Systemd Service (T1501)' } ), id: 'T1501', @@ -4412,7 +4484,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.taintSharedContentDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.taintSharedContentDescription', { defaultMessage: 'Taint Shared Content (T1080)' } ), id: 'T1080', @@ -4423,7 +4495,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.templateInjectionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.templateInjectionDescription', { defaultMessage: 'Template Injection (T1221)' } ), id: 'T1221', @@ -4434,7 +4506,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.thirdPartySoftwareDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.thirdPartySoftwareDescription', { defaultMessage: 'Third-party Software (T1072)' } ), id: 'T1072', @@ -4445,7 +4517,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.timeProvidersDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.timeProvidersDescription', { defaultMessage: 'Time Providers (T1209)' } ), id: 'T1209', @@ -4455,9 +4527,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'timeProviders', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.timestompDescription', { - defaultMessage: 'Timestomp (T1099)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.timestompDescription', + { + defaultMessage: 'Timestomp (T1099)', + } + ), id: 'T1099', name: 'Timestomp', reference: 'https://attack.mitre.org/techniques/T1099', @@ -4466,7 +4541,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.transferDataToCloudAccountDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.transferDataToCloudAccountDescription', { defaultMessage: 'Transfer Data to Cloud Account (T1537)' } ), id: 'T1537', @@ -4477,7 +4552,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.transmittedDataManipulationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.transmittedDataManipulationDescription', { defaultMessage: 'Transmitted Data Manipulation (T1493)' } ), id: 'T1493', @@ -4487,9 +4562,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'transmittedDataManipulation', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.trapDescription', { - defaultMessage: 'Trap (T1154)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trapDescription', + { + defaultMessage: 'Trap (T1154)', + } + ), id: 'T1154', name: 'Trap', reference: 'https://attack.mitre.org/techniques/T1154', @@ -4498,7 +4576,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.trustedDeveloperUtilitiesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trustedDeveloperUtilitiesDescription', { defaultMessage: 'Trusted Developer Utilities (T1127)' } ), id: 'T1127', @@ -4509,7 +4587,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.trustedRelationshipDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trustedRelationshipDescription', { defaultMessage: 'Trusted Relationship (T1199)' } ), id: 'T1199', @@ -4520,7 +4598,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.twoFactorAuthenticationInterceptionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.twoFactorAuthenticationInterceptionDescription', { defaultMessage: 'Two-Factor Authentication Interception (T1111)' } ), id: 'T1111', @@ -4531,7 +4609,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.uncommonlyUsedPortDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.uncommonlyUsedPortDescription', { defaultMessage: 'Uncommonly Used Port (T1065)' } ), id: 'T1065', @@ -4542,7 +4620,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.unusedUnsupportedCloudRegionsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.unusedUnsupportedCloudRegionsDescription', { defaultMessage: 'Unused/Unsupported Cloud Regions (T1535)' } ), id: 'T1535', @@ -4553,7 +4631,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.userExecutionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.userExecutionDescription', { defaultMessage: 'User Execution (T1204)' } ), id: 'T1204', @@ -4564,7 +4642,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.validAccountsDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.validAccountsDescription', { defaultMessage: 'Valid Accounts (T1078)' } ), id: 'T1078', @@ -4575,7 +4653,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.videoCaptureDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.videoCaptureDescription', { defaultMessage: 'Video Capture (T1125)' } ), id: 'T1125', @@ -4586,7 +4664,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.virtualizationSandboxEvasionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.virtualizationSandboxEvasionDescription', { defaultMessage: 'Virtualization/Sandbox Evasion (T1497)' } ), id: 'T1497', @@ -4597,7 +4675,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.webServiceDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webServiceDescription', { defaultMessage: 'Web Service (T1102)' } ), id: 'T1102', @@ -4608,7 +4686,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.webSessionCookieDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webSessionCookieDescription', { defaultMessage: 'Web Session Cookie (T1506)' } ), id: 'T1506', @@ -4618,9 +4696,12 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ value: 'webSessionCookie', }, { - label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.webShellDescription', { - defaultMessage: 'Web Shell (T1100)', - }), + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webShellDescription', + { + defaultMessage: 'Web Shell (T1100)', + } + ), id: 'T1100', name: 'Web Shell', reference: 'https://attack.mitre.org/techniques/T1100', @@ -4629,7 +4710,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsAdminSharesDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsAdminSharesDescription', { defaultMessage: 'Windows Admin Shares (T1077)' } ), id: 'T1077', @@ -4640,7 +4721,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationDescription', { defaultMessage: 'Windows Management Instrumentation (T1047)' } ), id: 'T1047', @@ -4651,7 +4732,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationEventSubscriptionDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationEventSubscriptionDescription', { defaultMessage: 'Windows Management Instrumentation Event Subscription (T1084)' } ), id: 'T1084', @@ -4662,7 +4743,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsRemoteManagementDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsRemoteManagementDescription', { defaultMessage: 'Windows Remote Management (T1028)' } ), id: 'T1028', @@ -4673,7 +4754,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.winlogonHelperDllDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.winlogonHelperDllDescription', { defaultMessage: 'Winlogon Helper DLL (T1004)' } ), id: 'T1004', @@ -4684,7 +4765,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ }, { label: i18n.translate( - 'xpack.siem.detectionEngine.mitreAttackTechniques.xslScriptProcessingDescription', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.xslScriptProcessingDescription', { defaultMessage: 'XSL Script Processing (T1220)' } ), id: 'T1220', diff --git a/x-pack/plugins/siem/public/alerts/mitre/types.ts b/x-pack/plugins/security_solution/public/alerts/mitre/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/mitre/types.ts rename to x-pack/plugins/security_solution/public/alerts/mitre/types.ts diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx new file mode 100644 index 0000000000000..e3eb4666522ad --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiSpacer } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { StickyContainer } from 'react-sticky'; +import { connect, ConnectedProps } from 'react-redux'; + +import { GlobalTime } from '../../../common/containers/global_time'; +import { + indicesExistOrDataTemporarilyUnavailable, + WithSource, +} from '../../../common/containers/source'; +import { UpdateDateRange } from '../../../common/components/charts/common'; +import { FiltersGlobal } from '../../../common/components/filters_global'; +import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; +import { SiemSearchBar } from '../../../common/components/search_bar'; +import { WrapperPage } from '../../../common/components/wrapper_page'; +import { State } from '../../../common/store'; +import { inputsSelectors } from '../../../common/store/inputs'; +import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; +import { SpyRoute } from '../../../common/utils/route/spy_routes'; +import { InputsRange } from '../../../common/store/inputs/model'; +import { useAlertInfo } from '../../components/alerts_info'; +import { AlertsTable } from '../../components/alerts_table'; +import { NoApiIntegrationKeyCallOut } from '../../components/no_api_integration_callout'; +import { NoWriteAlertsCallOut } from '../../components/no_write_alerts_callout'; +import { AlertsHistogramPanel } from '../../components/alerts_histogram_panel'; +import { alertsHistogramOptions } from '../../components/alerts_histogram_panel/config'; +import { useUserInfo } from '../../components/user_info'; +import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; +import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; +import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; +import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; +import * as i18n from './translations'; + +export const DetectionEnginePageComponent: React.FC = ({ + filters, + query, + setAbsoluteRangeDatePicker, +}) => { + const { + loading, + isSignalIndexExists, + isAuthenticated: isUserAuthenticated, + hasEncryptionKey, + canUserCRUD, + signalIndexName, + hasIndexWrite, + } = useUserInfo(); + + const [lastAlerts] = useAlertInfo({}); + + const updateDateRangeCallback = useCallback( + ({ x }) => { + if (!x) { + return; + } + const [min, max] = x; + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + + const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [ + signalIndexName, + ]); + + if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { + return ( + + + + + ); + } + if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { + return ( + + + + + ); + } + + return ( + <> + {hasEncryptionKey != null && !hasEncryptionKey && } + {hasIndexWrite != null && !hasIndexWrite && } + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + {i18n.LAST_ALERT} + {': '} + {lastAlerts} + + ) + } + title={i18n.PAGE_TITLE} + > + + {i18n.BUTTON_MANAGE_RULES} + + + + + {({ to, from, deleteQuery, setQuery }) => ( + <> + <> + + + + + + )} + + + + ) : ( + + + + + ); + }} + + + + ); +}; + +const makeMapStateToProps = () => { + const getGlobalInputs = inputsSelectors.globalSelector(); + return (state: State) => { + const globalInputs: InputsRange = getGlobalInputs(state); + const { query, filters } = globalInputs; + + return { + query, + filters, + }; + }; +}; + +const mapDispatchToProps = { + setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, +}; + +const connector = connect(makeMapStateToProps, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +export const DetectionEnginePage = connector(React.memo(DetectionEnginePageComponent)); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine_empty_page.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine_empty_page.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx similarity index 92% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx index 9632ddfeadc0d..0c58f5620964b 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx @@ -9,12 +9,13 @@ import React from 'react'; import { useKibana } from '../../../common/lib/kibana'; import { EmptyPage } from '../../../common/components/empty_page'; import * as i18n from '../../../common/translations'; +import { ADD_DATA_PATH } from '../../../../common/constants'; export const DetectionEngineEmptyPage = React.memo(() => ( > & { url: string }; + +const DetectionEngineContainerComponent: React.FC = () => ( + + + + + + + + + + + + + + + + + + ( + + )} + /> + + +); + +export const DetectionEngineContainer = React.memo(DetectionEngineContainerComponent); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/actions.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/actions.tsx new file mode 100644 index 0000000000000..d414321e4b775 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/actions.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as H from 'history'; +import React, { Dispatch } from 'react'; + +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../common/components/link_to/redirect_to_detection_engine'; +import { + deleteRules, + duplicateRules, + enableRules, + Rule, +} from '../../../../../alerts/containers/detection_engine/rules'; +import { Action } from './reducer'; + +import { + ActionToaster, + displayErrorToast, + displaySuccessToast, + errorToToaster, +} from '../../../../../common/components/toasters'; +import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../../../common/lib/telemetry'; + +import * as i18n from '../translations'; +import { bucketRulesResponse } from './helpers'; + +export const editRuleAction = (rule: Rule, history: H.History) => { + history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`); +}; + +export const duplicateRulesAction = async ( + rules: Rule[], + ruleIds: string[], + dispatch: React.Dispatch, + dispatchToaster: Dispatch +) => { + try { + dispatch({ type: 'loadingRuleIds', ids: ruleIds, actionType: 'duplicate' }); + const response = await duplicateRules({ rules }); + const { errors } = bucketRulesResponse(response); + if (errors.length > 0) { + displayErrorToast( + i18n.DUPLICATE_RULE_ERROR, + errors.map((e) => e.error.message), + dispatchToaster + ); + } else { + displaySuccessToast(i18n.SUCCESSFULLY_DUPLICATED_RULES(ruleIds.length), dispatchToaster); + } + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + } catch (error) { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + errorToToaster({ title: i18n.DUPLICATE_RULE_ERROR, error, dispatchToaster }); + } +}; + +export const exportRulesAction = (exportRuleId: string[], dispatch: React.Dispatch) => { + dispatch({ type: 'exportRuleIds', ids: exportRuleId }); +}; + +export const deleteRulesAction = async ( + ruleIds: string[], + dispatch: React.Dispatch, + dispatchToaster: Dispatch, + onRuleDeleted?: () => void +) => { + try { + dispatch({ type: 'loadingRuleIds', ids: ruleIds, actionType: 'delete' }); + const response = await deleteRules({ ids: ruleIds }); + const { errors } = bucketRulesResponse(response); + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + if (errors.length > 0) { + displayErrorToast( + i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ruleIds.length), + errors.map((e) => e.error.message), + dispatchToaster + ); + } else if (onRuleDeleted) { + onRuleDeleted(); + } + } catch (error) { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + errorToToaster({ + title: i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ruleIds.length), + error, + dispatchToaster, + }); + } +}; + +export const enableRulesAction = async ( + ids: string[], + enabled: boolean, + dispatch: React.Dispatch, + dispatchToaster: Dispatch +) => { + const errorTitle = enabled + ? i18n.BATCH_ACTION_ACTIVATE_SELECTED_ERROR(ids.length) + : i18n.BATCH_ACTION_DEACTIVATE_SELECTED_ERROR(ids.length); + + try { + dispatch({ type: 'loadingRuleIds', ids, actionType: enabled ? 'enable' : 'disable' }); + + const response = await enableRules({ ids, enabled }); + const { rules, errors } = bucketRulesResponse(response); + + dispatch({ type: 'updateRules', rules }); + + if (errors.length > 0) { + displayErrorToast( + errorTitle, + errors.map((e) => e.error.message), + dispatchToaster + ); + } + + if (rules.some((rule) => rule.immutable)) { + track( + METRIC_TYPE.COUNT, + enabled ? TELEMETRY_EVENT.SIEM_RULE_ENABLED : TELEMETRY_EVENT.SIEM_RULE_DISABLED + ); + } + if (rules.some((rule) => !rule.immutable)) { + track( + METRIC_TYPE.COUNT, + enabled ? TELEMETRY_EVENT.CUSTOM_RULE_ENABLED : TELEMETRY_EVENT.CUSTOM_RULE_DISABLED + ); + } + } catch (e) { + displayErrorToast(errorTitle, [e.message], dispatchToaster); + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + } +}; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/batch_actions.tsx similarity index 86% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/batch_actions.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/batch_actions.tsx index 769839a62091b..2c94588ce128a 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/batch_actions.tsx @@ -40,14 +40,14 @@ export const getBatchItems = ({ selectedRuleIds, }: GetBatchItems) => { const containsEnabled = selectedRuleIds.some( - id => rules.find(r => r.id === id)?.enabled ?? false + (id) => rules.find((r) => r.id === id)?.enabled ?? false ); const containsDisabled = selectedRuleIds.some( - id => !rules.find(r => r.id === id)?.enabled ?? false + (id) => !rules.find((r) => r.id === id)?.enabled ?? false ); - const containsLoading = selectedRuleIds.some(id => loadingRuleIds.includes(id)); + const containsLoading = selectedRuleIds.some((id) => loadingRuleIds.includes(id)); const containsImmutable = selectedRuleIds.some( - id => rules.find(r => r.id === id)?.immutable ?? false + (id) => rules.find((r) => r.id === id)?.immutable ?? false ); return [ @@ -58,11 +58,11 @@ export const getBatchItems = ({ onClick={async () => { closePopover(); const deactivatedIds = selectedRuleIds.filter( - id => !rules.find(r => r.id === id)?.enabled ?? false + (id) => !rules.find((r) => r.id === id)?.enabled ?? false ); const deactivatedIdsNoML = deactivatedIds.filter( - id => rules.find(r => r.id === id)?.type !== 'machine_learning' ?? false + (id) => rules.find((r) => r.id === id)?.type !== 'machine_learning' ?? false ); const mlRuleCount = deactivatedIds.length - deactivatedIdsNoML.length; @@ -87,7 +87,7 @@ export const getBatchItems = ({ onClick={async () => { closePopover(); const activatedIds = selectedRuleIds.filter( - id => rules.find(r => r.id === id)?.enabled ?? false + (id) => rules.find((r) => r.id === id)?.enabled ?? false ); await enableRulesAction(activatedIds, false, dispatch, dispatchToaster); }} @@ -101,7 +101,7 @@ export const getBatchItems = ({ onClick={() => { closePopover(); exportRulesAction( - rules.filter(r => selectedRuleIds.includes(r.id)).map(r => r.rule_id), + rules.filter((r) => selectedRuleIds.includes(r.id)).map((r) => r.rule_id), dispatch ); }} @@ -115,7 +115,7 @@ export const getBatchItems = ({ onClick={async () => { closePopover(); await duplicateRulesAction( - rules.filter(r => selectedRuleIds.includes(r.id)), + rules.filter((r) => selectedRuleIds.includes(r.id)), selectedRuleIds, dispatch, dispatchToaster diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.test.tsx new file mode 100644 index 0000000000000..4a1e8de56d217 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uuid from 'uuid'; +import { createMemoryHistory } from 'history'; + +const history = createMemoryHistory(); + +import { mockRule } from './__mocks__/mock'; +import { getActions } from './columns'; + +jest.mock('./actions', () => ({ + duplicateRulesAction: jest.fn(), + deleteRulesAction: jest.fn(), +})); + +import { duplicateRulesAction, deleteRulesAction } from './actions'; + +describe('AllRulesTable Columns', () => { + describe('getActions', () => { + const rule = mockRule(uuid.v4()); + let results: string[] = []; + const dispatch = jest.fn(); + const dispatchToaster = jest.fn(); + const reFetchRules = jest.fn(); + + beforeEach(() => { + results = []; + + reFetchRules.mockImplementation(() => { + results.push('reFetchRules'); + Promise.resolve(); + }); + }); + + test('duplicate rule onClick should call refetch after the rule is duplicated', async () => { + (duplicateRulesAction as jest.Mock).mockImplementation( + () => + new Promise((resolve) => + setTimeout(() => { + results.push('duplicateRulesAction'); + resolve(); + }, 500) + ) + ); + + const duplicateRulesActionObject = getActions( + dispatch, + dispatchToaster, + history, + reFetchRules + )[1]; + await duplicateRulesActionObject.onClick(rule); + expect(results).toEqual(['duplicateRulesAction', 'reFetchRules']); + }); + + test('delete rule onClick should call refetch after the rule is deleted', async () => { + (deleteRulesAction as jest.Mock).mockImplementation( + () => + new Promise((resolve) => + setTimeout(() => { + results.push('deleteRulesAction'); + resolve(); + }, 500) + ) + ); + + const deleteRulesActionObject = getActions( + dispatch, + dispatchToaster, + history, + reFetchRules + )[3]; + await deleteRulesActionObject.onClick(rule); + expect(results).toEqual(['deleteRulesAction', 'reFetchRules']); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.tsx new file mode 100644 index 0000000000000..cf8a3fdb6f1e6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.tsx @@ -0,0 +1,333 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable react/display-name */ + +import { + EuiBadge, + EuiLink, + EuiBasicTableColumn, + EuiTableActionsColumnType, + EuiText, + EuiHealth, + EuiToolTip, +} from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import * as H from 'history'; +import React, { Dispatch } from 'react'; + +import { isMlRule } from '../../../../../../common/machine_learning/helpers'; +import { Rule, RuleStatus } from '../../../../../alerts/containers/detection_engine/rules'; +import { getEmptyTagValue } from '../../../../../common/components/empty_value'; +import { FormattedDate } from '../../../../../common/components/formatted_date'; +import { getRuleDetailsUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine'; +import { ActionToaster } from '../../../../../common/components/toasters'; +import { TruncatableText } from '../../../../../common/components/truncatable_text'; +import { getStatusColor } from '../../../../components/rules/rule_status/helpers'; +import { RuleSwitch } from '../../../../components/rules/rule_switch'; +import { SeverityBadge } from '../../../../components/rules/severity_badge'; +import * as i18n from '../translations'; +import { + deleteRulesAction, + duplicateRulesAction, + editRuleAction, + exportRulesAction, +} from './actions'; +import { Action } from './reducer'; +import { LocalizedDateTooltip } from '../../../../../common/components/localized_date_tooltip'; +import * as detectionI18n from '../../translations'; + +export const getActions = ( + dispatch: React.Dispatch, + dispatchToaster: Dispatch, + history: H.History, + reFetchRules: (refreshPrePackagedRule?: boolean) => void +) => [ + { + description: i18n.EDIT_RULE_SETTINGS, + icon: 'controlsHorizontal', + name: i18n.EDIT_RULE_SETTINGS, + onClick: (rowItem: Rule) => editRuleAction(rowItem, history), + }, + { + description: i18n.DUPLICATE_RULE, + icon: 'copy', + name: i18n.DUPLICATE_RULE, + onClick: async (rowItem: Rule) => { + await duplicateRulesAction([rowItem], [rowItem.id], dispatch, dispatchToaster); + await reFetchRules(true); + }, + }, + { + 'data-test-subj': 'exportRuleAction', + description: i18n.EXPORT_RULE, + icon: 'exportAction', + name: i18n.EXPORT_RULE, + onClick: (rowItem: Rule) => exportRulesAction([rowItem.rule_id], dispatch), + enabled: (rowItem: Rule) => !rowItem.immutable, + }, + { + 'data-test-subj': 'deleteRuleAction', + description: i18n.DELETE_RULE, + icon: 'trash', + name: i18n.DELETE_RULE, + onClick: async (rowItem: Rule) => { + await deleteRulesAction([rowItem.id], dispatch, dispatchToaster); + await reFetchRules(true); + }, + }, +]; + +export type RuleStatusRowItemType = RuleStatus & { + name: string; + id: string; +}; +export type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType; +export type RulesStatusesColumns = EuiBasicTableColumn; + +interface GetColumns { + dispatch: React.Dispatch; + dispatchToaster: Dispatch; + history: H.History; + hasMlPermissions: boolean; + hasNoPermissions: boolean; + loadingRuleIds: string[]; + reFetchRules: (refreshPrePackagedRule?: boolean) => void; +} + +// Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? +export const getColumns = ({ + dispatch, + dispatchToaster, + history, + hasMlPermissions, + hasNoPermissions, + loadingRuleIds, + reFetchRules, +}: GetColumns): RulesColumns[] => { + const cols: RulesColumns[] = [ + { + field: 'name', + name: i18n.COLUMN_RULE, + render: (value: Rule['name'], item: Rule) => ( + + {value} + + ), + truncateText: true, + width: '24%', + }, + { + field: 'risk_score', + name: i18n.COLUMN_RISK_SCORE, + render: (value: Rule['risk_score']) => ( + + {value} + + ), + truncateText: true, + width: '14%', + }, + { + field: 'severity', + name: i18n.COLUMN_SEVERITY, + render: (value: Rule['severity']) => , + truncateText: true, + width: '16%', + }, + { + field: 'status_date', + name: i18n.COLUMN_LAST_COMPLETE_RUN, + render: (value: Rule['status_date']) => { + return value == null ? ( + getEmptyTagValue() + ) : ( + + + + ); + }, + truncateText: true, + width: '20%', + }, + { + field: 'status', + name: i18n.COLUMN_LAST_RESPONSE, + render: (value: Rule['status']) => { + return ( + <> + + {value ?? getEmptyTagValue()} + + + ); + }, + width: '16%', + truncateText: true, + }, + { + field: 'tags', + name: i18n.COLUMN_TAGS, + render: (value: Rule['tags']) => ( + + {value.map((tag, i) => ( + + {tag} + + ))} + + ), + truncateText: true, + width: '20%', + }, + { + align: 'center', + field: 'enabled', + name: i18n.COLUMN_ACTIVATE, + render: (value: Rule['enabled'], item: Rule) => ( + + + + ), + sortable: true, + width: '95px', + }, + ]; + const actions: RulesColumns[] = [ + { + actions: getActions(dispatch, dispatchToaster, history, reFetchRules), + width: '40px', + } as EuiTableActionsColumnType, + ]; + + return hasNoPermissions ? cols : [...cols, ...actions]; +}; + +export const getMonitoringColumns = (): RulesStatusesColumns[] => { + const cols: RulesStatusesColumns[] = [ + { + field: 'name', + name: i18n.COLUMN_RULE, + render: (value: RuleStatus['current_status']['status'], item: RuleStatusRowItemType) => { + return ( + + {value} + + ); + }, + truncateText: true, + width: '24%', + }, + { + field: 'current_status.bulk_create_time_durations', + name: i18n.COLUMN_INDEXING_TIMES, + render: (value: RuleStatus['current_status']['bulk_create_time_durations']) => ( + + {value != null && value.length > 0 + ? Math.max(...value?.map((item) => Number.parseFloat(item))) + : getEmptyTagValue()} + + ), + truncateText: true, + width: '14%', + }, + { + field: 'current_status.search_after_time_durations', + name: i18n.COLUMN_QUERY_TIMES, + render: (value: RuleStatus['current_status']['search_after_time_durations']) => ( + + {value != null && value.length > 0 + ? Math.max(...value?.map((item) => Number.parseFloat(item))) + : getEmptyTagValue()} + + ), + truncateText: true, + width: '14%', + }, + { + field: 'current_status.gap', + name: i18n.COLUMN_GAP, + render: (value: RuleStatus['current_status']['gap']) => ( + + {value ?? getEmptyTagValue()} + + ), + truncateText: true, + width: '14%', + }, + { + field: 'current_status.last_look_back_date', + name: i18n.COLUMN_LAST_LOOKBACK_DATE, + render: (value: RuleStatus['current_status']['last_look_back_date']) => { + return value == null ? ( + getEmptyTagValue() + ) : ( + + ); + }, + truncateText: true, + width: '16%', + }, + { + field: 'current_status.status_date', + name: i18n.COLUMN_LAST_COMPLETE_RUN, + render: (value: RuleStatus['current_status']['status_date']) => { + return value == null ? ( + getEmptyTagValue() + ) : ( + + + + ); + }, + truncateText: true, + width: '20%', + }, + { + field: 'current_status.status', + name: i18n.COLUMN_LAST_RESPONSE, + render: (value: RuleStatus['current_status']['status']) => { + return ( + <> + + {value ?? getEmptyTagValue()} + + + ); + }, + width: '16%', + truncateText: true, + }, + { + field: 'activate', + name: i18n.COLUMN_ACTIVATE, + render: (value: Rule['enabled']) => ( + + {value ? i18n.ACTIVE : i18n.INACTIVE} + + ), + width: '95px', + }, + ]; + + return cols; +}; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/helpers.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/helpers.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/helpers.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/helpers.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/helpers.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/helpers.ts rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/helpers.ts diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.tsx new file mode 100644 index 0000000000000..9e61ec0eb3bcf --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.tsx @@ -0,0 +1,423 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBasicTable, + EuiContextMenuPanel, + EuiLoadingContent, + EuiSpacer, + EuiTab, + EuiTabs, +} from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import uuid from 'uuid'; + +import { + useRules, + useRulesStatuses, + CreatePreBuiltRules, + FilterOptions, + Rule, + PaginationOptions, + exportRules, +} from '../../../../../alerts/containers/detection_engine/rules'; +import { HeaderSection } from '../../../../../common/components/header_section'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../../../common/components/utility_bar'; +import { useStateToaster } from '../../../../../common/components/toasters'; +import { Loader } from '../../../../../common/components/loader'; +import { Panel } from '../../../../../common/components/panel'; +import { PrePackagedRulesPrompt } from '../../../../components/rules/pre_packaged_rules/load_empty_prompt'; +import { GenericDownloader } from '../../../../../common/components/generic_downloader'; +import { AllRulesTables, SortingType } from '../../../../components/rules/all_rules_tables'; +import { getPrePackagedRuleStatus } from '../helpers'; +import * as i18n from '../translations'; +import { EuiBasicTableOnChange } from '../types'; +import { getBatchItems } from './batch_actions'; +import { getColumns, getMonitoringColumns } from './columns'; +import { showRulesTable } from './helpers'; +import { allRulesReducer, State } from './reducer'; +import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; +import { useMlCapabilities } from '../../../../../common/components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; + +const SORT_FIELD = 'enabled'; +const initialState: State = { + exportRuleIds: [], + filterOptions: { + filter: '', + sortField: SORT_FIELD, + sortOrder: 'desc', + }, + loadingRuleIds: [], + loadingRulesAction: null, + pagination: { + page: 1, + perPage: 20, + total: 0, + }, + rules: [], + selectedRuleIds: [], +}; + +interface AllRulesProps { + createPrePackagedRules: CreatePreBuiltRules | null; + hasNoPermissions: boolean; + loading: boolean; + loadingCreatePrePackagedRules: boolean; + refetchPrePackagedRulesStatus: () => void; + rulesCustomInstalled: number | null; + rulesInstalled: number | null; + rulesNotInstalled: number | null; + rulesNotUpdated: number | null; + setRefreshRulesData: (refreshRule: (refreshPrePackagedRule?: boolean) => void) => void; +} + +export enum AllRulesTabs { + rules = 'rules', + monitoring = 'monitoring', +} + +const allRulesTabs = [ + { + id: AllRulesTabs.rules, + name: i18n.RULES_TAB, + disabled: false, + }, + { + id: AllRulesTabs.monitoring, + name: i18n.MONITORING_TAB, + disabled: false, + }, +]; + +/** + * Table Component for displaying all Rules for a given cluster. Provides the ability to filter + * by name, sort by enabled, and perform the following actions: + * * Enable/Disable + * * Duplicate + * * Delete + * * Import/Export + */ +export const AllRules = React.memo( + ({ + createPrePackagedRules, + hasNoPermissions, + loading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, + rulesCustomInstalled, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + setRefreshRulesData, + }) => { + const [initLoading, setInitLoading] = useState(true); + const tableRef = useRef(); + const [ + { + exportRuleIds, + filterOptions, + loadingRuleIds, + loadingRulesAction, + pagination, + rules, + selectedRuleIds, + }, + dispatch, + ] = useReducer(allRulesReducer(tableRef), initialState); + const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); + const history = useHistory(); + const [, dispatchToaster] = useStateToaster(); + const mlCapabilities = useMlCapabilities(); + const [allRulesTab, setAllRulesTab] = useState(AllRulesTabs.rules); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); + + const setRules = useCallback((newRules: Rule[], newPagination: Partial) => { + dispatch({ + type: 'setRules', + rules: newRules, + pagination: newPagination, + }); + }, []); + + const [isLoadingRules, , reFetchRulesData] = useRules({ + pagination, + filterOptions, + refetchPrePackagedRulesStatus, + dispatchRulesInReducer: setRules, + }); + + const sorting = useMemo( + (): SortingType => ({ sort: { field: 'enabled', direction: filterOptions.sortOrder } }), + [filterOptions.sortOrder] + ); + + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + const getBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [ + dispatch, + dispatchToaster, + hasMlPermissions, + loadingRuleIds, + reFetchRulesData, + rules, + selectedRuleIds, + ] + ); + + const paginationMemo = useMemo( + () => ({ + pageIndex: pagination.page - 1, + pageSize: pagination.perPage, + totalItemCount: pagination.total, + pageSizeOptions: [5, 10, 20, 50, 100, 200, 300], + }), + [pagination] + ); + + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + sortField: SORT_FIELD, // Only enabled is supported for sorting currently + sortOrder: sort?.direction ?? 'desc', + }, + pagination: { page: page.index + 1, perPage: page.size }, + }); + }, + [dispatch] + ); + + const rulesColumns = useMemo(() => { + return getColumns({ + dispatch, + dispatchToaster, + history, + hasMlPermissions, + hasNoPermissions, + loadingRuleIds: + loadingRulesAction != null && + (loadingRulesAction === 'enable' || loadingRulesAction === 'disable') + ? loadingRuleIds + : [], + reFetchRules: reFetchRulesData, + }); + }, [ + dispatch, + dispatchToaster, + hasMlPermissions, + history, + loadingRuleIds, + loadingRulesAction, + reFetchRulesData, + ]); + + const monitoringColumns = useMemo(() => getMonitoringColumns(), []); + + useEffect(() => { + if (reFetchRulesData != null) { + setRefreshRulesData(reFetchRulesData); + } + }, [reFetchRulesData, setRefreshRulesData]); + + useEffect(() => { + if (initLoading && !loading && !isLoadingRules && !isLoadingRulesStatuses) { + setInitLoading(false); + } + }, [initLoading, loading, isLoadingRules, isLoadingRulesStatuses]); + + const handleCreatePrePackagedRules = useCallback(async () => { + if (createPrePackagedRules != null && reFetchRulesData != null) { + await createPrePackagedRules(); + reFetchRulesData(true); + } + }, [createPrePackagedRules, reFetchRulesData]); + + const euiBasicTableSelectionProps = useMemo( + () => ({ + selectable: (item: Rule) => !loadingRuleIds.includes(item.id), + onSelectionChange: (selected: Rule[]) => + dispatch({ type: 'selectedRuleIds', ids: selected.map((r) => r.id) }), + }), + [loadingRuleIds] + ); + + const onFilterChangedCallback = useCallback((newFilterOptions: Partial) => { + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + ...newFilterOptions, + }, + pagination: { page: 1 }, + }); + }, []); + + const isLoadingAnActionOnRule = useMemo(() => { + if ( + loadingRuleIds.length > 0 && + (loadingRulesAction === 'disable' || loadingRulesAction === 'enable') + ) { + return false; + } else if (loadingRuleIds.length > 0) { + return true; + } + return false; + }, [loadingRuleIds, loadingRulesAction]); + + const tabs = useMemo( + () => ( + + {allRulesTabs.map((tab) => ( + setAllRulesTab(tab.id)} + isSelected={tab.id === allRulesTab} + disabled={tab.disabled} + key={tab.id} + > + {tab.name} + + ))} + + ), + [allRulesTabs, allRulesTab, setAllRulesTab] + ); + + return ( + <> + { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + dispatchToaster({ + type: 'addToaster', + toast: { + id: uuid.v4(), + title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), + color: 'success', + iconType: 'check', + }, + }); + }} + exportSelectedData={exportRules} + /> + + {tabs} + + + + <> + + + + + {(loading || isLoadingRules || isLoadingAnActionOnRule || isLoadingRulesStatuses) && + !initLoading && ( + + )} + {rulesCustomInstalled != null && + rulesCustomInstalled === 0 && + prePackagedRuleStatus === 'ruleNotInstalled' && + !initLoading && ( + + )} + {initLoading && ( + + )} + {showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !initLoading && ( + <> + + + + + {i18n.SHOWING_RULES(pagination.total ?? 0)} + + + + + {i18n.SELECTED_RULES(selectedRuleIds.length)} + {!hasNoPermissions && ( + + {i18n.BATCH_ACTIONS} + + )} + reFetchRulesData(true)} + > + {i18n.REFRESH} + + + + + + + )} + + + + ); + } +); + +AllRules.displayName = 'AllRules'; diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/reducer.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/reducer.ts new file mode 100644 index 0000000000000..3fe17fcaeeb9c --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/reducer.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiBasicTable } from '@elastic/eui'; +import { + FilterOptions, + PaginationOptions, + Rule, +} from '../../../../../alerts/containers/detection_engine/rules'; + +type LoadingRuleAction = 'duplicate' | 'enable' | 'disable' | 'export' | 'delete' | null; +export interface State { + exportRuleIds: string[]; + filterOptions: FilterOptions; + loadingRuleIds: string[]; + loadingRulesAction: LoadingRuleAction; + pagination: PaginationOptions; + rules: Rule[]; + selectedRuleIds: string[]; +} + +export type Action = + | { type: 'exportRuleIds'; ids: string[] } + | { type: 'loadingRuleIds'; ids: string[]; actionType: LoadingRuleAction } + | { type: 'selectedRuleIds'; ids: string[] } + | { type: 'setRules'; rules: Rule[]; pagination: Partial } + | { type: 'updateRules'; rules: Rule[] } + | { + type: 'updateFilterOptions'; + filterOptions: Partial; + pagination: Partial; + } + | { type: 'failure' }; + +export const allRulesReducer = ( + tableRef: React.MutableRefObject | undefined> +) => (state: State, action: Action): State => { + switch (action.type) { + case 'exportRuleIds': { + return { + ...state, + loadingRuleIds: action.ids, + loadingRulesAction: 'export', + exportRuleIds: action.ids, + }; + } + case 'loadingRuleIds': { + return { + ...state, + loadingRuleIds: action.actionType == null ? [] : [...state.loadingRuleIds, ...action.ids], + loadingRulesAction: action.actionType, + }; + } + case 'selectedRuleIds': { + return { + ...state, + selectedRuleIds: action.ids, + }; + } + case 'setRules': { + if ( + tableRef != null && + tableRef.current != null && + tableRef.current.changeSelection != null + ) { + // for future devs: eui basic table is not giving us a prop to set the value, so + // we are using the ref in setTimeout to reset on the next loop so that we + // do not get a warning telling us we are trying to update during a render + window.setTimeout(() => tableRef?.current?.changeSelection([]), 0); + } + + return { + ...state, + rules: action.rules, + selectedRuleIds: [], + loadingRuleIds: [], + loadingRulesAction: null, + pagination: { + ...state.pagination, + ...action.pagination, + }, + }; + } + case 'updateRules': { + if (state.rules != null) { + const ruleIds = state.rules.map((r) => r.id); + const updatedRules = action.rules.reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.id)) { + newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); + const updatedRuleIds = action.rules.map((r) => r.id); + const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); + return { + ...state, + rules: updatedRules, + loadingRuleIds: newLoadingRuleIds, + loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, + }; + } + return state; + } + case 'updateFilterOptions': { + return { + ...state, + filterOptions: { + ...state.filterOptions, + ...action.filterOptions, + }, + pagination: { + ...state.pagination, + ...action.pagination, + }, + }; + } + case 'failure': { + return { + ...state, + rules: [], + }; + } + default: + return state; + } +}; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx similarity index 97% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx index de4804f37f1bc..2bc19ae45a078 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx @@ -52,7 +52,7 @@ const RulesTableFiltersComponent = ({ onFilterChanged({ filter, showCustomRules, showElasticRules, tags: selectedTags }); }, [filter, selectedTags, showCustomRules, showElasticRules, onFilterChanged]); - const handleOnSearch = useCallback(filterString => setFilter(filterString.trim()), [setFilter]); + const handleOnSearch = useCallback((filterString) => setFilter(filterString.trim()), [setFilter]); const handleElasticRulesClick = useCallback(() => { setShowElasticRules(!showElasticRules); @@ -65,7 +65,7 @@ const RulesTableFiltersComponent = ({ }, [setShowElasticRules, showCustomRules, setShowCustomRules]); const handleSelectedTags = useCallback( - newTags => { + (newTags) => { if (!isEqual(newTags, selectedTags)) { setSelectedTags(newTags); } diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.test.ts new file mode 100644 index 0000000000000..d9cbcfc8979a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.test.ts @@ -0,0 +1,730 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { NewRule } from '../../../../../alerts/containers/detection_engine/rules'; +import { + DefineStepRuleJson, + ScheduleStepRuleJson, + AboutStepRuleJson, + ActionsStepRuleJson, + AboutStepRule, + ActionsStepRule, + ScheduleStepRule, + DefineStepRule, +} from '../types'; +import { + getTimeTypeValue, + formatDefineStepData, + formatScheduleStepData, + formatAboutStepData, + formatActionsStepData, + formatRule, + filterRuleFieldsForType, +} from './helpers'; +import { + mockDefineStepRule, + mockQueryBar, + mockScheduleStepRule, + mockAboutStepRule, + mockActionsStepRule, +} from '../all/__mocks__/mock'; + +describe('helpers', () => { + describe('getTimeTypeValue', () => { + test('returns timeObj with value 0 if no time value found', () => { + const result = getTimeTypeValue('m'); + + expect(result).toEqual({ unit: 'm', value: 0 }); + }); + + test('returns timeObj with unit set to empty string if no expected time type found', () => { + const result = getTimeTypeValue('5l'); + + expect(result).toEqual({ unit: '', value: 5 }); + }); + + test('returns timeObj with unit of s and value 5 when time is 5s ', () => { + const result = getTimeTypeValue('5s'); + + expect(result).toEqual({ unit: 's', value: 5 }); + }); + + test('returns timeObj with unit of m and value 5 when time is 5m ', () => { + const result = getTimeTypeValue('5m'); + + expect(result).toEqual({ unit: 'm', value: 5 }); + }); + + test('returns timeObj with unit of h and value 5 when time is 5h ', () => { + const result = getTimeTypeValue('5h'); + + expect(result).toEqual({ unit: 'h', value: 5 }); + }); + + test('returns timeObj with value of 5 when time is float like 5.6m ', () => { + const result = getTimeTypeValue('5m'); + + expect(result).toEqual({ unit: 'm', value: 5 }); + }); + + test('returns timeObj with value of 0 and unit of "" if random string passed in', () => { + const result = getTimeTypeValue('random'); + + expect(result).toEqual({ unit: '', value: 0 }); + }); + }); + + describe('formatDefineStepData', () => { + let mockData: DefineStepRule; + + beforeEach(() => { + mockData = mockDefineStepRule(); + }); + + test('returns formatted object as DefineStepRuleJson', () => { + const result: DefineStepRuleJson = formatDefineStepData(mockData); + const expected = { + language: 'kuery', + filters: mockQueryBar.filters, + query: 'test query', + saved_id: 'test123', + index: ['filebeat-'], + type: 'saved_query', + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: 'Titled timeline', + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with no saved_id if no savedId provided', () => { + const mockStepData = { + ...mockData, + queryBar: { + ...mockData.queryBar, + saved_id: '', + }, + }; + const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + const expected = { + language: 'kuery', + filters: mockQueryBar.filters, + query: 'test query', + index: ['filebeat-'], + saved_id: '', + type: 'query', + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: 'Titled timeline', + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object without timeline_id and timeline_title if timeline.id is null', () => { + const mockStepData = { + ...mockData, + }; + delete mockStepData.timeline.id; + + const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + + const expected = { + language: 'kuery', + filters: mockQueryBar.filters, + query: 'test query', + index: ['filebeat-'], + saved_id: 'test123', + type: 'saved_query', + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with timeline_id and timeline_title if timeline.id is "', () => { + const mockStepData = { + ...mockData, + timeline: { + ...mockData.timeline, + id: '', + }, + }; + const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + + const expected = { + language: 'kuery', + filters: mockQueryBar.filters, + query: 'test query', + index: ['filebeat-'], + saved_id: 'test123', + type: 'saved_query', + timeline_id: '', + timeline_title: 'Titled timeline', + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object without timeline_id and timeline_title if timeline.title is null', () => { + const mockStepData = { + ...mockData, + timeline: { + ...mockData.timeline, + id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + }, + }; + delete mockStepData.timeline.title; + const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + + const expected = { + language: 'kuery', + filters: mockQueryBar.filters, + query: 'test query', + index: ['filebeat-'], + saved_id: 'test123', + type: 'saved_query', + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with timeline_id and timeline_title if timeline.title is "', () => { + const mockStepData = { + ...mockData, + timeline: { + ...mockData.timeline, + title: '', + }, + }; + const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + + const expected = { + language: 'kuery', + filters: mockQueryBar.filters, + query: 'test query', + index: ['filebeat-'], + saved_id: 'test123', + type: 'saved_query', + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: '', + }; + + expect(result).toEqual(expected); + }); + + test('returns ML fields if type is machine_learning', () => { + const mockStepData: DefineStepRule = { + ...mockData, + ruleType: 'machine_learning', + anomalyThreshold: 44, + machineLearningJobId: 'some_jobert_id', + }; + const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + + const expected = { + type: 'machine_learning', + anomaly_threshold: 44, + machine_learning_job_id: 'some_jobert_id', + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: 'Titled timeline', + }; + + expect(result).toEqual(expected); + }); + }); + + describe('formatScheduleStepData', () => { + let mockData: ScheduleStepRule; + + beforeEach(() => { + mockData = mockScheduleStepRule(); + }); + + test('returns formatted object as ScheduleStepRuleJson', () => { + const result: ScheduleStepRuleJson = formatScheduleStepData(mockData); + const expected = { + from: 'now-660s', + to: 'now', + interval: '5m', + meta: { + from: '6m', + }, + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with "to" as "now" if "to" not supplied', () => { + const mockStepData = { + ...mockData, + }; + delete mockStepData.to; + const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); + const expected = { + from: 'now-660s', + to: 'now', + interval: '5m', + meta: { + from: '6m', + }, + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with "to" as "now" if "to" random string', () => { + const mockStepData = { + ...mockData, + to: 'random', + }; + const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); + const expected = { + from: 'now-660s', + to: 'now', + interval: '5m', + meta: { + from: '6m', + }, + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object if "from" random string', () => { + const mockStepData = { + ...mockData, + from: 'random', + }; + const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); + const expected = { + from: 'now-300s', + to: 'now', + interval: '5m', + meta: { + from: 'random', + }, + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object if "interval" random string', () => { + const mockStepData = { + ...mockData, + interval: 'random', + }; + const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); + const expected = { + from: 'now-360s', + to: 'now', + interval: 'random', + meta: { + from: '6m', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('formatAboutStepData', () => { + let mockData: AboutStepRule; + + beforeEach(() => { + mockData = mockAboutStepRule(); + }); + + test('returns formatted object as AboutStepRuleJson', () => { + const result: AboutStepRuleJson = formatAboutStepData(mockData); + const expected = { + description: '24/7', + false_positives: ['test'], + name: 'Query with rule-id', + note: '# this is some markdown documentation', + references: ['www.test.co'], + risk_score: 21, + severity: 'low', + tags: ['tag1', 'tag2'], + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: '1234', + name: 'tactic1', + reference: 'reference1', + }, + technique: [ + { + id: '456', + name: 'technique1', + reference: 'technique reference', + }, + ], + }, + ], + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with empty falsePositive and references filtered out', () => { + const mockStepData = { + ...mockData, + falsePositives: ['', 'test', ''], + references: ['www.test.co', ''], + }; + const result: AboutStepRuleJson = formatAboutStepData(mockStepData); + const expected = { + description: '24/7', + false_positives: ['test'], + name: 'Query with rule-id', + note: '# this is some markdown documentation', + references: ['www.test.co'], + risk_score: 21, + severity: 'low', + tags: ['tag1', 'tag2'], + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: '1234', + name: 'tactic1', + reference: 'reference1', + }, + technique: [ + { + id: '456', + name: 'technique1', + reference: 'technique reference', + }, + ], + }, + ], + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object without note if note is empty string', () => { + const mockStepData = { + ...mockData, + note: '', + }; + const result: AboutStepRuleJson = formatAboutStepData(mockStepData); + const expected = { + description: '24/7', + false_positives: ['test'], + name: 'Query with rule-id', + references: ['www.test.co'], + risk_score: 21, + severity: 'low', + tags: ['tag1', 'tag2'], + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: '1234', + name: 'tactic1', + reference: 'reference1', + }, + technique: [ + { + id: '456', + name: 'technique1', + reference: 'technique reference', + }, + ], + }, + ], + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with threats filtered out where tactic.name is "none"', () => { + const mockStepData = { + ...mockData, + threat: [ + { + framework: 'mockFramework', + tactic: { + id: '1234', + name: 'tactic1', + reference: 'reference1', + }, + technique: [ + { + id: '456', + name: 'technique1', + reference: 'technique reference', + }, + ], + }, + { + framework: 'mockFramework', + tactic: { + id: '1234', + name: 'none', + reference: 'reference1', + }, + technique: [ + { + id: '456', + name: 'technique1', + reference: 'technique reference', + }, + ], + }, + ], + }; + const result: AboutStepRuleJson = formatAboutStepData(mockStepData); + const expected = { + description: '24/7', + false_positives: ['test'], + name: 'Query with rule-id', + note: '# this is some markdown documentation', + references: ['www.test.co'], + risk_score: 21, + severity: 'low', + tags: ['tag1', 'tag2'], + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { id: '1234', name: 'tactic1', reference: 'reference1' }, + technique: [{ id: '456', name: 'technique1', reference: 'technique reference' }], + }, + ], + }; + + expect(result).toEqual(expected); + }); + }); + + describe('formatActionsStepData', () => { + let mockData: ActionsStepRule; + + beforeEach(() => { + mockData = mockActionsStepRule(); + }); + + test('returns formatted object as ActionsStepRuleJson', () => { + const result: ActionsStepRuleJson = formatActionsStepData(mockData); + const expected = { + actions: [], + enabled: false, + meta: { + kibana_siem_app_url: 'http://localhost:5601/app/siem', + }, + throttle: 'no_actions', + }; + + expect(result).toEqual(expected); + }); + + test('returns proper throttle value for no_actions', () => { + const mockStepData = { + ...mockData, + throttle: 'no_actions', + }; + const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); + const expected = { + actions: [], + enabled: false, + meta: { + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, + }, + throttle: 'no_actions', + }; + + expect(result).toEqual(expected); + }); + + test('returns proper throttle value for rule', () => { + const mockStepData = { + ...mockData, + throttle: 'rule', + actions: [ + { + group: 'default', + id: 'id', + actionTypeId: 'actionTypeId', + params: {}, + }, + ], + }; + const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); + const expected = { + actions: [ + { + group: mockStepData.actions[0].group, + id: mockStepData.actions[0].id, + action_type_id: mockStepData.actions[0].actionTypeId, + params: mockStepData.actions[0].params, + }, + ], + enabled: false, + meta: { + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, + }, + throttle: 'rule', + }; + + expect(result).toEqual(expected); + }); + + test('returns proper throttle value for interval', () => { + const mockStepData = { + ...mockData, + throttle: '1d', + actions: [ + { + group: 'default', + id: 'id', + actionTypeId: 'actionTypeId', + params: {}, + }, + ], + }; + const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); + const expected = { + actions: [ + { + group: mockStepData.actions[0].group, + id: mockStepData.actions[0].id, + action_type_id: mockStepData.actions[0].actionTypeId, + params: mockStepData.actions[0].params, + }, + ], + enabled: false, + meta: { + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, + }, + throttle: mockStepData.throttle, + }; + + expect(result).toEqual(expected); + }); + + test('returns actions with action_type_id', () => { + const mockAction = { + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + params: { message: 'ML Rule generated {{state.signals_count}} alerts' }, + actionTypeId: '.slack', + }; + + const mockStepData = { + ...mockData, + actions: [mockAction], + }; + const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); + const expected = { + actions: [ + { + group: mockAction.group, + id: mockAction.id, + params: mockAction.params, + action_type_id: mockAction.actionTypeId, + }, + ], + enabled: false, + meta: { + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, + }, + throttle: 'no_actions', + }; + + expect(result).toEqual(expected); + }); + }); + + describe('formatRule', () => { + let mockAbout: AboutStepRule; + let mockDefine: DefineStepRule; + let mockSchedule: ScheduleStepRule; + let mockActions: ActionsStepRule; + + beforeEach(() => { + mockAbout = mockAboutStepRule(); + mockDefine = mockDefineStepRule(); + mockSchedule = mockScheduleStepRule(); + mockActions = mockActionsStepRule(); + }); + + test('returns NewRule with type of saved_query when saved_id exists', () => { + const result: NewRule = formatRule(mockDefine, mockAbout, mockSchedule, mockActions); + + expect(result.type).toEqual('saved_query'); + }); + + test('returns NewRule with type of query when saved_id does not exist', () => { + const mockDefineStepRuleWithoutSavedId = { + ...mockDefine, + queryBar: { + ...mockDefine.queryBar, + saved_id: '', + }, + }; + const result: NewRule = formatRule( + mockDefineStepRuleWithoutSavedId, + mockAbout, + mockSchedule, + mockActions + ); + + expect(result.type).toEqual('query'); + }); + + test('returns NewRule without id if ruleId does not exist', () => { + const result: NewRule = formatRule(mockDefine, mockAbout, mockSchedule, mockActions); + + expect(result.id).toBeUndefined(); + }); + }); + + describe('filterRuleFieldsForType', () => { + let fields: DefineStepRule; + + beforeEach(() => { + fields = mockDefineStepRule(); + }); + + it('removes query fields if the type is machine learning', () => { + const result = filterRuleFieldsForType(fields, 'machine_learning'); + expect(result).not.toHaveProperty('index'); + expect(result).not.toHaveProperty('queryBar'); + }); + + it('leaves ML fields if the type is machine learning', () => { + const result = filterRuleFieldsForType(fields, 'machine_learning'); + expect(result).toHaveProperty('anomalyThreshold'); + expect(result).toHaveProperty('machineLearningJobId'); + }); + + it('leaves arbitrary fields if the type is machine learning', () => { + const result = filterRuleFieldsForType(fields, 'machine_learning'); + expect(result).toHaveProperty('timeline'); + expect(result).toHaveProperty('ruleType'); + }); + + it('removes ML fields if the type is not machine learning', () => { + const result = filterRuleFieldsForType(fields, 'query'); + expect(result).not.toHaveProperty('anomalyThreshold'); + expect(result).not.toHaveProperty('machineLearningJobId'); + }); + + it('leaves query fields if the type is query', () => { + const result = filterRuleFieldsForType(fields, 'query'); + expect(result).toHaveProperty('index'); + expect(result).toHaveProperty('queryBar'); + }); + + it('leaves arbitrary fields if the type is query', () => { + const result = filterRuleFieldsForType(fields, 'query'); + expect(result).toHaveProperty('timeline'); + expect(result).toHaveProperty('ruleType'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.ts new file mode 100644 index 0000000000000..d5ce57ce5b3a9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { has, isEmpty } from 'lodash/fp'; +import moment from 'moment'; +import deepmerge from 'deepmerge'; + +import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../common/constants'; +import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions'; +import { RuleType } from '../../../../../../common/detection_engine/types'; +import { isMlRule } from '../../../../../../common/machine_learning/helpers'; +import { NewRule } from '../../../../../alerts/containers/detection_engine/rules'; + +import { + AboutStepRule, + DefineStepRule, + ScheduleStepRule, + ActionsStepRule, + DefineStepRuleJson, + ScheduleStepRuleJson, + AboutStepRuleJson, + ActionsStepRuleJson, +} from '../types'; + +export const getTimeTypeValue = (time: string): { unit: string; value: number } => { + const timeObj = { + unit: '', + value: 0, + }; + const filterTimeVal = (time as string).match(/\d+/g); + const filterTimeType = (time as string).match(/[a-zA-Z]+/g); + if (!isEmpty(filterTimeVal) && filterTimeVal != null && !isNaN(Number(filterTimeVal[0]))) { + timeObj.value = Number(filterTimeVal[0]); + } + if ( + !isEmpty(filterTimeType) && + filterTimeType != null && + ['s', 'm', 'h'].includes(filterTimeType[0]) + ) { + timeObj.unit = filterTimeType[0]; + } + return timeObj; +}; + +export interface RuleFields { + anomalyThreshold: unknown; + machineLearningJobId: unknown; + queryBar: unknown; + index: unknown; + ruleType: unknown; +} +type QueryRuleFields = Omit; +type MlRuleFields = Omit; + +const isMlFields = (fields: QueryRuleFields | MlRuleFields): fields is MlRuleFields => + has('anomalyThreshold', fields); + +export const filterRuleFieldsForType = (fields: T, type: RuleType) => { + if (isMlRule(type)) { + const { index, queryBar, ...mlRuleFields } = fields; + return mlRuleFields; + } else { + const { anomalyThreshold, machineLearningJobId, ...queryRuleFields } = fields; + return queryRuleFields; + } +}; + +export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJson => { + const ruleFields = filterRuleFieldsForType(defineStepData, defineStepData.ruleType); + const { ruleType, timeline } = ruleFields; + const baseFields = { + type: ruleType, + ...(timeline.id != null && + timeline.title != null && { + timeline_id: timeline.id, + timeline_title: timeline.title, + }), + }; + + const typeFields = isMlFields(ruleFields) + ? { + anomaly_threshold: ruleFields.anomalyThreshold, + machine_learning_job_id: ruleFields.machineLearningJobId, + } + : { + index: ruleFields.index, + filters: ruleFields.queryBar?.filters, + language: ruleFields.queryBar?.query?.language, + query: ruleFields.queryBar?.query?.query as string, + saved_id: ruleFields.queryBar?.saved_id, + ...(ruleType === 'query' && + ruleFields.queryBar?.saved_id && { type: 'saved_query' as RuleType }), + }; + + return { + ...baseFields, + ...typeFields, + }; +}; + +export const formatScheduleStepData = (scheduleData: ScheduleStepRule): ScheduleStepRuleJson => { + const { isNew, ...formatScheduleData } = scheduleData; + if (!isEmpty(formatScheduleData.interval) && !isEmpty(formatScheduleData.from)) { + const { unit: intervalUnit, value: intervalValue } = getTimeTypeValue( + formatScheduleData.interval + ); + const { unit: fromUnit, value: fromValue } = getTimeTypeValue(formatScheduleData.from); + const duration = moment.duration(intervalValue, intervalUnit as 's' | 'm' | 'h'); + duration.add(fromValue, fromUnit as 's' | 'm' | 'h'); + formatScheduleData.from = `now-${duration.asSeconds()}s`; + formatScheduleData.to = 'now'; + } + return { + ...formatScheduleData, + meta: { + from: scheduleData.from, + }, + }; +}; + +export const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => { + const { falsePositives, references, riskScore, threat, isNew, note, ...rest } = aboutStepData; + return { + false_positives: falsePositives.filter((item) => !isEmpty(item)), + references: references.filter((item) => !isEmpty(item)), + risk_score: riskScore, + threat: threat + .filter((singleThreat) => singleThreat.tactic.name !== 'none') + .map((singleThreat) => ({ + ...singleThreat, + framework: 'MITRE ATT&CK', + technique: singleThreat.technique.map((technique) => { + const { id, name, reference } = technique; + return { id, name, reference }; + }), + })), + ...(!isEmpty(note) ? { note } : {}), + ...rest, + }; +}; + +export const formatActionsStepData = (actionsStepData: ActionsStepRule): ActionsStepRuleJson => { + const { + actions = [], + enabled, + kibanaSiemAppUrl, + throttle = NOTIFICATION_THROTTLE_NO_ACTIONS, + } = actionsStepData; + + return { + actions: actions.map(transformAlertToRuleAction), + enabled, + throttle: actions.length ? throttle : NOTIFICATION_THROTTLE_NO_ACTIONS, + meta: { + kibana_siem_app_url: kibanaSiemAppUrl, + }, + }; +}; + +export const formatRule = ( + defineStepData: DefineStepRule, + aboutStepData: AboutStepRule, + scheduleData: ScheduleStepRule, + actionsData: ActionsStepRule +): NewRule => + deepmerge.all([ + formatDefineStepData(defineStepData), + formatAboutStepData(aboutStepData), + formatScheduleStepData(scheduleData), + formatActionsStepData(actionsData), + ]) as NewRule; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx new file mode 100644 index 0000000000000..dd290ec1ae582 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx @@ -0,0 +1,428 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButtonEmpty, EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui'; +import React, { useCallback, useRef, useState, useMemo } from 'react'; +import { Redirect } from 'react-router-dom'; +import styled, { StyledComponent } from 'styled-components'; + +import { usePersistRule } from '../../../../../alerts/containers/detection_engine/rules'; + +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../common/components/link_to/redirect_to_detection_engine'; +import { WrapperPage } from '../../../../../common/components/wrapper_page'; +import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters'; +import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; +import { useUserInfo } from '../../../../components/user_info'; +import { AccordionTitle } from '../../../../components/rules/accordion_title'; +import { FormData, FormHook } from '../../../../../shared_imports'; +import { StepAboutRule } from '../../../../components/rules/step_about_rule'; +import { StepDefineRule } from '../../../../components/rules/step_define_rule'; +import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; +import { StepRuleActions } from '../../../../components/rules/step_rule_actions'; +import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; +import * as RuleI18n from '../translations'; +import { redirectToDetections, getActionMessageParams, userHasNoPermissions } from '../helpers'; +import { + AboutStepRule, + DefineStepRule, + RuleStep, + RuleStepData, + ScheduleStepRule, + ActionsStepRule, +} from '../types'; +import { formatRule } from './helpers'; +import * as i18n from './translations'; + +const stepsRuleOrder = [ + RuleStep.defineRule, + RuleStep.aboutRule, + RuleStep.scheduleRule, + RuleStep.ruleActions, +]; + +const MyEuiPanel = styled(EuiPanel)<{ + zindex?: number; +}>` + position: relative; + z-index: ${(props) => props.zindex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ + + > .euiAccordion > .euiAccordion__triggerWrapper { + .euiAccordion__button { + cursor: default !important; + &:hover { + text-decoration: none !important; + } + } + + .euiAccordion__iconWrapper { + display: none; + } + } +`; + +MyEuiPanel.displayName = 'MyEuiPanel'; + +const StepDefineRuleAccordion: StyledComponent< + typeof EuiAccordion, + any, // eslint-disable-line + { ref: React.MutableRefObject }, + never +> = styled(EuiAccordion)` + .euiAccordion__childWrapper { + overflow: visible; + } +`; + +StepDefineRuleAccordion.displayName = 'StepDefineRuleAccordion'; + +const CreateRulePageComponent: React.FC = () => { + const { + loading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + } = useUserInfo(); + const [, dispatchToaster] = useStateToaster(); + const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); + const defineRuleRef = useRef(null); + const aboutRuleRef = useRef(null); + const scheduleRuleRef = useRef(null); + const ruleActionsRef = useRef(null); + const stepsForm = useRef | null>>({ + [RuleStep.defineRule]: null, + [RuleStep.aboutRule]: null, + [RuleStep.scheduleRule]: null, + [RuleStep.ruleActions]: null, + }); + const stepsData = useRef>({ + [RuleStep.defineRule]: { isValid: false, data: {} }, + [RuleStep.aboutRule]: { isValid: false, data: {} }, + [RuleStep.scheduleRule]: { isValid: false, data: {} }, + [RuleStep.ruleActions]: { isValid: false, data: {} }, + }); + const [isStepRuleInReadOnlyView, setIsStepRuleInEditView] = useState>({ + [RuleStep.defineRule]: false, + [RuleStep.aboutRule]: false, + [RuleStep.scheduleRule]: false, + [RuleStep.ruleActions]: false, + }); + const [{ isLoading, isSaved }, setRule] = usePersistRule(); + const actionMessageParams = useMemo( + () => + getActionMessageParams((stepsData.current['define-rule'].data as DefineStepRule).ruleType), + [stepsData.current['define-rule'].data] + ); + + const setStepData = useCallback( + (step: RuleStep, data: unknown, isValid: boolean) => { + stepsData.current[step] = { ...stepsData.current[step], data, isValid }; + if (isValid) { + const stepRuleIdx = stepsRuleOrder.findIndex((item) => step === item); + if ([0, 1, 2].includes(stepRuleIdx)) { + if (isStepRuleInReadOnlyView[stepsRuleOrder[stepRuleIdx + 1]]) { + setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [step]: true, + [stepsRuleOrder[stepRuleIdx + 1]]: false, + }); + } else if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [step]: true, + }); + openCloseAccordion(stepsRuleOrder[stepRuleIdx + 1]); + setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); + } + } else if ( + stepRuleIdx === 3 && + stepsData.current[RuleStep.defineRule].isValid && + stepsData.current[RuleStep.aboutRule].isValid && + stepsData.current[RuleStep.scheduleRule].isValid + ) { + setRule( + formatRule( + stepsData.current[RuleStep.defineRule].data as DefineStepRule, + stepsData.current[RuleStep.aboutRule].data as AboutStepRule, + stepsData.current[RuleStep.scheduleRule].data as ScheduleStepRule, + stepsData.current[RuleStep.ruleActions].data as ActionsStepRule + ) + ); + } + } + }, + [isStepRuleInReadOnlyView, openAccordionId, stepsData.current, setRule] + ); + + const setStepsForm = useCallback((step: RuleStep, form: FormHook) => { + stepsForm.current[step] = form; + }, []); + + const getAccordionType = useCallback( + (accordionId: RuleStep) => { + if (accordionId === openAccordionId) { + return 'active'; + } else if (stepsData.current[accordionId].isValid) { + return 'valid'; + } + return 'passive'; + }, + [openAccordionId, stepsData.current] + ); + + const defineRuleButton = ( + + ); + + const aboutRuleButton = ( + + ); + + const scheduleRuleButton = ( + + ); + + const ruleActionsButton = ( + + ); + + const openCloseAccordion = (accordionId: RuleStep | null) => { + if (accordionId != null) { + if (accordionId === RuleStep.defineRule && defineRuleRef.current != null) { + defineRuleRef.current.onToggle(); + } else if (accordionId === RuleStep.aboutRule && aboutRuleRef.current != null) { + aboutRuleRef.current.onToggle(); + } else if (accordionId === RuleStep.scheduleRule && scheduleRuleRef.current != null) { + scheduleRuleRef.current.onToggle(); + } else if (accordionId === RuleStep.ruleActions && ruleActionsRef.current != null) { + ruleActionsRef.current.onToggle(); + } + } + }; + + // eslint-disable-next-line react-hooks/rules-of-hooks + const manageAccordions = useCallback( + (id: RuleStep, isOpen: boolean) => { + const activeRuleIdx = stepsRuleOrder.findIndex((step) => step === openAccordionId); + const stepRuleIdx = stepsRuleOrder.findIndex((step) => step === id); + + if ((id === openAccordionId || stepRuleIdx < activeRuleIdx) && !isOpen) { + openCloseAccordion(id); + } else if (stepRuleIdx >= activeRuleIdx) { + if ( + openAccordionId !== id && + !stepsData.current[openAccordionId].isValid && + !isStepRuleInReadOnlyView[id] && + isOpen + ) { + openCloseAccordion(id); + } + } + }, + [isStepRuleInReadOnlyView, openAccordionId, stepsData] + ); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const manageIsEditable = useCallback( + async (id: RuleStep) => { + const activeForm = await stepsForm.current[openAccordionId]?.submit(); + if (activeForm != null && activeForm?.isValid) { + stepsData.current[openAccordionId] = { + ...stepsData.current[openAccordionId], + data: activeForm.data, + isValid: activeForm.isValid, + }; + setOpenAccordionId(id); + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [openAccordionId]: true, + [id]: false, + }); + } + }, + [isStepRuleInReadOnlyView, openAccordionId] + ); + + if (isSaved) { + const ruleName = (stepsData.current[RuleStep.aboutRule].data as AboutStepRule).name; + displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster); + return ; + } + + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { + return ; + } else if (userHasNoPermissions(canUserCRUD)) { + return ; + } + + return ( + <> + + + + + {i18n.EDIT_RULE} + + ) + } + > + + + + + + + + {i18n.EDIT_RULE} + + ) + } + > + + + + + + + + {i18n.EDIT_RULE} + + ) + } + > + + + + + + + + {i18n.EDIT_RULE} + + ) + } + > + + + + + + + + + ); +}; + +export const CreateRulePage = React.memo(CreateRulePageComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/translations.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/translations.ts new file mode 100644 index 0000000000000..dbcd05470b5cd --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/translations.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.pageTitle', + { + defaultMessage: 'Create new rule', + } +); + +export const BACK_TO_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.backToRulesDescription', + { + defaultMessage: 'Back to detection rules', + } +); + +export const EDIT_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.editRuleButton', + { + defaultMessage: 'Edit', + } +); + +export const SUCCESSFULLY_CREATED_RULES = (ruleName: string) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.create.successfullyCreatedRuleTitle', + { + values: { ruleName }, + defaultMessage: '{ruleName} was created', + } + ); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/failure_history.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/failure_history.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/failure_history.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/failure_history.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/failure_history.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/failure_history.tsx similarity index 95% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/failure_history.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/failure_history.tsx index f03f320c51418..1030aaa30d752 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/failure_history.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/failure_history.tsx @@ -68,7 +68,7 @@ const FailureHistoryComponent: React.FC = ({ id }) => { columns={columns} loading={loading} items={ - ruleStatus != null ? ruleStatus?.failures.filter(rs => rs.last_failure_at != null) : [] + ruleStatus != null ? ruleStatus?.failures.filter((rs) => rs.last_failure_at != null) : [] } sorting={{ sort: { field: 'status_date', direction: 'desc' } }} /> diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx new file mode 100644 index 0000000000000..ebd6ed118f937 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx @@ -0,0 +1,429 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable react-hooks/rules-of-hooks */ + +import { + EuiButton, + EuiLoadingSpinner, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTab, + EuiTabs, + EuiToolTip, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC, memo, useCallback, useMemo, useState } from 'react'; +import { Redirect, useParams } from 'react-router-dom'; +import { StickyContainer } from 'react-sticky'; +import { connect, ConnectedProps } from 'react-redux'; + +import { UpdateDateRange } from '../../../../../common/components/charts/common'; +import { FiltersGlobal } from '../../../../../common/components/filters_global'; +import { FormattedDate } from '../../../../../common/components/formatted_date'; +import { + getEditRuleUrl, + getRulesUrl, + DETECTION_ENGINE_PAGE_NAME, +} from '../../../../../common/components/link_to/redirect_to_detection_engine'; +import { SiemSearchBar } from '../../../../../common/components/search_bar'; +import { WrapperPage } from '../../../../../common/components/wrapper_page'; +import { useRule } from '../../../../../alerts/containers/detection_engine/rules'; + +import { + indicesExistOrDataTemporarilyUnavailable, + WithSource, +} from '../../../../../common/containers/source'; +import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; + +import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; +import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; +import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel'; +import { AlertsTable } from '../../../../components/alerts_table'; +import { useUserInfo } from '../../../../components/user_info'; +import { DetectionEngineEmptyPage } from '../../detection_engine_empty_page'; +import { useAlertInfo } from '../../../../components/alerts_info'; +import { StepDefineRule } from '../../../../components/rules/step_define_rule'; +import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; +import { buildAlertsRuleIdFilter } from '../../../../components/alerts_table/default_config'; +import { NoWriteAlertsCallOut } from '../../../../components/no_write_alerts_callout'; +import * as detectionI18n from '../../translations'; +import { ReadOnlyCallOut } from '../../../../components/rules/read_only_callout'; +import { RuleSwitch } from '../../../../components/rules/rule_switch'; +import { StepPanel } from '../../../../components/rules/step_panel'; +import { getStepsData, redirectToDetections, userHasNoPermissions } from '../helpers'; +import * as ruleI18n from '../translations'; +import * as i18n from './translations'; +import { GlobalTime } from '../../../../../common/containers/global_time'; +import { alertsHistogramOptions } from '../../../../components/alerts_histogram_panel/config'; +import { inputsSelectors } from '../../../../../common/store/inputs'; +import { State } from '../../../../../common/store'; +import { InputsRange } from '../../../../../common/store/inputs/model'; +import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions'; +import { RuleActionsOverflow } from '../../../../components/rules/rule_actions_overflow'; +import { RuleStatusFailedCallOut } from './status_failed_callout'; +import { FailureHistory } from './failure_history'; +import { RuleStatus } from '../../../../components/rules//rule_status'; +import { useMlCapabilities } from '../../../../../common/components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; + +enum RuleDetailTabs { + alerts = 'alerts', + failures = 'failures', +} + +const ruleDetailTabs = [ + { + id: RuleDetailTabs.alerts, + name: detectionI18n.ALERT, + disabled: false, + }, + { + id: RuleDetailTabs.failures, + name: i18n.FAILURE_HISTORY_TAB, + disabled: false, + }, +]; + +export const RuleDetailsPageComponent: FC = ({ + filters, + query, + setAbsoluteRangeDatePicker, +}) => { + const { + loading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + hasIndexWrite, + signalIndexName, + } = useUserInfo(); + const { detailName: ruleId } = useParams(); + const [isLoading, rule] = useRule(ruleId); + // This is used to re-trigger api rule status when user de/activate rule + const [ruleEnabled, setRuleEnabled] = useState(null); + const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.alerts); + const { aboutRuleData, modifiedAboutRuleDetailsData, defineRuleData, scheduleRuleData } = + rule != null + ? getStepsData({ rule, detailsView: true }) + : { + aboutRuleData: null, + modifiedAboutRuleDetailsData: null, + defineRuleData: null, + scheduleRuleData: null, + }; + const [lastAlerts] = useAlertInfo({ ruleId }); + const mlCapabilities = useMlCapabilities(); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); + + const title = isLoading === true || rule === null ? : rule.name; + const subTitle = useMemo( + () => + isLoading === true || rule === null ? ( + + ) : ( + [ + + ), + }} + />, + rule?.updated_by != null ? ( + + ), + }} + /> + ) : ( + '' + ), + ] + ), + [isLoading, rule] + ); + + const alertDefaultFilters = useMemo( + () => (ruleId != null ? buildAlertsRuleIdFilter(ruleId) : []), + [ruleId] + ); + + const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ + alertDefaultFilters, + filters, + ]); + + const tabs = useMemo( + () => ( + + {ruleDetailTabs.map((tab) => ( + setRuleDetailTab(tab.id)} + isSelected={tab.id === ruleDetailTab} + disabled={tab.disabled} + key={tab.id} + > + {tab.name} + + ))} + + ), + [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] + ); + const ruleError = useMemo( + () => + rule?.status === 'failed' && + ruleDetailTab === RuleDetailTabs.alerts && + rule?.last_failure_at != null ? ( + + ) : null, + [rule, ruleDetailTab] + ); + + const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [ + signalIndexName, + ]); + + const updateDateRangeCallback = useCallback( + ({ x }) => { + if (!x) { + return; + } + const [min, max] = x; + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + + const handleOnChangeEnabledRule = useCallback( + (enabled: boolean) => { + if (ruleEnabled == null || enabled !== ruleEnabled) { + setRuleEnabled(enabled); + } + }, + [ruleEnabled, setRuleEnabled] + ); + + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { + return ; + } + + return ( + <> + {hasIndexWrite != null && !hasIndexWrite && } + {userHasNoPermissions(canUserCRUD) && } + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + {({ to, from, deleteQuery, setQuery }) => ( + + + + + + + + {detectionI18n.LAST_ALERT} + {': '} + {lastAlerts} + , + ] + : []), + , + ]} + title={title} + > + + + + + + + + + + + + {ruleI18n.EDIT_RULE_SETTINGS} + + + + + + + + + + {ruleError} + + + + + + + + + + + {defineRuleData != null && ( + + )} + + + + + + {scheduleRuleData != null && ( + + )} + + + + + + + {tabs} + + {ruleDetailTab === RuleDetailTabs.alerts && ( + <> + + + {ruleId != null && ( + + )} + + )} + {ruleDetailTab === RuleDetailTabs.failures && } + + + )} + + ) : ( + + + + + + ); + }} + + + + + ); +}; + +const makeMapStateToProps = () => { + const getGlobalInputs = inputsSelectors.globalSelector(); + return (state: State) => { + const globalInputs: InputsRange = getGlobalInputs(state); + const { query, filters } = globalInputs; + + return { + query, + filters, + }; + }; +}; + +const mapDispatchToProps = { + setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, +}; + +const connector = connect(makeMapStateToProps, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +export const RuleDetailsPage = connector(memo(RuleDetailsPageComponent)); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/status_failed_callout.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/status_failed_callout.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/status_failed_callout.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/status_failed_callout.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/status_failed_callout.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/status_failed_callout.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/status_failed_callout.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/status_failed_callout.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/translations.ts new file mode 100644 index 0000000000000..9cf510f4a9b5d --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/translations.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.pageTitle', + { + defaultMessage: 'Rule details', + } +); + +export const BACK_TO_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.backToRulesDescription', + { + defaultMessage: 'Back to detection rules', + } +); + +export const EXPERIMENTAL = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.experimentalDescription', + { + defaultMessage: 'Experimental', + } +); + +export const ACTIVATE_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.activateRuleLabel', + { + defaultMessage: 'Activate', + } +); + +export const UNKNOWN = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.unknownDescription', + { + defaultMessage: 'Unknown', + } +); + +export const ERROR_CALLOUT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.errorCalloutTitle', + { + defaultMessage: 'Rule failure at', + } +); + +export const FAILURE_HISTORY_TAB = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.failureHistoryTab', + { + defaultMessage: 'Failure History', + } +); + +export const LAST_FIVE_ERRORS = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.lastFiveErrorsTitle', + { + defaultMessage: 'Last five errors', + } +); + +export const COLUMN_STATUS_TYPE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.statusTypeColumn', + { + defaultMessage: 'Type', + } +); + +export const COLUMN_FAILED_AT = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.statusFailedAtColumn', + { + defaultMessage: 'Failed at', + } +); + +export const COLUMN_FAILED_MSG = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.statusFailedMsgColumn', + { + defaultMessage: 'Failed message', + } +); + +export const TYPE_FAILED = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.statusFailedDescription', + { + defaultMessage: 'Failed', + } +); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/edit/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.tsx new file mode 100644 index 0000000000000..8fc646c01b17c --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.tsx @@ -0,0 +1,431 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable react-hooks/rules-of-hooks */ + +import { + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Redirect, useParams } from 'react-router-dom'; + +import { useRule, usePersistRule } from '../../../../../alerts/containers/detection_engine/rules'; +import { WrapperPage } from '../../../../../common/components/wrapper_page'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../common/components/link_to/redirect_to_detection_engine'; +import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters'; +import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; +import { useUserInfo } from '../../../../components/user_info'; +import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; +import { FormHook, FormData } from '../../../../../shared_imports'; +import { StepPanel } from '../../../../components/rules/step_panel'; +import { StepAboutRule } from '../../../../components/rules/step_about_rule'; +import { StepDefineRule } from '../../../../components/rules/step_define_rule'; +import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; +import { StepRuleActions } from '../../../../components/rules/step_rule_actions'; +import { formatRule } from '../create/helpers'; +import { + getStepsData, + redirectToDetections, + getActionMessageParams, + userHasNoPermissions, +} from '../helpers'; +import * as ruleI18n from '../translations'; +import { + RuleStep, + DefineStepRule, + AboutStepRule, + ScheduleStepRule, + ActionsStepRule, +} from '../types'; +import * as i18n from './translations'; + +interface StepRuleForm { + isValid: boolean; +} +interface AboutStepRuleForm extends StepRuleForm { + data: AboutStepRule | null; +} +interface DefineStepRuleForm extends StepRuleForm { + data: DefineStepRule | null; +} +interface ScheduleStepRuleForm extends StepRuleForm { + data: ScheduleStepRule | null; +} + +interface ActionsStepRuleForm extends StepRuleForm { + data: ActionsStepRule | null; +} + +const EditRulePageComponent: FC = () => { + const [, dispatchToaster] = useStateToaster(); + const { + loading: initLoading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + } = useUserInfo(); + const { detailName: ruleId } = useParams(); + const [loading, rule] = useRule(ruleId); + + const [initForm, setInitForm] = useState(false); + const [myAboutRuleForm, setMyAboutRuleForm] = useState({ + data: null, + isValid: false, + }); + const [myDefineRuleForm, setMyDefineRuleForm] = useState({ + data: null, + isValid: false, + }); + const [myScheduleRuleForm, setMyScheduleRuleForm] = useState({ + data: null, + isValid: false, + }); + const [myActionsRuleForm, setMyActionsRuleForm] = useState({ + data: null, + isValid: false, + }); + const [selectedTab, setSelectedTab] = useState(); + const stepsForm = useRef | null>>({ + [RuleStep.defineRule]: null, + [RuleStep.aboutRule]: null, + [RuleStep.scheduleRule]: null, + [RuleStep.ruleActions]: null, + }); + const [{ isLoading, isSaved }, setRule] = usePersistRule(); + const [tabHasError, setTabHasError] = useState([]); + const actionMessageParams = useMemo(() => getActionMessageParams(rule?.type), [rule]); + const setStepsForm = useCallback( + (step: RuleStep, form: FormHook) => { + stepsForm.current[step] = form; + if (initForm && step === (selectedTab?.id as RuleStep) && form.isSubmitted === false) { + setInitForm(false); + form.submit(); + } + }, + [initForm, selectedTab] + ); + const tabs = useMemo( + () => [ + { + id: RuleStep.defineRule, + name: ruleI18n.DEFINITION, + disabled: rule?.immutable, + content: ( + <> + + + {myDefineRuleForm.data != null && ( + + )} + + + + ), + }, + { + id: RuleStep.aboutRule, + name: ruleI18n.ABOUT, + disabled: rule?.immutable, + content: ( + <> + + + {myAboutRuleForm.data != null && ( + + )} + + + + ), + }, + { + id: RuleStep.scheduleRule, + name: ruleI18n.SCHEDULE, + disabled: rule?.immutable, + content: ( + <> + + + {myScheduleRuleForm.data != null && ( + + )} + + + + ), + }, + { + id: RuleStep.ruleActions, + name: ruleI18n.ACTIONS, + content: ( + <> + + + {myActionsRuleForm.data != null && ( + + )} + + + + ), + }, + ], + [ + rule, + loading, + initLoading, + isLoading, + myAboutRuleForm, + myDefineRuleForm, + myScheduleRuleForm, + myActionsRuleForm, + setStepsForm, + stepsForm, + actionMessageParams, + ] + ); + + const onSubmit = useCallback(async () => { + const activeFormId = selectedTab?.id as RuleStep; + const activeForm = await stepsForm.current[activeFormId]?.submit(); + + const invalidForms = [ + RuleStep.aboutRule, + RuleStep.defineRule, + RuleStep.scheduleRule, + RuleStep.ruleActions, + ].reduce((acc, step) => { + if ( + (step === activeFormId && activeForm != null && !activeForm?.isValid) || + (step === RuleStep.aboutRule && !myAboutRuleForm.isValid) || + (step === RuleStep.defineRule && !myDefineRuleForm.isValid) || + (step === RuleStep.scheduleRule && !myScheduleRuleForm.isValid) || + (step === RuleStep.ruleActions && !myActionsRuleForm.isValid) + ) { + return [...acc, step]; + } + return acc; + }, []); + + if (invalidForms.length === 0 && activeForm != null) { + setTabHasError([]); + setRule({ + ...formatRule( + (activeFormId === RuleStep.defineRule + ? activeForm.data + : myDefineRuleForm.data) as DefineStepRule, + (activeFormId === RuleStep.aboutRule + ? activeForm.data + : myAboutRuleForm.data) as AboutStepRule, + (activeFormId === RuleStep.scheduleRule + ? activeForm.data + : myScheduleRuleForm.data) as ScheduleStepRule, + (activeFormId === RuleStep.ruleActions + ? activeForm.data + : myActionsRuleForm.data) as ActionsStepRule + ), + ...(ruleId ? { id: ruleId } : {}), + }); + } else { + setTabHasError(invalidForms); + } + }, [ + stepsForm, + myAboutRuleForm, + myDefineRuleForm, + myScheduleRuleForm, + myActionsRuleForm, + selectedTab, + ruleId, + ]); + + useEffect(() => { + if (rule != null) { + const { aboutRuleData, defineRuleData, scheduleRuleData, ruleActionsData } = getStepsData({ + rule, + }); + setMyAboutRuleForm({ data: aboutRuleData, isValid: true }); + setMyDefineRuleForm({ data: defineRuleData, isValid: true }); + setMyScheduleRuleForm({ data: scheduleRuleData, isValid: true }); + setMyActionsRuleForm({ data: ruleActionsData, isValid: true }); + } + }, [rule]); + + const onTabClick = useCallback( + async (tab: EuiTabbedContentTab) => { + if (selectedTab != null) { + const ruleStep = selectedTab.id as RuleStep; + const respForm = await stepsForm.current[ruleStep]?.submit(); + + if (respForm != null) { + if (ruleStep === RuleStep.aboutRule) { + setMyAboutRuleForm({ + data: respForm.data as AboutStepRule, + isValid: respForm.isValid, + }); + } else if (ruleStep === RuleStep.defineRule) { + setMyDefineRuleForm({ + data: respForm.data as DefineStepRule, + isValid: respForm.isValid, + }); + } else if (ruleStep === RuleStep.scheduleRule) { + setMyScheduleRuleForm({ + data: respForm.data as ScheduleStepRule, + isValid: respForm.isValid, + }); + } else if (ruleStep === RuleStep.ruleActions) { + setMyActionsRuleForm({ + data: respForm.data as ActionsStepRule, + isValid: respForm.isValid, + }); + } + } + } + setInitForm(true); + setSelectedTab(tab); + }, + [selectedTab, stepsForm.current] + ); + + useEffect(() => { + if (rule != null) { + const { aboutRuleData, defineRuleData, scheduleRuleData, ruleActionsData } = getStepsData({ + rule, + }); + setMyAboutRuleForm({ data: aboutRuleData, isValid: true }); + setMyDefineRuleForm({ data: defineRuleData, isValid: true }); + setMyScheduleRuleForm({ data: scheduleRuleData, isValid: true }); + setMyActionsRuleForm({ data: ruleActionsData, isValid: true }); + } + }, [rule]); + + useEffect(() => { + const tabIndex = rule?.immutable ? 3 : 0; + setSelectedTab(tabs[tabIndex]); + }, [rule]); + + if (isSaved) { + displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); + return ; + } + + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { + return ; + } else if (userHasNoPermissions(canUserCRUD)) { + return ; + } + + return ( + <> + + + {tabHasError.length > 0 && ( + + { + if (t === RuleStep.aboutRule) { + return ruleI18n.ABOUT; + } else if (t === RuleStep.defineRule) { + return ruleI18n.DEFINITION; + } else if (t === RuleStep.scheduleRule) { + return ruleI18n.SCHEDULE; + } else if (t === RuleStep.ruleActions) { + return ruleI18n.RULE_ACTIONS; + } + return t; + }) + .join(', '), + }} + /> + + )} + + t.id === selectedTab?.id)} + onTabClick={onTabClick} + tabs={tabs} + /> + + + + + + + {i18n.CANCEL} + + + + + + {i18n.SAVE_CHANGES} + + + + + + + + ); +}; + +export const EditRulePage = memo(EditRulePageComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/translations.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/translations.ts new file mode 100644 index 0000000000000..dcd55caf6549e --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/translations.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.editRule.pageTitle', + { + defaultMessage: 'Edit rule settings', + } +); + +export const CANCEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.editRule.cancelTitle', + { + defaultMessage: 'Cancel', + } +); + +export const SAVE_CHANGES = i18n.translate( + 'xpack.securitySolution.detectionEngine.editRule.saveChangeTitle', + { + defaultMessage: 'Save changes', + } +); + +export const SORRY_ERRORS = i18n.translate( + 'xpack.securitySolution.detectionEngine.editRule.errorMsgDescription', + { + defaultMessage: 'Sorry', + } +); + +export const BACK_TO = i18n.translate( + 'xpack.securitySolution.detectionEngine.editRule.backToDescription', + { + defaultMessage: 'Back to', + } +); + +export const SUCCESSFULLY_SAVED_RULE = (ruleName: string) => + i18n.translate('xpack.securitySolution.detectionEngine.rules.update.successfullySavedRuleTitle', { + values: { ruleName }, + defaultMessage: '{ruleName} was saved', + }); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/helpers.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.tsx new file mode 100644 index 0000000000000..2cd211a35e9ba --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.tsx @@ -0,0 +1,273 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import dateMath from '@elastic/datemath'; +import { get } from 'lodash/fp'; +import moment from 'moment'; +import memoizeOne from 'memoize-one'; +import { useLocation } from 'react-router-dom'; + +import { RuleAlertAction, RuleType } from '../../../../../common/detection_engine/types'; +import { isMlRule } from '../../../../../common/machine_learning/helpers'; +import { transformRuleToAlertAction } from '../../../../../common/detection_engine/transform_actions'; +import { Filter } from '../../../../../../../../src/plugins/data/public'; +import { Rule } from '../../../../alerts/containers/detection_engine/rules'; +import { FormData, FormHook, FormSchema } from '../../../../shared_imports'; +import { + AboutStepRule, + AboutStepRuleDetails, + DefineStepRule, + IMitreEnterpriseAttack, + ScheduleStepRule, + ActionsStepRule, +} from './types'; + +export interface GetStepsData { + aboutRuleData: AboutStepRule; + modifiedAboutRuleDetailsData: AboutStepRuleDetails; + defineRuleData: DefineStepRule; + scheduleRuleData: ScheduleStepRule; + ruleActionsData: ActionsStepRule; +} + +export const getStepsData = ({ + rule, + detailsView = false, +}: { + rule: Rule; + detailsView?: boolean; +}): GetStepsData => { + const defineRuleData: DefineStepRule = getDefineStepsData(rule); + const aboutRuleData: AboutStepRule = getAboutStepsData(rule, detailsView); + const modifiedAboutRuleDetailsData: AboutStepRuleDetails = getModifiedAboutDetailsData(rule); + const scheduleRuleData: ScheduleStepRule = getScheduleStepsData(rule); + const ruleActionsData: ActionsStepRule = getActionsStepsData(rule); + + return { + aboutRuleData, + modifiedAboutRuleDetailsData, + defineRuleData, + scheduleRuleData, + ruleActionsData, + }; +}; + +export const getActionsStepsData = ( + rule: Omit & { actions: RuleAlertAction[] } +): ActionsStepRule => { + const { enabled, throttle, meta, actions = [] } = rule; + + return { + actions: actions?.map(transformRuleToAlertAction), + isNew: false, + throttle, + kibanaSiemAppUrl: meta?.kibana_siem_app_url, + enabled, + }; +}; + +export const getDefineStepsData = (rule: Rule): DefineStepRule => ({ + isNew: false, + ruleType: rule.type, + anomalyThreshold: rule.anomaly_threshold ?? 50, + machineLearningJobId: rule.machine_learning_job_id ?? '', + index: rule.index ?? [], + queryBar: { + query: { query: rule.query ?? '', language: rule.language ?? '' }, + filters: (rule.filters ?? []) as Filter[], + saved_id: rule.saved_id, + }, + timeline: { + id: rule.timeline_id ?? null, + title: rule.timeline_title ?? null, + }, +}); + +export const getScheduleStepsData = (rule: Rule): ScheduleStepRule => { + const { interval, from } = rule; + const fromHumanizedValue = getHumanizedDuration(from, interval); + + return { + isNew: false, + interval, + from: fromHumanizedValue, + }; +}; + +export const getHumanizedDuration = (from: string, interval: string): string => { + const fromValue = dateMath.parse(from) ?? moment(); + const intervalValue = dateMath.parse(`now-${interval}`) ?? moment(); + + const fromDuration = moment.duration(intervalValue.diff(fromValue)); + const fromHumanize = `${Math.floor(fromDuration.asHours())}h`; + + if (fromDuration.asSeconds() < 60) { + return `${Math.floor(fromDuration.asSeconds())}s`; + } else if (fromDuration.asMinutes() < 60) { + return `${Math.floor(fromDuration.asMinutes())}m`; + } + + return fromHumanize; +}; + +export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRule => { + const { name, description, note } = determineDetailsValue(rule, detailsView); + const { + references, + severity, + false_positives: falsePositives, + risk_score: riskScore, + tags, + threat, + } = rule; + + return { + isNew: false, + name, + description, + note: note!, + references, + severity, + tags, + riskScore, + falsePositives, + threat: threat as IMitreEnterpriseAttack[], + }; +}; + +export const determineDetailsValue = ( + rule: Rule, + detailsView: boolean +): Pick => { + const { name, description, note } = rule; + if (detailsView) { + return { name: '', description: '', note: '' }; + } + + return { name, description, note: note ?? '' }; +}; + +export const getModifiedAboutDetailsData = (rule: Rule): AboutStepRuleDetails => ({ + note: rule.note ?? '', + description: rule.description, +}); + +export const useQuery = () => new URLSearchParams(useLocation().search); + +export type PrePackagedRuleStatus = + | 'ruleInstalled' + | 'ruleNotInstalled' + | 'ruleNeedUpdate' + | 'someRuleUninstall' + | 'unknown'; + +export const getPrePackagedRuleStatus = ( + rulesInstalled: number | null, + rulesNotInstalled: number | null, + rulesNotUpdated: number | null +): PrePackagedRuleStatus => { + if ( + rulesNotInstalled != null && + rulesInstalled === 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { + return 'ruleNotInstalled'; + } else if ( + rulesInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled === 0 && + rulesNotUpdated === 0 + ) { + return 'ruleInstalled'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { + return 'someRuleUninstall'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesNotUpdated != null && + rulesInstalled > 0 && + rulesNotInstalled >= 0 && + rulesNotUpdated > 0 + ) { + return 'ruleNeedUpdate'; + } + return 'unknown'; +}; +export const setFieldValue = ( + form: FormHook, + schema: FormSchema, + defaultValues: unknown +) => + Object.keys(schema).forEach((key) => { + const val = get(key, defaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); + +export const redirectToDetections = ( + isSignalIndexExists: boolean | null, + isAuthenticated: boolean | null, + hasEncryptionKey: boolean | null +) => + isSignalIndexExists != null && + isAuthenticated != null && + hasEncryptionKey != null && + (!isSignalIndexExists || !isAuthenticated || !hasEncryptionKey); + +export const getActionMessageRuleParams = (ruleType: RuleType): string[] => { + const commonRuleParamsKeys = [ + 'id', + 'name', + 'description', + 'false_positives', + 'rule_id', + 'max_signals', + 'risk_score', + 'output_index', + 'references', + 'severity', + 'timeline_id', + 'timeline_title', + 'threat', + 'type', + 'version', + // 'lists', + ]; + + const ruleParamsKeys = [ + ...commonRuleParamsKeys, + ...(isMlRule(ruleType) + ? ['anomaly_threshold', 'machine_learning_job_id'] + : ['index', 'filters', 'language', 'query', 'saved_id']), + ].sort(); + + return ruleParamsKeys; +}; + +export const getActionMessageParams = memoizeOne((ruleType: RuleType | undefined): string[] => { + if (!ruleType) { + return []; + } + const actionMessageRuleParams = getActionMessageRuleParams(ruleType); + + return [ + 'state.signals_count', + '{context.results_link}', + ...actionMessageRuleParams.map((param) => `context.rule.${param}`), + ]; +}); + +// typed as null not undefined as the initial state for this value is null. +export const userHasNoPermissions = (canUserCRUD: boolean | null): boolean => + canUserCRUD != null ? !canUserCRUD : false; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.tsx new file mode 100644 index 0000000000000..4d36a24781727 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.tsx @@ -0,0 +1,196 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useCallback, useRef, useState } from 'react'; +import { Redirect } from 'react-router-dom'; + +import { + usePrePackagedRules, + importRules, +} from '../../../../alerts/containers/detection_engine/rules'; +import { + DETECTION_ENGINE_PAGE_NAME, + getDetectionEngineUrl, + getCreateRuleUrl, +} from '../../../../common/components/link_to/redirect_to_detection_engine'; +import { DetectionEngineHeaderPage } from '../../../components/detection_engine_header_page'; +import { WrapperPage } from '../../../../common/components/wrapper_page'; +import { SpyRoute } from '../../../../common/utils/route/spy_routes'; + +import { useUserInfo } from '../../../components/user_info'; +import { AllRules } from './all'; +import { ImportDataModal } from '../../../../common/components/import_data_modal'; +import { ReadOnlyCallOut } from '../../../components/rules/read_only_callout'; +import { UpdatePrePackagedRulesCallOut } from '../../../components/rules/pre_packaged_rules/update_callout'; +import { getPrePackagedRuleStatus, redirectToDetections, userHasNoPermissions } from './helpers'; +import * as i18n from './translations'; + +type Func = (refreshPrePackagedRule?: boolean) => void; + +const RulesPageComponent: React.FC = () => { + const [showImportModal, setShowImportModal] = useState(false); + const refreshRulesData = useRef(null); + const { + loading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + hasIndexWrite, + } = useUserInfo(); + const { + createPrePackagedRules, + loading: prePackagedRuleLoading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, + rulesCustomInstalled, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + } = usePrePackagedRules({ + canUserCRUD, + hasIndexWrite, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + }); + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + const handleRefreshRules = useCallback(async () => { + if (refreshRulesData.current != null) { + refreshRulesData.current(true); + } + }, [refreshRulesData]); + + const handleCreatePrePackagedRules = useCallback(async () => { + if (createPrePackagedRules != null) { + await createPrePackagedRules(); + handleRefreshRules(); + } + }, [createPrePackagedRules, handleRefreshRules]); + + const handleRefetchPrePackagedRulesStatus = useCallback(() => { + if (refetchPrePackagedRulesStatus != null) { + refetchPrePackagedRulesStatus(); + } + }, [refetchPrePackagedRulesStatus]); + + const handleSetRefreshRulesData = useCallback((refreshRule: Func) => { + refreshRulesData.current = refreshRule; + }, []); + + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { + return ; + } + + return ( + <> + {userHasNoPermissions(canUserCRUD) && } + setShowImportModal(false)} + description={i18n.SELECT_RULE} + errorMessage={i18n.IMPORT_FAILED} + failedDetailed={i18n.IMPORT_FAILED_DETAILED} + importComplete={handleRefreshRules} + importData={importRules} + successMessage={i18n.SUCCESSFULLY_IMPORTED_RULES} + showCheckBox={true} + showModal={showImportModal} + submitBtnText={i18n.IMPORT_RULE_BTN_TITLE} + subtitle={i18n.INITIAL_PROMPT_TEXT} + title={i18n.IMPORT_RULE} + /> + + + + {prePackagedRuleStatus === 'ruleNotInstalled' && ( + + + {i18n.LOAD_PREPACKAGED_RULES} + + + )} + {prePackagedRuleStatus === 'someRuleUninstall' && ( + + + {i18n.RELOAD_MISSING_PREPACKAGED_RULES(rulesNotInstalled ?? 0)} + + + )} + + { + setShowImportModal(true); + }} + > + {i18n.IMPORT_RULE} + + + + + {i18n.ADD_NEW_RULE} + + + + + {prePackagedRuleStatus === 'ruleNeedUpdate' && ( + + )} + + + + + + ); +}; + +export const RulesPage = React.memo(RulesPageComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/translations.ts new file mode 100644 index 0000000000000..32f59093e6a04 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/translations.ts @@ -0,0 +1,530 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const BACK_TO_ALERTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.backOptionsHeader', + { + defaultMessage: 'Back to alerts', + } +); + +export const IMPORT_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.importRuleTitle', + { + defaultMessage: 'Import rule…', + } +); + +export const ADD_NEW_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.addNewRuleTitle', + { + defaultMessage: 'Create new rule', + } +); + +export const PAGE_TITLE = i18n.translate('xpack.securitySolution.detectionEngine.rules.pageTitle', { + defaultMessage: 'Detection rules', +}); + +export const ADD_PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.addPageTitle', + { + defaultMessage: 'Create', + } +); + +export const EDIT_PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.editPageTitle', + { + defaultMessage: 'Edit', + } +); + +export const REFRESH = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.refreshTitle', + { + defaultMessage: 'Refresh', + } +); + +export const BATCH_ACTIONS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActionsTitle', + { + defaultMessage: 'Bulk actions', + } +); + +export const ACTIVE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.activeRuleDescription', + { + defaultMessage: 'active', + } +); + +export const INACTIVE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.inactiveRuleDescription', + { + defaultMessage: 'inactive', + } +); + +export const BATCH_ACTION_ACTIVATE_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.activateSelectedTitle', + { + defaultMessage: 'Activate selected', + } +); + +export const BATCH_ACTION_ACTIVATE_SELECTED_ERROR = (totalRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.activateSelectedErrorTitle', + { + values: { totalRules }, + defaultMessage: 'Error activating {totalRules, plural, =1 {rule} other {rules}}…', + } + ); + +export const BATCH_ACTION_DEACTIVATE_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deactivateSelectedTitle', + { + defaultMessage: 'Deactivate selected', + } +); + +export const BATCH_ACTION_DEACTIVATE_SELECTED_ERROR = (totalRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deactivateSelectedErrorTitle', + { + values: { totalRules }, + defaultMessage: 'Error deactivating {totalRules, plural, =1 {rule} other {rules}}…', + } + ); + +export const BATCH_ACTION_EXPORT_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.exportSelectedTitle', + { + defaultMessage: 'Export selected', + } +); + +export const BATCH_ACTION_DUPLICATE_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.duplicateSelectedTitle', + { + defaultMessage: 'Duplicate selected…', + } +); + +export const BATCH_ACTION_DELETE_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deleteSelectedTitle', + { + defaultMessage: 'Delete selected…', + } +); + +export const BATCH_ACTION_DELETE_SELECTED_IMMUTABLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deleteSelectedImmutableTitle', + { + defaultMessage: 'Selection contains immutable rules which cannot be deleted', + } +); + +export const BATCH_ACTION_DELETE_SELECTED_ERROR = (totalRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deleteSelectedErrorTitle', + { + values: { totalRules }, + defaultMessage: 'Error deleting {totalRules, plural, =1 {rule} other {rules}}…', + } + ); + +export const EXPORT_FILENAME = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.exportFilenameTitle', + { + defaultMessage: 'rules_export', + } +); + +export const SUCCESSFULLY_EXPORTED_RULES = (totalRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.successfullyExportedRulesTitle', + { + values: { totalRules }, + defaultMessage: + 'Successfully exported {totalRules, plural, =0 {all rules} =1 {{totalRules} rule} other {{totalRules} rules}}', + } + ); + +export const ALL_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.tableTitle', + { + defaultMessage: 'All rules', + } +); + +export const SEARCH_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.searchAriaLabel', + { + defaultMessage: 'Search rules', + } +); + +export const SEARCH_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.searchPlaceholder', + { + defaultMessage: 'e.g. rule name', + } +); + +export const SHOWING_RULES = (totalRules: number) => + i18n.translate('xpack.securitySolution.detectionEngine.rules.allRules.showingRulesTitle', { + values: { totalRules }, + defaultMessage: 'Showing {totalRules} {totalRules, plural, =1 {rule} other {rules}}', + }); + +export const SELECTED_RULES = (selectedRules: number) => + i18n.translate('xpack.securitySolution.detectionEngine.rules.allRules.selectedRulesTitle', { + values: { selectedRules }, + defaultMessage: 'Selected {selectedRules} {selectedRules, plural, =1 {rule} other {rules}}', + }); + +export const EDIT_RULE_SETTINGS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsDescription', + { + defaultMessage: 'Edit rule settings', + } +); + +export const DUPLICATE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.duplicateTitle', + { + defaultMessage: 'Duplicate', + } +); + +export const DUPLICATE_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.duplicateRuleDescription', + { + defaultMessage: 'Duplicate rule…', + } +); + +export const SUCCESSFULLY_DUPLICATED_RULES = (totalRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.successfullyDuplicatedRulesTitle', + { + values: { totalRules }, + defaultMessage: + 'Successfully duplicated {totalRules, plural, =1 {{totalRules} rule} other {{totalRules} rules}}', + } + ); + +export const DUPLICATE_RULE_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.duplicateRuleErrorDescription', + { + defaultMessage: 'Error duplicating rule…', + } +); + +export const EXPORT_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.exportRuleDescription', + { + defaultMessage: 'Export rule', + } +); + +export const DELETE_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.deleteeRuleDescription', + { + defaultMessage: 'Delete rule…', + } +); + +export const COLUMN_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.ruleTitle', + { + defaultMessage: 'Rule', + } +); + +export const COLUMN_RISK_SCORE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.riskScoreTitle', + { + defaultMessage: 'Risk score', + } +); + +export const COLUMN_SEVERITY = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.severityTitle', + { + defaultMessage: 'Severity', + } +); + +export const COLUMN_LAST_COMPLETE_RUN = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.lastRunTitle', + { + defaultMessage: 'Last run', + } +); + +export const COLUMN_LAST_RESPONSE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.lastResponseTitle', + { + defaultMessage: 'Last response', + } +); + +export const COLUMN_TAGS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.tagsTitle', + { + defaultMessage: 'Tags', + } +); + +export const COLUMN_ACTIVATE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.activateTitle', + { + defaultMessage: 'Activated', + } +); + +export const COLUMN_INDEXING_TIMES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.indexingTimes', + { + defaultMessage: 'Indexing Time (ms)', + } +); + +export const COLUMN_QUERY_TIMES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.queryTimes', + { + defaultMessage: 'Query Time (ms)', + } +); + +export const COLUMN_GAP = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.gap', + { + defaultMessage: 'Gap (if any)', + } +); + +export const COLUMN_LAST_LOOKBACK_DATE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.lastLookBackDate', + { + defaultMessage: 'Last Look-Back Date', + } +); + +export const RULES_TAB = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.tabs.rules', + { + defaultMessage: 'Rules', + } +); + +export const MONITORING_TAB = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.tabs.monitoring', + { + defaultMessage: 'Monitoring', + } +); + +export const CUSTOM_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.filters.customRulesTitle', + { + defaultMessage: 'Custom rules', + } +); + +export const ELASTIC_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.filters.elasticRulesTitle', + { + defaultMessage: 'Elastic rules', + } +); + +export const TAGS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.filters.tagsLabel', + { + defaultMessage: 'Tags', + } +); + +export const NO_TAGS_AVAILABLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.filters.noTagsAvailableDescription', + { + defaultMessage: 'No tags available', + } +); + +export const NO_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.filters.noRulesTitle', + { + defaultMessage: 'No rules found', + } +); + +export const NO_RULES_BODY = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.filters.noRulesBodyTitle', + { + defaultMessage: "We weren't able to find any rules with the above filters.", + } +); + +export const DEFINE_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.defineRuleTitle', + { + defaultMessage: 'Define rule', + } +); + +export const ABOUT_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.aboutRuleTitle', + { + defaultMessage: 'About rule', + } +); + +export const SCHEDULE_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.scheduleRuleTitle', + { + defaultMessage: 'Schedule rule', + } +); + +export const RULE_ACTIONS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.ruleActionsTitle', + { + defaultMessage: 'Rule actions', + } +); + +export const DEFINITION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.stepDefinitionTitle', + { + defaultMessage: 'Definition', + } +); + +export const ABOUT = i18n.translate('xpack.securitySolution.detectionEngine.rules.stepAboutTitle', { + defaultMessage: 'About', +}); + +export const SCHEDULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.stepScheduleTitle', + { + defaultMessage: 'Schedule', + } +); + +export const ACTIONS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.stepActionsTitle', + { + defaultMessage: 'Actions', + } +); + +export const OPTIONAL_FIELD = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.optionalFieldDescription', + { + defaultMessage: 'Optional', + } +); + +export const CONTINUE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.continueButtonTitle', + { + defaultMessage: 'Continue', + } +); + +export const UPDATE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.updateButtonTitle', + { + defaultMessage: 'Update', + } +); + +export const DELETE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.deleteDescription', + { + defaultMessage: 'Delete', + } +); + +export const LOAD_PREPACKAGED_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesButton', + { + defaultMessage: 'Load Elastic prebuilt rules', + } +); + +export const RELOAD_MISSING_PREPACKAGED_RULES = (missingRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton', + { + values: { missingRules }, + defaultMessage: + 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + } + ); + +export const IMPORT_RULE_BTN_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.importRuleTitle', + { + defaultMessage: 'Import rule', + } +); + +export const SELECT_RULE = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.selectRuleDescription', + { + defaultMessage: 'Select a SIEM rule (as exported from the Detection Engine view) to import', + } +); + +export const INITIAL_PROMPT_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.initialPromptTextDescription', + { + defaultMessage: 'Select or drag and drop a valid rules_export.ndjson file', + } +); + +export const OVERWRITE_WITH_SAME_NAME = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.overwriteDescription', + { + defaultMessage: 'Automatically overwrite saved objects with the same rule ID', + } +); + +export const SUCCESSFULLY_IMPORTED_RULES = (totalRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.successfullyImportedRulesTitle', + { + values: { totalRules }, + defaultMessage: + 'Successfully imported {totalRules} {totalRules, plural, =1 {rule} other {rules}}', + } + ); + +export const IMPORT_FAILED = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedTitle', + { + defaultMessage: 'Failed to import rules', + } +); + +export const IMPORT_FAILED_DETAILED = (ruleId: string, statusCode: number, message: string) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedDetailedTitle', + { + values: { ruleId, statusCode, message }, + defaultMessage: 'Rule ID: {ruleId}\n Status Code: {statusCode}\n Message: {message}', + } + ); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/types.ts new file mode 100644 index 0000000000000..5f81409010a28 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/types.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RuleAlertAction, RuleType } from '../../../../../common/detection_engine/types'; +import { AlertAction } from '../../../../../../alerts/common'; +import { Filter } from '../../../../../../../../src/plugins/data/common'; +import { FormData, FormHook } from '../../../../shared_imports'; +import { FieldValueQueryBar } from '../../../components/rules/query_bar'; +import { FieldValueTimeline } from '../../../components/rules/pick_timeline'; + +export interface EuiBasicTableSortTypes { + field: string; + direction: 'asc' | 'desc'; +} + +export interface EuiBasicTableOnChange { + page: { + index: number; + size: number; + }; + sort?: EuiBasicTableSortTypes; +} + +export enum RuleStep { + defineRule = 'define-rule', + aboutRule = 'about-rule', + scheduleRule = 'schedule-rule', + ruleActions = 'rule-actions', +} +export type RuleStatusType = 'passive' | 'active' | 'valid'; + +export interface RuleStepData { + data: unknown; + isValid: boolean; +} + +export interface RuleStepProps { + addPadding?: boolean; + descriptionColumns?: 'multi' | 'single' | 'singleSplit'; + setStepData?: (step: RuleStep, data: unknown, isValid: boolean) => void; + isReadOnlyView: boolean; + isUpdateView?: boolean; + isLoading: boolean; + resizeParentContainer?: (height: number) => void; + setForm?: (step: RuleStep, form: FormHook) => void; +} + +interface StepRuleData { + isNew: boolean; +} +export interface AboutStepRule extends StepRuleData { + name: string; + description: string; + severity: string; + riskScore: number; + references: string[]; + falsePositives: string[]; + tags: string[]; + threat: IMitreEnterpriseAttack[]; + note: string; +} + +export interface AboutStepRuleDetails { + note: string; + description: string; +} + +export interface DefineStepRule extends StepRuleData { + anomalyThreshold: number; + index: string[]; + machineLearningJobId: string; + queryBar: FieldValueQueryBar; + ruleType: RuleType; + timeline: FieldValueTimeline; +} + +export interface ScheduleStepRule extends StepRuleData { + interval: string; + from: string; + to?: string; +} + +export interface ActionsStepRule extends StepRuleData { + actions: AlertAction[]; + enabled: boolean; + kibanaSiemAppUrl?: string; + throttle?: string | null; +} + +export interface DefineStepRuleJson { + anomaly_threshold?: number; + index?: string[]; + filters?: Filter[]; + machine_learning_job_id?: string; + saved_id?: string; + query?: string; + language?: string; + timeline_id?: string; + timeline_title?: string; + type: RuleType; +} + +export interface AboutStepRuleJson { + name: string; + description: string; + severity: string; + risk_score: number; + references: string[]; + false_positives: string[]; + tags: string[]; + threat: IMitreEnterpriseAttack[]; + note?: string; +} + +export interface ScheduleStepRuleJson { + interval: string; + from: string; + to?: string; + meta?: unknown; +} + +export interface ActionsStepRuleJson { + actions: RuleAlertAction[]; + enabled: boolean; + throttle?: string | null; + meta?: unknown; +} + +export interface IMitreAttack { + id: string; + name: string; + reference: string; +} +export interface IMitreEnterpriseAttack { + framework: string; + tactic: IMitreAttack; + technique: IMitreAttack[]; +} diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.test.ts new file mode 100644 index 0000000000000..3d2f2dc03946a --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getBreadcrumbs } from './utils'; + +describe('getBreadcrumbs', () => { + it('returns default value for incorrect params', () => { + expect( + getBreadcrumbs( + { + pageName: 'pageName', + detailName: 'detailName', + tabName: undefined, + search: '', + pathName: 'pathName', + }, + [] + ) + ).toEqual([{ href: '#/link-to/detections', text: 'Alerts' }]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.ts new file mode 100644 index 0000000000000..e5cdbd7123ff4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash/fp'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ChromeBreadcrumb } from '../../../../../../../../src/core/public'; +import { + getDetectionEngineUrl, + getDetectionEngineTabUrl, + getRulesUrl, + getRuleDetailsUrl, + getCreateRuleUrl, + getEditRuleUrl, +} from '../../../../common/components/link_to/redirect_to_detection_engine'; +import * as i18nDetections from '../translations'; +import * as i18nRules from './translations'; +import { RouteSpyState } from '../../../../common/utils/route/types'; + +const getTabBreadcrumb = (pathname: string, search: string[]) => { + const tabPath = pathname.split('/')[2]; + + if (tabPath === 'alerts') { + return { + text: i18nDetections.ALERT, + href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } + + if (tabPath === 'rules') { + return { + text: i18nRules.PAGE_TITLE, + href: `${getRulesUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } +}; + +const isRuleCreatePage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/create'); + +const isRuleEditPage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/edit'); + +export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeBreadcrumb[] => { + let breadcrumb = [ + { + text: i18nDetections.PAGE_TITLE, + href: `${getDetectionEngineUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }, + ]; + + const tabBreadcrumb = getTabBreadcrumb(params.pathName, search); + + if (tabBreadcrumb) { + breadcrumb = [...breadcrumb, tabBreadcrumb]; + } + + if (params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: params.state.ruleName, + href: `${getRuleDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleCreatePage(params.pathName)) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.ADD_PAGE_TITLE, + href: `${getCreateRuleUrl()}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleEditPage(params.pathName) && params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.EDIT_PAGE_TITLE, + href: `${getEditRuleUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + return breadcrumb; +}; diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/translations.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/translations.ts new file mode 100644 index 0000000000000..a19422d0de2c1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/translations.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.detectionsPageTitle', + { + defaultMessage: 'Alerts', + } +); + +export const LAST_ALERT = i18n.translate('xpack.securitySolution.detectionEngine.lastSignalTitle', { + defaultMessage: 'Last alert', +}); + +export const TOTAL_SIGNAL = i18n.translate( + 'xpack.securitySolution.detectionEngine.totalSignalTitle', + { + defaultMessage: 'Total', + } +); + +export const SIGNAL = i18n.translate('xpack.securitySolution.detectionEngine.signalTitle', { + defaultMessage: 'Detected alerts', +}); + +export const ALERT = i18n.translate('xpack.securitySolution.detectionEngine.alertTitle', { + defaultMessage: 'External alerts', +}); + +export const BUTTON_MANAGE_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.buttonManageRules', + { + defaultMessage: 'Manage detection rules', + } +); + +export const PANEL_SUBTITLE_SHOWING = i18n.translate( + 'xpack.securitySolution.detectionEngine.panelSubtitleShowing', + { + defaultMessage: 'Showing', + } +); + +export const EMPTY_TITLE = i18n.translate('xpack.securitySolution.detectionEngine.emptyTitle', { + defaultMessage: + 'It looks like you don’t have any indices relevant to the detection engine in the SIEM application', +}); + +export const EMPTY_ACTION_PRIMARY = i18n.translate( + 'xpack.securitySolution.detectionEngine.emptyActionPrimary', + { + defaultMessage: 'View setup instructions', + } +); + +export const EMPTY_ACTION_SECONDARY = i18n.translate( + 'xpack.securitySolution.detectionEngine.emptyActionSecondary', + { + defaultMessage: 'Go to documentation', + } +); + +export const NO_INDEX_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.noIndexTitle', + { + defaultMessage: 'Let’s set up your detection engine', + } +); + +export const NO_INDEX_MSG_BODY = i18n.translate( + 'xpack.securitySolution.detectionEngine.noIndexMsgBody', + { + defaultMessage: + 'To use the detection engine, a user with the required cluster and index privileges must first access this page. For more help, contact your administrator.', + } +); + +export const GO_TO_DOCUMENTATION = i18n.translate( + 'xpack.securitySolution.detectionEngine.goToDocumentationButton', + { + defaultMessage: 'View documentation', + } +); + +export const USER_UNAUTHENTICATED_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.userUnauthenticatedTitle', + { + defaultMessage: 'Detection engine permissions required', + } +); + +export const USER_UNAUTHENTICATED_MSG_BODY = i18n.translate( + 'xpack.securitySolution.detectionEngine.userUnauthenticatedMsgBody', + { + defaultMessage: + 'You do not have the required permissions for viewing the detection engine. For more help, contact your administrator.', + } +); + +export const ML_RULES_DISABLED_MESSAGE = i18n.translate( + 'xpack.securitySolution.detectionEngine.mlRulesDisabledMessageTitle', + { + defaultMessage: 'ML rules require Platinum License and ML Admin Permissions', + } +); + +export const ML_RULES_UNAVAILABLE = (totalRules: number) => + i18n.translate('xpack.securitySolution.detectionEngine.mlUnavailableTitle', { + values: { totalRules }, + defaultMessage: + '{totalRules} {totalRules, plural, =1 {rule requires} other {rules require}} Machine Learning to enable.', + }); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts rename to x-pack/plugins/security_solution/public/alerts/pages/detection_engine/types.ts diff --git a/x-pack/plugins/siem/public/alerts/routes.tsx b/x-pack/plugins/security_solution/public/alerts/routes.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/routes.tsx rename to x-pack/plugins/security_solution/public/alerts/routes.tsx diff --git a/x-pack/plugins/siem/public/app/404.tsx b/x-pack/plugins/security_solution/public/app/404.tsx similarity index 88% rename from x-pack/plugins/siem/public/app/404.tsx rename to x-pack/plugins/security_solution/public/app/404.tsx index 6a1b5c56dc853..b24cad5ad5b4b 100644 --- a/x-pack/plugins/siem/public/app/404.tsx +++ b/x-pack/plugins/security_solution/public/app/404.tsx @@ -12,7 +12,7 @@ import { WrapperPage } from '../common/components/wrapper_page'; export const NotFoundPage = React.memo(() => ( diff --git a/x-pack/plugins/siem/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx similarity index 84% rename from x-pack/plugins/siem/public/app/app.tsx rename to x-pack/plugins/security_solution/public/app/app.tsx index 732b1883b9b77..50a24ef012b8b 100644 --- a/x-pack/plugins/siem/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -29,6 +29,7 @@ import { PageRouter } from './routes'; import { createStore, createInitialState } from '../common/store'; import { GlobalToaster, ManageGlobalToaster } from '../common/components/toasters'; import { MlCapabilitiesProvider } from '../common/components/ml/permissions/ml_capabilities_provider'; +import { ManageGlobalTimeline } from '../timelines/components/manage_timeline'; import { ApolloClientContext } from '../common/utils/apollo_context'; import { SecuritySubPlugins } from './types'; @@ -49,19 +50,21 @@ const AppPluginRootComponent: React.FC = ({ history, }) => ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); diff --git a/x-pack/plugins/siem/public/app/home/home_navigations.tsx b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx similarity index 85% rename from x-pack/plugins/siem/public/app/home/home_navigations.tsx rename to x-pack/plugins/security_solution/public/app/home/home_navigations.tsx index 2eed64a2b26e5..bb9e99326182f 100644 --- a/x-pack/plugins/siem/public/app/home/home_navigations.tsx +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx @@ -14,6 +14,7 @@ import { } from '../../common/components/link_to'; import * as i18n from './translations'; import { SiemPageName, SiemNavTab } from '../types'; +import { getManagementUrl } from '../../management'; export const navTabs: SiemNavTab = { [SiemPageName.overview]: { @@ -58,4 +59,11 @@ export const navTabs: SiemNavTab = { disabled: false, urlKey: 'case', }, + [SiemPageName.management]: { + id: SiemPageName.management, + name: i18n.MANAGEMENT, + href: getManagementUrl({ name: 'default' }), + disabled: false, + urlKey: SiemPageName.management, + }, }; diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx new file mode 100644 index 0000000000000..b5aae0b28c871 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo } from 'react'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import styled from 'styled-components'; + +import { useThrottledResizeObserver } from '../../common/components/utils'; +import { DragDropContextWrapper } from '../../common/components/drag_and_drop/drag_drop_context_wrapper'; +import { Flyout } from '../../timelines/components/flyout'; +import { HeaderGlobal } from '../../common/components/header_global'; +import { HelpMenu } from '../../common/components/help_menu'; +import { LinkToPage } from '../../common/components/link_to'; +import { MlHostConditionalContainer } from '../../common/components/ml/conditional_links/ml_host_conditional_container'; +import { MlNetworkConditionalContainer } from '../../common/components/ml/conditional_links/ml_network_conditional_container'; +import { AutoSaveWarningMsg } from '../../timelines/components/timeline/auto_save_warning'; +import { UseUrlState } from '../../common/components/url_state'; +import { + WithSource, + indicesExistOrDataTemporarilyUnavailable, +} from '../../common/containers/source'; +import { SpyRoute } from '../../common/utils/route/spy_routes'; +import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline'; +import { NotFoundPage } from '../404'; +import { navTabs } from './home_navigations'; +import { SiemPageName } from '../types'; + +const WrappedByAutoSizer = styled.div` + height: 100%; +`; +WrappedByAutoSizer.displayName = 'WrappedByAutoSizer'; + +const Main = styled.main` + height: 100%; +`; +Main.displayName = 'Main'; + +const usersViewing = ['elastic']; // TODO: get the users viewing this timeline from Elasticsearch (persistance) + +/** the global Kibana navigation at the top of every page */ +const globalHeaderHeightPx = 48; + +const calculateFlyoutHeight = ({ + globalHeaderSize, + windowHeight, +}: { + globalHeaderSize: number; + windowHeight: number; +}): number => Math.max(0, windowHeight - globalHeaderSize); + +interface HomePageProps { + subPlugins: JSX.Element[]; +} + +export const HomePage: React.FC = ({ subPlugins }) => { + const { ref: measureRef, height: windowHeight = 0 } = useThrottledResizeObserver(); + const flyoutHeight = useMemo( + () => + calculateFlyoutHeight({ + globalHeaderSize: globalHeaderHeightPx, + windowHeight, + }), + [windowHeight] + ); + + const [showTimeline] = useShowTimeline(); + + return ( + + + +
    + + {({ browserFields, indexPattern, indicesExist }) => ( + + + {indicesExistOrDataTemporarilyUnavailable(indicesExist) && showTimeline && ( + <> + + + + )} + + + + {subPlugins} + } /> + ( + + )} + /> + ( + + )} + /> + } /> + + + )} + +
    + + + + +
    + ); +}; + +HomePage.displayName = 'HomePage'; diff --git a/x-pack/plugins/siem/public/app/home/setup.tsx b/x-pack/plugins/security_solution/public/app/home/setup.tsx similarity index 90% rename from x-pack/plugins/siem/public/app/home/setup.tsx rename to x-pack/plugins/security_solution/public/app/home/setup.tsx index a88b6a36100db..5b977a83302a9 100644 --- a/x-pack/plugins/siem/public/app/home/setup.tsx +++ b/x-pack/plugins/security_solution/public/app/home/setup.tsx @@ -13,11 +13,11 @@ export const Setup: React.FunctionComponent<{ notifications: NotificationsStart; }> = ({ ingestManager, notifications }) => { React.useEffect(() => { - const defaultText = i18n.translate('xpack.siem.endpoint.ingestToastMessage', { + const defaultText = i18n.translate('xpack.securitySolution.endpoint.ingestToastMessage', { defaultMessage: 'Ingest Manager failed during its setup.', }); - const title = i18n.translate('xpack.siem.endpoint.ingestToastTitle', { + const title = i18n.translate('xpack.securitySolution.endpoint.ingestToastTitle', { defaultMessage: 'App failed to initialize', }); diff --git a/x-pack/plugins/security_solution/public/app/home/translations.ts b/x-pack/plugins/security_solution/public/app/home/translations.ts new file mode 100644 index 0000000000000..ccf927eba20c9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/translations.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const OVERVIEW = i18n.translate('xpack.securitySolution.navigation.overview', { + defaultMessage: 'Overview', +}); + +export const HOSTS = i18n.translate('xpack.securitySolution.navigation.hosts', { + defaultMessage: 'Hosts', +}); + +export const NETWORK = i18n.translate('xpack.securitySolution.navigation.network', { + defaultMessage: 'Network', +}); + +export const DETECTION_ENGINE = i18n.translate( + 'xpack.securitySolution.navigation.detectionEngine', + { + defaultMessage: 'Detections', + } +); + +export const TIMELINES = i18n.translate('xpack.securitySolution.navigation.timelines', { + defaultMessage: 'Timelines', +}); + +export const CASE = i18n.translate('xpack.securitySolution.navigation.case', { + defaultMessage: 'Cases', +}); + +export const MANAGEMENT = i18n.translate('xpack.securitySolution.navigation.management', { + defaultMessage: 'Management', +}); diff --git a/x-pack/plugins/siem/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/app/index.tsx rename to x-pack/plugins/security_solution/public/app/index.tsx diff --git a/x-pack/plugins/siem/public/app/routes.tsx b/x-pack/plugins/security_solution/public/app/routes.tsx similarity index 100% rename from x-pack/plugins/siem/public/app/routes.tsx rename to x-pack/plugins/security_solution/public/app/routes.tsx diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts new file mode 100644 index 0000000000000..4b00dee3b7510 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Reducer, + AnyAction, + Middleware, + Dispatch, + PreloadedState, + StateFromReducersMapObject, + CombinedState, +} from 'redux'; + +import { NavTab } from '../common/components/navigation/types'; +import { State, SubPluginsInitReducer } from '../common/store'; +import { Immutable } from '../../common/endpoint/types'; +import { AppAction } from '../common/store/actions'; + +export enum SiemPageName { + overview = 'overview', + hosts = 'hosts', + network = 'network', + detections = 'detections', + timelines = 'timelines', + case = 'case', + management = 'management', +} + +export type SiemNavTabKey = + | SiemPageName.overview + | SiemPageName.hosts + | SiemPageName.network + | SiemPageName.detections + | SiemPageName.timelines + | SiemPageName.case + | SiemPageName.management; + +export type SiemNavTab = Record; + +export interface SecuritySubPluginStore { + initialState: Record; + reducer: Record>; + middleware?: Array>>>; +} + +export interface SecuritySubPlugin { + routes: React.ReactElement[]; +} + +type SecuritySubPluginKeyStore = + | 'hosts' + | 'network' + | 'timeline' + | 'hostList' + | 'alertList' + | 'management'; + +/** + * Returned by the various 'SecuritySubPlugin' classes from the `start` method. + */ +export interface SecuritySubPluginWithStore + extends SecuritySubPlugin { + store: SecuritySubPluginStore; +} + +export interface SecuritySubPlugins extends SecuritySubPlugin { + store: { + initialState: PreloadedState< + CombinedState< + StateFromReducersMapObject< + /** SubPluginsInitReducer, being an interface, will not work in `StateFromReducersMapObject`. + * Picking its keys does the trick. + **/ + Pick + > + > + >; + reducer: SubPluginsInitReducer; + middlewares: Array>>>; + }; +} diff --git a/x-pack/plugins/siem/public/cases/components/__mock__/form.ts b/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/components/__mock__/form.ts rename to x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts diff --git a/x-pack/plugins/siem/public/cases/components/__mock__/router.ts b/x-pack/plugins/security_solution/public/cases/components/__mock__/router.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/components/__mock__/router.ts rename to x-pack/plugins/security_solution/public/cases/components/__mock__/router.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx new file mode 100644 index 0000000000000..5da75033d17fa --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { AddComment } from '.'; +import { TestProviders } from '../../../common/mock'; +import { getFormMock } from '../__mock__/form'; +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; + +import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; +import { usePostComment } from '../../containers/use_post_comment'; +import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { wait } from '../../../common/lib/helpers'; + +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); + +jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'); +jest.mock('../../containers/use_post_comment'); + +export const useFormMock = useForm as jest.Mock; + +const useInsertTimelineMock = useInsertTimeline as jest.Mock; +const usePostCommentMock = usePostComment as jest.Mock; + +const onCommentSaving = jest.fn(); +const onCommentPosted = jest.fn(); +const postComment = jest.fn(); +const handleCursorChange = jest.fn(); +const handleOnTimelineChange = jest.fn(); + +const addCommentProps = { + caseId: '1234', + disabled: false, + insertQuote: null, + onCommentSaving, + onCommentPosted, + showLoading: false, +}; + +const defaultInsertTimeline = { + cursorPosition: { + start: 0, + end: 0, + }, + handleCursorChange, + handleOnTimelineChange, +}; + +const defaultPostCommment = { + isLoading: false, + isError: false, + postComment, +}; +const sampleData = { + comment: 'what a cool comment', +}; +describe('AddComment ', () => { + const formHookMock = getFormMock(sampleData); + + beforeEach(() => { + jest.resetAllMocks(); + useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); + usePostCommentMock.mockImplementation(() => defaultPostCommment); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); + }); + + it('should post comment on submit click', async () => { + const wrapper = mount( + + + + + + ); + expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy(); + + wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click'); + await wait(); + expect(onCommentSaving).toBeCalled(); + expect(postComment).toBeCalledWith(sampleData, onCommentPosted); + expect(formHookMock.reset).toBeCalled(); + }); + + it('should render spinner and disable submit when loading', () => { + usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true })); + const wrapper = mount( + + + + + + ); + expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled') + ).toBeTruthy(); + }); + + it('should disable submit button when disabled prop passed', () => { + usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true })); + const wrapper = mount( + + + + + + ); + expect( + wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled') + ).toBeTruthy(); + }); + + it('should insert a quote if one is available', () => { + const sampleQuote = 'what a cool quote'; + mount( + + + + + + ); + + expect(formHookMock.setFieldValue).toBeCalledWith( + 'comment', + `${sampleData.comment}\n\n${sampleQuote}` + ); + }); +}); diff --git a/x-pack/plugins/siem/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/add_comment/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx diff --git a/x-pack/plugins/siem/public/cases/components/add_comment/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/add_comment/schema.tsx rename to x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx diff --git a/x-pack/plugins/siem/public/cases/components/add_comment/translations.ts b/x-pack/plugins/security_solution/public/cases/components/add_comment/translations.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/components/add_comment/translations.ts rename to x-pack/plugins/security_solution/public/cases/components/add_comment/translations.ts diff --git a/x-pack/plugins/siem/public/cases/components/all_cases/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/all_cases/actions.tsx rename to x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx new file mode 100644 index 0000000000000..9db8adbf9346f --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { ExternalServiceColumn } from './columns'; + +import { useGetCasesMockState } from '../../containers/mock'; + +describe('ExternalServiceColumn ', () => { + it('Not pushed render', () => { + const wrapper = mount( + + ); + expect( + wrapper.find(`[data-test-subj="case-table-column-external-notPushed"]`).last().exists() + ).toBeTruthy(); + }); + it('Up to date', () => { + const wrapper = mount( + + ); + expect( + wrapper.find(`[data-test-subj="case-table-column-external-upToDate"]`).last().exists() + ).toBeTruthy(); + }); + it('Needs update', () => { + const wrapper = mount( + + ); + expect( + wrapper.find(`[data-test-subj="case-table-column-external-requiresUpdate"]`).last().exists() + ).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/siem/public/cases/components/all_cases/columns.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/all_cases/columns.tsx rename to x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx new file mode 100644 index 0000000000000..e3f4fee15ce68 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -0,0 +1,305 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import moment from 'moment-timezone'; +import { AllCases } from '.'; +import { TestProviders } from '../../../common/mock'; +import { useGetCasesMockState } from '../../containers/mock'; +import * as i18n from './translations'; + +import { getEmptyTagValue } from '../../../common/components/empty_value'; +import { useDeleteCases } from '../../containers/use_delete_cases'; +import { useGetCases } from '../../containers/use_get_cases'; +import { useGetCasesStatus } from '../../containers/use_get_cases_status'; +import { useUpdateCases } from '../../containers/use_bulk_update_case'; +import { getCasesColumns } from './columns'; + +jest.mock('../../containers/use_bulk_update_case'); +jest.mock('../../containers/use_delete_cases'); +jest.mock('../../containers/use_get_cases'); +jest.mock('../../containers/use_get_cases_status'); + +const useDeleteCasesMock = useDeleteCases as jest.Mock; +const useGetCasesMock = useGetCases as jest.Mock; +const useGetCasesStatusMock = useGetCasesStatus as jest.Mock; +const useUpdateCasesMock = useUpdateCases as jest.Mock; + +describe('AllCases', () => { + const dispatchResetIsDeleted = jest.fn(); + const dispatchResetIsUpdated = jest.fn(); + const dispatchUpdateCaseProperty = jest.fn(); + const handleOnDeleteConfirm = jest.fn(); + const handleToggleModal = jest.fn(); + const refetchCases = jest.fn(); + const setFilters = jest.fn(); + const setQueryParams = jest.fn(); + const setSelectedCases = jest.fn(); + const updateBulkStatus = jest.fn(); + const fetchCasesStatus = jest.fn(); + const emptyTag = getEmptyTagValue().props.children; + + const defaultGetCases = { + ...useGetCasesMockState, + dispatchUpdateCaseProperty, + refetchCases, + setFilters, + setQueryParams, + setSelectedCases, + }; + const defaultDeleteCases = { + dispatchResetIsDeleted, + handleOnDeleteConfirm, + handleToggleModal, + isDeleted: false, + isDisplayConfirmDeleteModal: false, + isLoading: false, + }; + const defaultCasesStatus = { + countClosedCases: 0, + countOpenCases: 5, + fetchCasesStatus, + isError: false, + isLoading: true, + }; + const defaultUpdateCases = { + isUpdated: false, + isLoading: false, + isError: false, + dispatchResetIsUpdated, + updateBulkStatus, + }; + /* eslint-disable no-console */ + // Silence until enzyme fixed to use ReactTestUtils.act() + const originalError = console.error; + beforeAll(() => { + console.error = jest.fn(); + }); + afterAll(() => { + console.error = originalError; + }); + /* eslint-enable no-console */ + beforeEach(() => { + jest.resetAllMocks(); + useUpdateCasesMock.mockImplementation(() => defaultUpdateCases); + useGetCasesMock.mockImplementation(() => defaultGetCases); + useDeleteCasesMock.mockImplementation(() => defaultDeleteCases); + useGetCasesStatusMock.mockImplementation(() => defaultCasesStatus); + moment.tz.setDefault('UTC'); + }); + it('should render AllCases', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().prop('href')).toEqual( + `#/link-to/case/${useGetCasesMockState.data.cases[0].id}?timerange=(global:(linkTo:!(timeline),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)),timeline:(linkTo:!(global),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)))` + ); + expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().text()).toEqual( + useGetCasesMockState.data.cases[0].title + ); + expect( + wrapper.find(`span[data-test-subj="case-table-column-tags-0"]`).first().prop('title') + ).toEqual(useGetCasesMockState.data.cases[0].tags[0]); + expect(wrapper.find(`[data-test-subj="case-table-column-createdBy"]`).first().text()).toEqual( + useGetCasesMockState.data.cases[0].createdBy.fullName + ); + expect( + wrapper + .find(`[data-test-subj="case-table-column-createdAt"]`) + .first() + .childAt(0) + .prop('value') + ).toBe(useGetCasesMockState.data.cases[0].createdAt); + expect(wrapper.find(`[data-test-subj="case-table-case-count"]`).first().text()).toEqual( + 'Showing 10 cases' + ); + }); + it('should render empty fields', () => { + useGetCasesMock.mockImplementation(() => ({ + ...defaultGetCases, + data: { + ...defaultGetCases.data, + cases: [ + { + ...defaultGetCases.data.cases[0], + id: null, + createdAt: null, + createdBy: null, + tags: null, + title: null, + totalComment: null, + }, + ], + }, + })); + const wrapper = mount( + + + + ); + const checkIt = (columnName: string, key: number) => { + const column = wrapper.find('[data-test-subj="cases-table"] tbody .euiTableRowCell').at(key); + if (columnName === i18n.ACTIONS) { + return; + } + expect(column.find('.euiTableRowCell--hideForDesktop').text()).toEqual(columnName); + expect(column.find('span').text()).toEqual(emptyTag); + }; + getCasesColumns([], 'open').map((i, key) => i.name != null && checkIt(`${i.name}`, key)); + }); + it('should tableHeaderSortButton AllCases', () => { + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="tableHeaderSortButton"]').first().simulate('click'); + expect(setQueryParams).toBeCalledWith({ + page: 1, + perPage: 5, + sortField: 'createdAt', + sortOrder: 'asc', + }); + }); + it('closes case when row action icon clicked', () => { + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="action-close"]').first().simulate('click'); + const firstCase = useGetCasesMockState.data.cases[0]; + expect(dispatchUpdateCaseProperty).toBeCalledWith({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: 'closed', + refetchCasesStatus: fetchCasesStatus, + version: firstCase.version, + }); + }); + it('opens case when row action icon clicked', () => { + useGetCasesMock.mockImplementation(() => ({ + ...defaultGetCases, + filterOptions: { ...defaultGetCases.filterOptions, status: 'closed' }, + })); + + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="action-open"]').first().simulate('click'); + const firstCase = useGetCasesMockState.data.cases[0]; + expect(dispatchUpdateCaseProperty).toBeCalledWith({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: 'open', + refetchCasesStatus: fetchCasesStatus, + version: firstCase.version, + }); + }); + it('Bulk delete', () => { + useGetCasesMock.mockImplementation(() => ({ + ...defaultGetCases, + selectedCases: useGetCasesMockState.data.cases, + })); + useDeleteCasesMock + .mockReturnValueOnce({ + ...defaultDeleteCases, + isDisplayConfirmDeleteModal: false, + }) + .mockReturnValue({ + ...defaultDeleteCases, + isDisplayConfirmDeleteModal: true, + }); + + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); + wrapper.find('[data-test-subj="cases-bulk-delete-button"]').first().simulate('click'); + expect(handleToggleModal).toBeCalled(); + + wrapper + .find( + '[data-test-subj="confirm-delete-case-modal"] [data-test-subj="confirmModalConfirmButton"]' + ) + .last() + .simulate('click'); + expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual( + useGetCasesMockState.data.cases.map(({ id }) => ({ id })) + ); + }); + it('Bulk close status update', () => { + useGetCasesMock.mockImplementation(() => ({ + ...defaultGetCases, + selectedCases: useGetCasesMockState.data.cases, + })); + + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); + wrapper.find('[data-test-subj="cases-bulk-close-button"]').first().simulate('click'); + expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, 'closed'); + }); + it('Bulk open status update', () => { + useGetCasesMock.mockImplementation(() => ({ + ...defaultGetCases, + selectedCases: useGetCasesMockState.data.cases, + filterOptions: { + ...defaultGetCases.filterOptions, + status: 'closed', + }, + })); + + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); + wrapper.find('[data-test-subj="cases-bulk-open-button"]').first().simulate('click'); + expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, 'open'); + }); + it('isDeleted is true, refetch', () => { + useDeleteCasesMock.mockImplementation(() => ({ + ...defaultDeleteCases, + isDeleted: true, + })); + + mount( + + + + ); + expect(refetchCases).toBeCalled(); + expect(fetchCasesStatus).toBeCalled(); + expect(dispatchResetIsDeleted).toBeCalled(); + }); + it('isUpdated is true, refetch', () => { + useUpdateCasesMock.mockImplementation(() => ({ + ...defaultUpdateCases, + isUpdated: true, + })); + + mount( + + + + ); + expect(refetchCases).toBeCalled(); + expect(fetchCasesStatus).toBeCalled(); + expect(dispatchResetIsUpdated).toBeCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx new file mode 100644 index 0000000000000..12f0d02eb10f6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -0,0 +1,439 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + EuiBasicTable, + EuiButton, + EuiContextMenuPanel, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiProgress, + EuiTableSortingType, +} from '@elastic/eui'; +import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; +import { isEmpty } from 'lodash/fp'; +import styled, { css } from 'styled-components'; +import * as i18n from './translations'; + +import { getCasesColumns } from './columns'; +import { Case, DeleteCase, FilterOptions, SortFieldCase } from '../../containers/types'; +import { useGetCases, UpdateCase } from '../../containers/use_get_cases'; +import { useGetCasesStatus } from '../../containers/use_get_cases_status'; +import { useDeleteCases } from '../../containers/use_delete_cases'; +import { EuiBasicTableOnChange } from '../../../alerts/pages/detection_engine/rules/types'; +import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; +import { Panel } from '../../../common/components/panel'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../common/components/utility_bar'; +import { getCreateCaseUrl } from '../../../common/components/link_to'; +import { getBulkItems } from '../bulk_actions'; +import { CaseHeaderPage } from '../case_header_page'; +import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; +import { OpenClosedStats } from '../open_closed_stats'; +import { navTabs } from '../../../app/home/home_navigations'; + +import { getActions } from './actions'; +import { CasesTableFilters } from './table_filters'; +import { useUpdateCases } from '../../containers/use_bulk_update_case'; +import { useGetActionLicense } from '../../containers/use_get_action_license'; +import { getActionLicenseError } from '../use_push_to_service/helpers'; +import { CaseCallOut } from '../callout'; +import { ConfigureCaseButton } from '../configure_cases/button'; +import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations'; + +const Div = styled.div` + margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; +`; +const FlexItemDivider = styled(EuiFlexItem)` + ${({ theme }) => css` + .euiFlexGroup--gutterMedium > &.euiFlexItem { + border-right: ${theme.eui.euiBorderThin}; + padding-right: ${theme.eui.euiSize}; + margin-right: ${theme.eui.euiSize}; + } + `} +`; + +const ProgressLoader = styled(EuiProgress)` + ${({ theme }) => css` + top: 2px; + border-radius: ${theme.eui.euiBorderRadius}; + z-index: ${theme.eui.euiZHeader}; + `} +`; + +const getSortField = (field: string): SortFieldCase => { + if (field === SortFieldCase.createdAt) { + return SortFieldCase.createdAt; + } else if (field === SortFieldCase.closedAt) { + return SortFieldCase.closedAt; + } + return SortFieldCase.createdAt; +}; + +interface AllCasesProps { + userCanCrud: boolean; +} +export const AllCases = React.memo(({ userCanCrud }) => { + const urlSearch = useGetUrlSearch(navTabs.case); + const { actionLicense } = useGetActionLicense(); + const { + countClosedCases, + countOpenCases, + isLoading: isCasesStatusLoading, + fetchCasesStatus, + } = useGetCasesStatus(); + const { + data, + dispatchUpdateCaseProperty, + filterOptions, + loading, + queryParams, + selectedCases, + refetchCases, + setFilters, + setQueryParams, + setSelectedCases, + } = useGetCases(); + + // Delete case + const { + dispatchResetIsDeleted, + handleOnDeleteConfirm, + handleToggleModal, + isLoading: isDeleting, + isDeleted, + isDisplayConfirmDeleteModal, + } = useDeleteCases(); + + // Update case + const { + dispatchResetIsUpdated, + isLoading: isUpdating, + isUpdated, + updateBulkStatus, + } = useUpdateCases(); + const [deleteThisCase, setDeleteThisCase] = useState({ + title: '', + id: '', + }); + const [deleteBulk, setDeleteBulk] = useState([]); + const filterRefetch = useRef<() => void>(); + const setFilterRefetch = useCallback( + (refetchFilter: () => void) => { + filterRefetch.current = refetchFilter; + }, + [filterRefetch.current] + ); + const refreshCases = useCallback( + (dataRefresh = true) => { + if (dataRefresh) refetchCases(); + fetchCasesStatus(); + setSelectedCases([]); + setDeleteBulk([]); + if (filterRefetch.current != null) { + filterRefetch.current(); + } + }, + [filterOptions, queryParams, filterRefetch.current] + ); + + useEffect(() => { + if (isDeleted) { + refreshCases(); + dispatchResetIsDeleted(); + } + if (isUpdated) { + refreshCases(); + dispatchResetIsUpdated(); + } + }, [isDeleted, isUpdated]); + const confirmDeleteModal = useMemo( + () => ( + 0} + onCancel={handleToggleModal} + onConfirm={handleOnDeleteConfirm.bind( + null, + deleteBulk.length > 0 ? deleteBulk : [deleteThisCase] + )} + /> + ), + [deleteBulk, deleteThisCase, isDisplayConfirmDeleteModal] + ); + + const toggleDeleteModal = useCallback((deleteCase: Case) => { + handleToggleModal(); + setDeleteThisCase(deleteCase); + }, []); + + const toggleBulkDeleteModal = useCallback( + (caseIds: string[]) => { + handleToggleModal(); + if (caseIds.length === 1) { + const singleCase = selectedCases.find((theCase) => theCase.id === caseIds[0]); + if (singleCase) { + return setDeleteThisCase({ id: singleCase.id, title: singleCase.title }); + } + } + const convertToDeleteCases: DeleteCase[] = caseIds.map((id) => ({ id })); + setDeleteBulk(convertToDeleteCases); + }, + [selectedCases] + ); + + const handleUpdateCaseStatus = useCallback( + (status: string) => { + updateBulkStatus(selectedCases, status); + }, + [selectedCases] + ); + + const selectedCaseIds = useMemo( + (): string[] => selectedCases.map((caseObj: Case) => caseObj.id), + [selectedCases] + ); + + const getBulkItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [selectedCaseIds, filterOptions.status, toggleBulkDeleteModal] + ); + const handleDispatchUpdate = useCallback( + (args: Omit) => { + dispatchUpdateCaseProperty({ ...args, refetchCasesStatus: fetchCasesStatus }); + }, + [dispatchUpdateCaseProperty, fetchCasesStatus] + ); + + const actions = useMemo( + () => + getActions({ + caseStatus: filterOptions.status, + deleteCaseOnClick: toggleDeleteModal, + dispatchUpdate: handleDispatchUpdate, + }), + [filterOptions.status, toggleDeleteModal, handleDispatchUpdate] + ); + + const actionsErrors = useMemo(() => getActionLicenseError(actionLicense), [actionLicense]); + + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + let newQueryParams = queryParams; + if (sort) { + newQueryParams = { + ...newQueryParams, + sortField: getSortField(sort.field), + sortOrder: sort.direction, + }; + } + if (page) { + newQueryParams = { + ...newQueryParams, + page: page.index + 1, + perPage: page.size, + }; + } + setQueryParams(newQueryParams); + refreshCases(false); + }, + [queryParams] + ); + + const onFilterChangedCallback = useCallback( + (newFilterOptions: Partial) => { + if (newFilterOptions.status && newFilterOptions.status === 'closed') { + setQueryParams({ sortField: SortFieldCase.closedAt }); + } else if (newFilterOptions.status && newFilterOptions.status === 'open') { + setQueryParams({ sortField: SortFieldCase.createdAt }); + } + setFilters(newFilterOptions); + refreshCases(false); + }, + [filterOptions, queryParams] + ); + + const memoizedGetCasesColumns = useMemo( + () => getCasesColumns(userCanCrud ? actions : [], filterOptions.status), + [actions, filterOptions.status, userCanCrud] + ); + const memoizedPagination = useMemo( + () => ({ + pageIndex: queryParams.page - 1, + pageSize: queryParams.perPage, + totalItemCount: data.total, + pageSizeOptions: [5, 10, 15, 20, 25], + }), + [data, queryParams] + ); + + const sorting: EuiTableSortingType = { + sort: { field: queryParams.sortField, direction: queryParams.sortOrder }, + }; + const euiBasicTableSelectionProps = useMemo>( + () => ({ onSelectionChange: setSelectedCases }), + [selectedCases] + ); + const isCasesLoading = useMemo( + () => loading.indexOf('cases') > -1 || loading.indexOf('caseUpdate') > -1, + [loading] + ); + const isDataEmpty = useMemo(() => data.total === 0, [data]); + + return ( + <> + {!isEmpty(actionsErrors) && ( + + )} + + + + + + + + + + } + titleTooltip={!isEmpty(actionsErrors) ? actionsErrors[0].title : ''} + urlSearch={urlSearch} + /> + + + + {i18n.CREATE_TITLE} + + + + + {(isCasesLoading || isDeleting || isUpdating) && !isDataEmpty && ( + + )} + + + {isCasesLoading && isDataEmpty ? ( +
    + +
    + ) : ( +
    + + + + + {i18n.SHOWING_CASES(data.total ?? 0)} + + + + + {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} + + {userCanCrud && ( + + {i18n.BULK_ACTIONS} + + )} + + {i18n.REFRESH} + + + + + {i18n.NO_CASES}} + titleSize="xs" + body={i18n.NO_CASES_BODY} + actions={ + + {i18n.ADD_NEW_CASE} + + } + /> + } + onChange={tableOnChangeCallback} + pagination={memoizedPagination} + selection={userCanCrud ? euiBasicTableSelectionProps : {}} + sorting={sorting} + /> +
    + )} +
    + {confirmDeleteModal} + + ); +}); + +AllCases.displayName = 'AllCases'; diff --git a/x-pack/plugins/siem/public/cases/components/all_cases/table_filters.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx similarity index 84% rename from x-pack/plugins/siem/public/cases/components/all_cases/table_filters.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx index 05702e931fc25..7807b4a8a77d8 100644 --- a/x-pack/plugins/siem/public/cases/components/all_cases/table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx @@ -47,18 +47,12 @@ describe('CasesTableFilters ', () => { ); - expect( - wrapper - .find(`[data-test-subj="open-case-count"]`) - .last() - .text() - ).toEqual('Open cases (99)'); - expect( - wrapper - .find(`[data-test-subj="closed-case-count"]`) - .last() - .text() - ).toEqual('Closed cases (1234)'); + expect(wrapper.find(`[data-test-subj="open-case-count"]`).last().text()).toEqual( + 'Open cases (99)' + ); + expect(wrapper.find(`[data-test-subj="closed-case-count"]`).last().text()).toEqual( + 'Closed cases (1234)' + ); }); it('should call onFilterChange when selected tags change', () => { const wrapper = mount( @@ -66,14 +60,8 @@ describe('CasesTableFilters ', () => { ); - wrapper - .find(`[data-test-subj="options-filter-popover-button-Tags"]`) - .last() - .simulate('click'); - wrapper - .find(`[data-test-subj="options-filter-popover-item-0"]`) - .last() - .simulate('click'); + wrapper.find(`[data-test-subj="options-filter-popover-button-Tags"]`).last().simulate('click'); + wrapper.find(`[data-test-subj="options-filter-popover-item-0"]`).last().simulate('click'); expect(onFilterChanged).toBeCalledWith({ tags: ['coke'] }); }); @@ -88,10 +76,7 @@ describe('CasesTableFilters ', () => { .last() .simulate('click'); - wrapper - .find(`[data-test-subj="options-filter-popover-item-0"]`) - .last() - .simulate('click'); + wrapper.find(`[data-test-subj="options-filter-popover-item-0"]`).last().simulate('click'); expect(onFilterChanged).toBeCalledWith({ reporters: [{ username: 'casetester' }] }); }); @@ -114,10 +99,7 @@ describe('CasesTableFilters ', () => { ); - wrapper - .find(`[data-test-subj="closed-case-count"]`) - .last() - .simulate('click'); + wrapper.find(`[data-test-subj="closed-case-count"]`).last().simulate('click'); expect(onFilterChanged).toBeCalledWith({ status: 'closed' }); }); diff --git a/x-pack/plugins/siem/public/cases/components/all_cases/table_filters.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx similarity index 93% rename from x-pack/plugins/siem/public/cases/components/all_cases/table_filters.tsx rename to x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx index 55713c201743a..42340b455c3af 100644 --- a/x-pack/plugins/siem/public/cases/components/all_cases/table_filters.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx @@ -45,7 +45,7 @@ const CasesTableFiltersComponent = ({ setFilterRefetch, }: CasesTableFiltersProps) => { const [selectedReporters, setSelectedReporters] = useState( - initial.reporters.map(r => r.full_name ?? r.username ?? '') + initial.reporters.map((r) => r.full_name ?? r.username ?? '') ); const [search, setSearch] = useState(initial.search); const [selectedTags, setSelectedTags] = useState(initial.tags); @@ -63,23 +63,23 @@ const CasesTableFiltersComponent = ({ }, [refetch, setFilterRefetch]); useEffect(() => { if (selectedReporters.length) { - const newReporters = selectedReporters.filter(r => reporters.includes(r)); + const newReporters = selectedReporters.filter((r) => reporters.includes(r)); handleSelectedReporters(newReporters); } }, [reporters]); useEffect(() => { if (selectedTags.length) { - const newTags = selectedTags.filter(t => tags.includes(t)); + const newTags = selectedTags.filter((t) => tags.includes(t)); handleSelectedTags(newTags); } }, [tags]); const handleSelectedReporters = useCallback( - newReporters => { + (newReporters) => { if (!isEqual(newReporters, selectedReporters)) { setSelectedReporters(newReporters); const reportersObj = respReporters.filter( - r => newReporters.includes(r.username) || newReporters.includes(r.full_name) + (r) => newReporters.includes(r.username) || newReporters.includes(r.full_name) ); onFilterChanged({ reporters: reportersObj }); } @@ -88,7 +88,7 @@ const CasesTableFiltersComponent = ({ ); const handleSelectedTags = useCallback( - newTags => { + (newTags) => { if (!isEqual(newTags, selectedTags)) { setSelectedTags(newTags); onFilterChanged({ tags: newTags }); @@ -97,7 +97,7 @@ const CasesTableFiltersComponent = ({ [selectedTags] ); const handleOnSearch = useCallback( - newSearch => { + (newSearch) => { const trimSearch = newSearch.trim(); if (!isEqual(trimSearch, search)) { setSearch(trimSearch); @@ -107,7 +107,7 @@ const CasesTableFiltersComponent = ({ [search] ); const handleToggleFilter = useCallback( - showOpen => { + (showOpen) => { if (showOpen !== showOpenCases) { setShowOpenCases(showOpen); onFilterChanged({ status: showOpen ? 'open' : 'closed' }); diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts new file mode 100644 index 0000000000000..9198aaceb96f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export * from '../../translations'; + +export const NO_CASES = i18n.translate('xpack.securitySolution.case.caseTable.noCases.title', { + defaultMessage: 'No Cases', +}); +export const NO_CASES_BODY = i18n.translate('xpack.securitySolution.case.caseTable.noCases.body', { + defaultMessage: + 'There are no cases to display. Please create a new case or change your filter settings above.', +}); + +export const ADD_NEW_CASE = i18n.translate('xpack.securitySolution.case.caseTable.addNewCase', { + defaultMessage: 'Add New Case', +}); + +export const SHOWING_SELECTED_CASES = (totalRules: number) => + i18n.translate('xpack.securitySolution.case.caseTable.selectedCasesTitle', { + values: { totalRules }, + defaultMessage: 'Selected {totalRules} {totalRules, plural, =1 {case} other {cases}}', + }); + +export const SHOWING_CASES = (totalRules: number) => + i18n.translate('xpack.securitySolution.case.caseTable.showingCasesTitle', { + values: { totalRules }, + defaultMessage: 'Showing {totalRules} {totalRules, plural, =1 {case} other {cases}}', + }); + +export const UNIT = (totalCount: number) => + i18n.translate('xpack.securitySolution.case.caseTable.unit', { + values: { totalCount }, + defaultMessage: `{totalCount, plural, =1 {case} other {cases}}`, + }); + +export const SEARCH_CASES = i18n.translate( + 'xpack.securitySolution.case.caseTable.searchAriaLabel', + { + defaultMessage: 'Search cases', + } +); + +export const BULK_ACTIONS = i18n.translate('xpack.securitySolution.case.caseTable.bulkActions', { + defaultMessage: 'Bulk actions', +}); + +export const EXTERNAL_INCIDENT = i18n.translate( + 'xpack.securitySolution.case.caseTable.snIncident', + { + defaultMessage: 'External Incident', + } +); + +export const INCIDENT_MANAGEMENT_SYSTEM = i18n.translate( + 'xpack.securitySolution.case.caseTable.incidentSystem', + { + defaultMessage: 'Incident Management System', + } +); + +export const SEARCH_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.case.caseTable.searchPlaceholder', + { + defaultMessage: 'e.g. case name', + } +); +export const OPEN_CASES = i18n.translate('xpack.securitySolution.case.caseTable.openCases', { + defaultMessage: 'Open cases', +}); +export const CLOSED_CASES = i18n.translate('xpack.securitySolution.case.caseTable.closedCases', { + defaultMessage: 'Closed cases', +}); + +export const CLOSED = i18n.translate('xpack.securitySolution.case.caseTable.closed', { + defaultMessage: 'Closed', +}); + +export const DELETE = i18n.translate('xpack.securitySolution.case.caseTable.delete', { + defaultMessage: 'Delete', +}); + +export const REQUIRES_UPDATE = i18n.translate( + 'xpack.securitySolution.case.caseTable.requiresUpdate', + { + defaultMessage: ' requires update', + } +); + +export const UP_TO_DATE = i18n.translate('xpack.securitySolution.case.caseTable.upToDate', { + defaultMessage: ' is up to date', +}); +export const NOT_PUSHED = i18n.translate('xpack.securitySolution.case.caseTable.notPushed', { + defaultMessage: 'Not pushed', +}); + +export const REFRESH = i18n.translate('xpack.securitySolution.case.caseTable.refreshTitle', { + defaultMessage: 'Refresh', +}); + +export const SERVICENOW_LINK_ARIA = i18n.translate( + 'xpack.securitySolution.case.caseTable.serviceNowLinkAria', + { + defaultMessage: 'click to view the incident on servicenow', + } +); diff --git a/x-pack/plugins/siem/public/cases/components/bulk_actions/index.tsx b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/bulk_actions/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts new file mode 100644 index 0000000000000..25e22aebb6a0b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const BULK_ACTION_CLOSE_SELECTED = i18n.translate( + 'xpack.securitySolution.case.caseTable.bulkActions.closeSelectedTitle', + { + defaultMessage: 'Close selected', + } +); + +export const BULK_ACTION_OPEN_SELECTED = i18n.translate( + 'xpack.securitySolution.case.caseTable.bulkActions.openSelectedTitle', + { + defaultMessage: 'Reopen selected', + } +); + +export const BULK_ACTION_DELETE_SELECTED = i18n.translate( + 'xpack.securitySolution.case.caseTable.bulkActions.deleteSelectedTitle', + { + defaultMessage: 'Delete selected', + } +); diff --git a/x-pack/plugins/siem/public/cases/components/callout/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/callout/helpers.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/callout/helpers.tsx rename to x-pack/plugins/security_solution/public/cases/components/callout/helpers.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/callout/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/callout/index.test.tsx new file mode 100644 index 0000000000000..ee3faeb2ceeb5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/callout/index.test.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { CaseCallOut } from '.'; + +const defaultProps = { + title: 'hey title', +}; + +describe('CaseCallOut ', () => { + it('Renders single message callout', () => { + const props = { + ...defaultProps, + message: 'we have one message', + }; + + const wrapper = mount(); + + expect(wrapper.find(`[data-test-subj="callout-message-primary"]`).last().exists()).toBeTruthy(); + }); + + it('Renders multi message callout', () => { + const props = { + ...defaultProps, + messages: [ + { ...defaultProps, description:

    {'we have two messages'}

    }, + { ...defaultProps, description:

    {'for real'}

    }, + ], + }; + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="callout-message-primary"]`).last().exists()).toBeFalsy(); + expect( + wrapper.find(`[data-test-subj="callout-messages-primary"]`).last().exists() + ).toBeTruthy(); + }); + + it('it shows the correct type of callouts', () => { + const props = { + ...defaultProps, + messages: [ + { + ...defaultProps, + description:

    {'we have two messages'}

    , + errorType: 'danger' as 'primary' | 'success' | 'warning' | 'danger', + }, + { ...defaultProps, description:

    {'for real'}

    }, + ], + }; + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="callout-messages-danger"]`).last().exists()).toBeTruthy(); + + expect( + wrapper.find(`[data-test-subj="callout-messages-primary"]`).last().exists() + ).toBeTruthy(); + }); + + it('it applies the correct color to button', () => { + const props = { + ...defaultProps, + messages: [ + { + ...defaultProps, + description:

    {'one'}

    , + errorType: 'danger' as 'primary' | 'success' | 'warning' | 'danger', + }, + { + ...defaultProps, + description:

    {'two'}

    , + errorType: 'success' as 'primary' | 'success' | 'warning' | 'danger', + }, + { + ...defaultProps, + description:

    {'three'}

    , + errorType: 'primary' as 'primary' | 'success' | 'warning' | 'danger', + }, + ], + }; + + const wrapper = mount(); + + expect(wrapper.find(`[data-test-subj="callout-dismiss-danger"]`).first().prop('color')).toBe( + 'danger' + ); + + expect(wrapper.find(`[data-test-subj="callout-dismiss-success"]`).first().prop('color')).toBe( + 'secondary' + ); + + expect(wrapper.find(`[data-test-subj="callout-dismiss-primary"]`).first().prop('color')).toBe( + 'primary' + ); + }); + + it('Dismisses callout', () => { + const props = { + ...defaultProps, + message: 'we have one message', + }; + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="case-call-out-primary"]`).exists()).toBeTruthy(); + wrapper.find(`[data-test-subj="callout-dismiss-primary"]`).last().simulate('click'); + expect(wrapper.find(`[data-test-subj="case-call-out-primary"]`).exists()).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/callout/index.tsx b/x-pack/plugins/security_solution/public/cases/components/callout/index.tsx new file mode 100644 index 0000000000000..171c0508b9d92 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/callout/index.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiCallOut, EuiButton, EuiDescriptionList, EuiSpacer } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { memo, useCallback, useState } from 'react'; + +import * as i18n from './translations'; + +export * from './helpers'; + +interface ErrorMessage { + title: string; + description: JSX.Element; + errorType?: 'primary' | 'success' | 'warning' | 'danger'; +} + +interface CaseCallOutProps { + title: string; + message?: string; + messages?: ErrorMessage[]; +} + +const CaseCallOutComponent = ({ title, message, messages }: CaseCallOutProps) => { + const [showCallOut, setShowCallOut] = useState(true); + const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); + let callOutMessages = messages ?? []; + + if (message) { + callOutMessages = [ + ...callOutMessages, + { + title: '', + description:

    {message}

    , + errorType: 'primary', + }, + ]; + } + + const groupedErrorMessages = callOutMessages.reduce((acc, currentMessage: ErrorMessage) => { + const key = currentMessage.errorType == null ? 'primary' : currentMessage.errorType; + return { + ...acc, + [key]: [...(acc[key] || []), currentMessage], + }; + }, {} as { [key in NonNullable]: ErrorMessage[] }); + + return showCallOut ? ( + <> + {(Object.keys(groupedErrorMessages) as Array).map((key) => ( + + + {!isEmpty(groupedErrorMessages[key]) && ( + + )} + + {i18n.DISMISS_CALLOUT} + + + + + ))} + + ) : null; +}; + +export const CaseCallOut = memo(CaseCallOutComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/callout/translations.ts b/x-pack/plugins/security_solution/public/cases/components/callout/translations.ts new file mode 100644 index 0000000000000..01956ca942997 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/callout/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const READ_ONLY_SAVED_OBJECT_TITLE = i18n.translate( + 'xpack.securitySolution.case.readOnlySavedObjectTitle', + { + defaultMessage: 'You have read-only feature privileges', + } +); + +export const READ_ONLY_SAVED_OBJECT_MSG = i18n.translate( + 'xpack.securitySolution.case.readOnlySavedObjectDescription', + { + defaultMessage: + 'You are only allowed to view cases. If you need to open and update cases, contact your Kibana administrator', + } +); + +export const DISMISS_CALLOUT = i18n.translate( + 'xpack.securitySolution.case.dismissErrorsPushServiceCallOutTitle', + { + defaultMessage: 'Dismiss', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx new file mode 100644 index 0000000000000..0ac6093f2ee04 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { HeaderPage, HeaderPageProps } from '../../../common/components/header_page'; +import * as i18n from './translations'; + +const CaseHeaderPageComponent: React.FC = (props) => ; + +CaseHeaderPageComponent.defaultProps = { + badgeOptions: { + beta: true, + text: i18n.PAGE_BADGE_LABEL, + tooltip: i18n.PAGE_BADGE_TOOLTIP, + }, +}; + +export const CaseHeaderPage = React.memo(CaseHeaderPageComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_header_page/translations.ts b/x-pack/plugins/security_solution/public/cases/components/case_header_page/translations.ts new file mode 100644 index 0000000000000..8cdc287b1584c --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_header_page/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_BADGE_LABEL = i18n.translate( + 'xpack.securitySolution.case.caseView.pageBadgeLabel', + { + defaultMessage: 'Beta', + } +); + +export const PAGE_BADGE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.case.caseView.pageBadgeTooltip', + { + defaultMessage: + 'Case Workflow is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', + } +); diff --git a/x-pack/plugins/siem/public/cases/components/case_status/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_status/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/case_status/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/case_status/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx new file mode 100644 index 0000000000000..2641ac68cf952 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { useDeleteCases } from '../../containers/use_delete_cases'; +import { TestProviders } from '../../../common/mock'; +import { basicCase, basicPush } from '../../containers/mock'; +import { CaseViewActions } from './actions'; +import * as i18n from './translations'; +jest.mock('../../containers/use_delete_cases'); +const useDeleteCasesMock = useDeleteCases as jest.Mock; + +describe('CaseView actions', () => { + const handleOnDeleteConfirm = jest.fn(); + const handleToggleModal = jest.fn(); + const dispatchResetIsDeleted = jest.fn(); + const defaultDeleteState = { + dispatchResetIsDeleted, + handleToggleModal, + handleOnDeleteConfirm, + isLoading: false, + isError: false, + isDeleted: false, + isDisplayConfirmDeleteModal: false, + }; + beforeEach(() => { + jest.resetAllMocks(); + useDeleteCasesMock.mockImplementation(() => defaultDeleteState); + }); + it('clicking trash toggles modal', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeFalsy(); + + wrapper.find('button[data-test-subj="property-actions-ellipses"]').first().simulate('click'); + wrapper.find('button[data-test-subj="property-actions-trash"]').simulate('click'); + expect(handleToggleModal).toHaveBeenCalled(); + }); + it('toggle delete modal and confirm', () => { + useDeleteCasesMock.mockImplementation(() => ({ + ...defaultDeleteState, + isDisplayConfirmDeleteModal: true, + })); + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy(); + wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click'); + expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([ + { id: basicCase.id, title: basicCase.title }, + ]); + }); + it('displays active incident link', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeFalsy(); + + wrapper.find('button[data-test-subj="property-actions-ellipses"]').first().simulate('click'); + expect( + wrapper.find('[data-test-subj="property-actions-popout"]').first().prop('aria-label') + ).toEqual(i18n.VIEW_INCIDENT(basicPush.externalTitle)); + }); +}); diff --git a/x-pack/plugins/siem/public/cases/components/case_view/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/case_view/actions.tsx rename to x-pack/plugins/security_solution/public/cases/components/case_view/actions.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx new file mode 100644 index 0000000000000..2832a28fbb7cd --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -0,0 +1,411 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; +import { CaseComponent, CaseProps, CaseView } from '.'; +import { basicCase, basicCaseClosed, caseUserActions } from '../../containers/mock'; +import { TestProviders } from '../../../common/mock'; +import { useUpdateCase } from '../../containers/use_update_case'; +import { useGetCase } from '../../containers/use_get_case'; +import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions'; +import { wait } from '../../../common/lib/helpers'; + +import { useConnectors } from '../../containers/configure/use_connectors'; +import { connectorsMock } from '../../containers/configure/mock'; + +import { usePostPushToService } from '../../containers/use_post_push_to_service'; + +jest.mock('../../containers/use_update_case'); +jest.mock('../../containers/use_get_case_user_actions'); +jest.mock('../../containers/use_get_case'); +jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/use_post_push_to_service'); + +const useUpdateCaseMock = useUpdateCase as jest.Mock; +const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; +const usePostPushToServiceMock = usePostPushToService as jest.Mock; + +export const caseProps: CaseProps = { + caseId: basicCase.id, + userCanCrud: true, + caseData: { ...basicCase, connectorId: 'servicenow-2' }, + fetchCase: jest.fn(), + updateCase: jest.fn(), +}; + +export const caseClosedProps: CaseProps = { + ...caseProps, + caseData: basicCaseClosed, +}; + +describe('CaseView ', () => { + const updateCaseProperty = jest.fn(); + const fetchCaseUserActions = jest.fn(); + const fetchCase = jest.fn(); + const updateCase = jest.fn(); + const postPushToService = jest.fn(); + + const data = caseProps.caseData; + const defaultGetCase = { + isLoading: false, + isError: false, + data, + updateCase, + fetchCase, + }; + /* eslint-disable no-console */ + // Silence until enzyme fixed to use ReactTestUtils.act() + const originalError = console.error; + beforeAll(() => { + console.error = jest.fn(); + }); + afterAll(() => { + console.error = originalError; + }); + /* eslint-enable no-console */ + + const defaultUpdateCaseState = { + isLoading: false, + isError: false, + updateKey: null, + updateCaseProperty, + }; + + const defaultUseGetCaseUserActions = { + caseUserActions, + caseServices: {}, + fetchCaseUserActions, + firstIndexPushToService: -1, + hasDataToPush: false, + isLoading: false, + isError: false, + lastIndexPushToService: -1, + participants: [data.createdBy], + }; + + beforeEach(() => { + jest.resetAllMocks(); + useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); + useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions); + usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, postPushToService })); + useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, isLoading: false })); + }); + + it('should render CaseComponent', async () => { + const wrapper = mount( + + + + + + ); + await wait(); + expect(wrapper.find(`[data-test-subj="case-view-title"]`).first().prop('title')).toEqual( + data.title + ); + expect(wrapper.find(`[data-test-subj="case-view-status"]`).first().text()).toEqual(data.status); + expect( + wrapper + .find(`[data-test-subj="case-view-tag-list"] [data-test-subj="case-tag"]`) + .first() + .text() + ).toEqual(data.tags[0]); + expect(wrapper.find(`[data-test-subj="case-view-username"]`).first().text()).toEqual( + data.createdBy.username + ); + expect(wrapper.contains(`[data-test-subj="case-view-closedAt"]`)).toBe(false); + expect(wrapper.find(`[data-test-subj="case-view-createdAt"]`).first().prop('value')).toEqual( + data.createdAt + ); + expect( + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="user-action-markdown"]`) + .first() + .prop('raw') + ).toEqual(data.description); + }); + + it('should show closed indicators in header when case is closed', async () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + caseData: basicCaseClosed, + })); + const wrapper = mount( + + + + + + ); + await wait(); + expect(wrapper.contains(`[data-test-subj="case-view-createdAt"]`)).toBe(false); + expect(wrapper.find(`[data-test-subj="case-view-closedAt"]`).first().prop('value')).toEqual( + basicCaseClosed.closedAt + ); + expect(wrapper.find(`[data-test-subj="case-view-status"]`).first().text()).toEqual( + basicCaseClosed.status + ); + }); + + it('should dispatch update state when button is toggled', async () => { + const wrapper = mount( + + + + + + ); + await wait(); + wrapper + .find('input[data-test-subj="toggle-case-status"]') + .simulate('change', { target: { checked: true } }); + expect(updateCaseProperty).toHaveBeenCalled(); + }); + + it('should display EditableTitle isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'title', + })); + const wrapper = mount( + + + + + + ); + expect(wrapper.find('[data-test-subj="editable-title-loading"]').first().exists()).toBeTruthy(); + expect( + wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists() + ).toBeFalsy(); + }); + + it('should display Toggle Status isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'status', + })); + const wrapper = mount( + + + + + + ); + expect( + wrapper.find('[data-test-subj="toggle-case-status"]').first().prop('isLoading') + ).toBeTruthy(); + }); + + it('should display description isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'description', + })); + const wrapper = mount( + + + + + + ); + expect( + wrapper + .find('[data-test-subj="description-action"] [data-test-subj="user-action-title-loading"]') + .first() + .exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="description-action"] [data-test-subj="property-actions"]') + .first() + .exists() + ).toBeFalsy(); + }); + + it('should display tags isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'tags', + })); + const wrapper = mount( + + + + + + ); + expect( + wrapper + .find('[data-test-subj="case-view-tag-list"] [data-test-subj="tag-list-loading"]') + .first() + .exists() + ).toBeTruthy(); + expect(wrapper.find('[data-test-subj="tag-list-edit"]').first().exists()).toBeFalsy(); + }); + + it('should update title', () => { + const wrapper = mount( + + + + + + ); + const newTitle = 'The new title'; + wrapper.find(`[data-test-subj="editable-title-edit-icon"]`).first().simulate('click'); + wrapper.update(); + wrapper + .find(`[data-test-subj="editable-title-input-field"]`) + .last() + .simulate('change', { target: { value: newTitle } }); + + wrapper.update(); + wrapper.find(`[data-test-subj="editable-title-submit-btn"]`).first().simulate('click'); + + wrapper.update(); + const updateObject = updateCaseProperty.mock.calls[0][0]; + expect(updateObject.updateKey).toEqual('title'); + expect(updateObject.updateValue).toEqual(newTitle); + }); + + it('should push updates on button click', async () => { + useGetCaseUserActionsMock.mockImplementation(() => ({ + ...defaultUseGetCaseUserActions, + hasDataToPush: true, + })); + + const wrapper = mount( + + + + + + ); + + await wait(); + + expect( + wrapper.find('[data-test-subj="has-data-to-push-button"]').first().exists() + ).toBeTruthy(); + + wrapper.find('[data-test-subj="push-to-external-service"]').first().simulate('click'); + + wrapper.update(); + + expect(postPushToService).toHaveBeenCalled(); + }); + + it('should return null if error', () => { + (useGetCase as jest.Mock).mockImplementation(() => ({ + ...defaultGetCase, + isError: true, + })); + const wrapper = mount( + + + + + + ); + expect(wrapper).toEqual({}); + }); + + it('should return spinner if loading', () => { + (useGetCase as jest.Mock).mockImplementation(() => ({ + ...defaultGetCase, + isLoading: true, + })); + const wrapper = mount( + + + + + + ); + expect(wrapper.find('[data-test-subj="case-view-loading"]').exists()).toBeTruthy(); + }); + + it('should return case view when data is there', () => { + (useGetCase as jest.Mock).mockImplementation(() => defaultGetCase); + const wrapper = mount( + + + + + + ); + expect(wrapper.find('[data-test-subj="case-view-title"]').exists()).toBeTruthy(); + }); + + it('should refresh data on refresh', () => { + (useGetCase as jest.Mock).mockImplementation(() => defaultGetCase); + const wrapper = mount( + + + + + + ); + wrapper.find('[data-test-subj="case-refresh"]').first().simulate('click'); + expect(fetchCaseUserActions).toBeCalledWith(caseProps.caseData.id); + expect(fetchCase).toBeCalled(); + }); + + it('should disable the push button when connector is invalid', () => { + useGetCaseUserActionsMock.mockImplementation(() => ({ + ...defaultUseGetCaseUserActions, + hasDataToPush: true, + })); + + const wrapper = mount( + + + + + + ); + + expect( + wrapper.find('button[data-test-subj="push-to-external-service"]').first().prop('disabled') + ).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx new file mode 100644 index 0000000000000..bb63cf633747b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -0,0 +1,392 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButtonToggle, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiLoadingSpinner, + EuiHorizontalRule, +} from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import styled from 'styled-components'; + +import * as i18n from './translations'; +import { Case } from '../../containers/types'; +import { getCaseUrl } from '../../../common/components/link_to'; +import { gutterTimeline } from '../../../common/lib/helpers'; +import { HeaderPage } from '../../../common/components/header_page'; +import { EditableTitle } from '../../../common/components/header_page/editable_title'; +import { TagList } from '../tag_list'; +import { useGetCase } from '../../containers/use_get_case'; +import { UserActionTree } from '../user_action_tree'; +import { UserList } from '../user_list'; +import { useUpdateCase } from '../../containers/use_update_case'; +import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; +import { getTypedPayload } from '../../containers/utils'; +import { WhitePageWrapper, HeaderWrapper } from '../wrappers'; +import { useBasePath } from '../../../common/lib/kibana'; +import { CaseStatus } from '../case_status'; +import { navTabs } from '../../../app/home/home_navigations'; +import { SpyRoute } from '../../../common/utils/route/spy_routes'; +import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions'; +import { usePushToService } from '../use_push_to_service'; +import { EditConnector } from '../edit_connector'; +import { useConnectors } from '../../containers/configure/use_connectors'; + +interface Props { + caseId: string; + userCanCrud: boolean; +} + +const MyWrapper = styled.div` + padding: ${({ + theme, + }) => `${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l} + ${theme.eui.paddingSizes.l}`}; +`; + +const MyEuiFlexGroup = styled(EuiFlexGroup)` + height: 100%; +`; + +const MyEuiHorizontalRule = styled(EuiHorizontalRule)` + margin-left: 48px; + &.euiHorizontalRule--full { + width: calc(100% - 48px); + } +`; + +export interface CaseProps extends Props { + fetchCase: () => void; + caseData: Case; + updateCase: (newCase: Case) => void; +} + +export const CaseComponent = React.memo( + ({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => { + const basePath = window.location.origin + useBasePath(); + const caseLink = `${basePath}/app/security#/case/${caseId}`; + const search = useGetUrlSearch(navTabs.case); + const [initLoadingData, setInitLoadingData] = useState(true); + const { + caseUserActions, + fetchCaseUserActions, + caseServices, + hasDataToPush, + isLoading: isLoadingUserActions, + participants, + } = useGetCaseUserActions(caseId, caseData.connectorId); + const { isLoading, updateKey, updateCaseProperty } = useUpdateCase({ + caseId, + }); + + // Update Fields + const onUpdateField = useCallback( + (newUpdateKey: keyof Case, updateValue: Case[keyof Case]) => { + const handleUpdateNewCase = (newCase: Case) => + updateCase({ ...newCase, comments: caseData.comments }); + switch (newUpdateKey) { + case 'title': + const titleUpdate = getTypedPayload(updateValue); + if (titleUpdate.length > 0) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'title', + updateValue: titleUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + } + break; + case 'connectorId': + const connectorId = getTypedPayload(updateValue); + if (connectorId.length > 0) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'connector_id', + updateValue: connectorId, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + } + break; + case 'description': + const descriptionUpdate = getTypedPayload(updateValue); + if (descriptionUpdate.length > 0) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'description', + updateValue: descriptionUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + } + break; + case 'tags': + const tagsUpdate = getTypedPayload(updateValue); + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'tags', + updateValue: tagsUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + break; + case 'status': + const statusUpdate = getTypedPayload(updateValue); + if (caseData.status !== updateValue) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'status', + updateValue: statusUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + } + default: + return null; + } + }, + [fetchCaseUserActions, updateCaseProperty, updateCase, caseData] + ); + const handleUpdateCase = useCallback( + (newCase: Case) => { + updateCase(newCase); + fetchCaseUserActions(newCase.id); + }, + [updateCase, fetchCaseUserActions] + ); + + const { loading: isLoadingConnectors, connectors } = useConnectors(); + + const [caseConnectorName, isValidConnector] = useMemo(() => { + const connector = connectors.find((c) => c.id === caseData.connectorId); + return [connector?.name ?? 'none', !!connector]; + }, [connectors, caseData.connectorId]); + + const currentExternalIncident = useMemo( + () => + caseServices != null && caseServices[caseData.connectorId] != null + ? caseServices[caseData.connectorId] + : null, + [caseServices, caseData.connectorId] + ); + + const { pushButton, pushCallouts } = usePushToService({ + caseConnectorId: caseData.connectorId, + caseConnectorName, + caseServices, + caseId: caseData.id, + caseStatus: caseData.status, + connectors, + updateCase: handleUpdateCase, + userCanCrud, + isValidConnector, + }); + + const onSubmitConnector = useCallback( + (connectorId) => onUpdateField('connectorId', connectorId), + [onUpdateField] + ); + const onSubmitTags = useCallback((newTags) => onUpdateField('tags', newTags), [onUpdateField]); + const onSubmitTitle = useCallback((newTitle) => onUpdateField('title', newTitle), [ + onUpdateField, + ]); + const toggleStatusCase = useCallback( + (e) => onUpdateField('status', e.target.checked ? 'closed' : 'open'), + [onUpdateField] + ); + const handleRefresh = useCallback(() => { + fetchCaseUserActions(caseData.id); + fetchCase(); + }, [caseData.id, fetchCase, fetchCaseUserActions]); + + const spyState = useMemo(() => ({ caseTitle: caseData.title }), [caseData.title]); + + const caseStatusData = useMemo( + () => + caseData.status === 'open' + ? { + 'data-test-subj': 'case-view-createdAt', + value: caseData.createdAt, + title: i18n.CASE_OPENED, + buttonLabel: i18n.CLOSE_CASE, + status: caseData.status, + icon: 'folderCheck', + badgeColor: 'secondary', + isSelected: false, + } + : { + 'data-test-subj': 'case-view-closedAt', + value: caseData.closedAt ?? '', + title: i18n.CASE_CLOSED, + buttonLabel: i18n.REOPEN_CASE, + status: caseData.status, + icon: 'folderExclamation', + badgeColor: 'danger', + isSelected: true, + }, + [caseData.closedAt, caseData.createdAt, caseData.status] + ); + const emailContent = useMemo( + () => ({ + subject: i18n.EMAIL_SUBJECT(caseData.title), + body: i18n.EMAIL_BODY(caseLink), + }), + [caseLink, caseData.title] + ); + + useEffect(() => { + if (initLoadingData && !isLoadingUserActions) { + setInitLoadingData(false); + } + }, [initLoadingData, isLoadingUserActions]); + + const backOptions = useMemo( + () => ({ + href: getCaseUrl(search), + text: i18n.BACK_TO_ALL, + dataTestSubj: 'backToCases', + }), + [search] + ); + + return ( + <> + + + } + title={caseData.title} + > + + + + + + {!initLoadingData && pushCallouts != null && pushCallouts} + + + {initLoadingData && } + {!initLoadingData && ( + <> + + + + + + + {hasDataToPush && ( + + {pushButton} + + )} + + + )} + + + + + + + + + + + + + ); + } +); + +export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => { + const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId); + if (isError) { + return null; + } + if (isLoading) { + return ( + + + + + + ); + } + + return ( + + ); +}); + +CaseComponent.displayName = 'CaseComponent'; +CaseView.displayName = 'CaseView'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts new file mode 100644 index 0000000000000..b8219ad52f5b0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export * from '../../translations'; + +export const SHOWING_CASES = (actionDate: string, actionName: string, userName: string) => + i18n.translate('xpack.securitySolution.case.caseView.actionHeadline', { + values: { + actionDate, + actionName, + userName, + }, + defaultMessage: '{userName} {actionName} on {actionDate}', + }); + +export const ADDED_FIELD = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.addedField', + { + defaultMessage: 'added', + } +); + +export const CHANGED_FIELD = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.changededField', + { + defaultMessage: 'changed', + } +); + +export const SELECTED_THIRD_PARTY = (thirdParty: string) => + i18n.translate('xpack.securitySolution.case.caseView.actionLabel.selectedThirdParty', { + values: { + thirdParty, + }, + defaultMessage: 'selected { thirdParty } as incident management system', + }); + +export const REMOVED_THIRD_PARTY = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.removedThirdParty', + { + defaultMessage: 'removed external incident management system', + } +); + +export const EDITED_FIELD = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.editedField', + { + defaultMessage: 'edited', + } +); + +export const REMOVED_FIELD = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.removedField', + { + defaultMessage: 'removed', + } +); + +export const VIEW_INCIDENT = (incidentNumber: string) => + i18n.translate('xpack.securitySolution.case.caseView.actionLabel.viewIncident', { + defaultMessage: 'View {incidentNumber}', + values: { + incidentNumber, + }, + }); + +export const PUSHED_NEW_INCIDENT = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.pushedNewIncident', + { + defaultMessage: 'pushed as new incident', + } +); + +export const UPDATE_INCIDENT = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.updateIncident', + { + defaultMessage: 'updated incident', + } +); + +export const ADDED_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.addDescription', + { + defaultMessage: 'added description', + } +); + +export const EDIT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.case.caseView.edit.description', + { + defaultMessage: 'Edit description', + } +); + +export const QUOTE = i18n.translate('xpack.securitySolution.case.caseView.edit.quote', { + defaultMessage: 'Quote', +}); + +export const EDIT_COMMENT = i18n.translate('xpack.securitySolution.case.caseView.edit.comment', { + defaultMessage: 'Edit comment', +}); + +export const ON = i18n.translate('xpack.securitySolution.case.caseView.actionLabel.on', { + defaultMessage: 'on', +}); + +export const ADDED_COMMENT = i18n.translate( + 'xpack.securitySolution.case.caseView.actionLabel.addComment', + { + defaultMessage: 'added comment', + } +); + +export const STATUS = i18n.translate('xpack.securitySolution.case.caseView.statusLabel', { + defaultMessage: 'Status', +}); + +export const CASE = i18n.translate('xpack.securitySolution.case.caseView.case', { + defaultMessage: 'case', +}); + +export const COMMENT = i18n.translate('xpack.securitySolution.case.caseView.comment', { + defaultMessage: 'comment', +}); + +export const CASE_OPENED = i18n.translate('xpack.securitySolution.case.caseView.caseOpened', { + defaultMessage: 'Case opened', +}); + +export const CASE_CLOSED = i18n.translate('xpack.securitySolution.case.caseView.caseClosed', { + defaultMessage: 'Case closed', +}); + +export const CASE_REFRESH = i18n.translate('xpack.securitySolution.case.caseView.caseRefresh', { + defaultMessage: 'Refresh case', +}); + +export const EMAIL_SUBJECT = (caseTitle: string) => + i18n.translate('xpack.securitySolution.case.caseView.emailSubject', { + values: { caseTitle }, + defaultMessage: 'Security Case - {caseTitle}', + }); + +export const EMAIL_BODY = (caseUrl: string) => + i18n.translate('xpack.securitySolution.case.caseView.emailBody', { + values: { caseUrl }, + defaultMessage: 'Case reference: {caseUrl}', + }); +export const UNKNOWN = i18n.translate('xpack.securitySolution.case.caseView.unknown', { + defaultMessage: 'Unknown', +}); diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/__mock__/index.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/configure_cases/__mock__/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx new file mode 100644 index 0000000000000..aefe515f4bd4d --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; +import { EuiText } from '@elastic/eui'; + +import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button'; +import { TestProviders } from '../../../common/mock'; +import { searchURL } from './__mock__'; + +describe('Configuration button', () => { + let wrapper: ReactWrapper; + const props: ConfigureCaseButtonProps = { + isDisabled: false, + label: 'My label', + msgTooltip: <>, + showToolTip: false, + titleTooltip: '', + urlSearch: searchURL, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders without the tooltip', () => { + expect(wrapper.find('[data-test-subj="configure-case-button"]').first().exists()).toBe(true); + + expect(wrapper.find('[data-test-subj="configure-case-tooltip"]').first().exists()).toBe(false); + }); + + test('it pass the correct props to the button', () => { + expect(wrapper.find('[data-test-subj="configure-case-button"]').first().props()).toMatchObject({ + href: `#/link-to/case/configure${searchURL}`, + iconType: 'controlsHorizontal', + isDisabled: false, + 'aria-label': 'My label', + children: 'My label', + }); + }); + + test('it renders the tooltip', () => { + const msgTooltip = {'My message tooltip'}; + + const newWrapper = mount( + , + { + wrappingComponent: TestProviders, + } + ); + + expect(newWrapper.find('[data-test-subj="configure-case-tooltip"]').first().exists()).toBe( + true + ); + + expect(wrapper.find('[data-test-subj="configure-case-button"]').first().exists()).toBe(true); + }); + + test('it shows the tooltip when hovering the button', () => { + const msgTooltip = 'My message tooltip'; + const titleTooltip = 'My title'; + + const newWrapper = mount( + {msgTooltip}} + />, + { + wrappingComponent: TestProviders, + } + ); + + newWrapper.find('[data-test-subj="configure-case-button"]').first().simulate('mouseOver'); + + expect(newWrapper.find('.euiToolTipPopover').text()).toBe(`${titleTooltip}${msgTooltip}`); + }); +}); diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/button.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/configure_cases/button.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/closure_options.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.test.tsx similarity index 80% rename from x-pack/plugins/siem/public/cases/components/configure_cases/closure_options.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.test.tsx index 6192fd0ee9fff..7a7901f826590 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/closure_options.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.test.tsx @@ -26,29 +26,20 @@ describe('ClosureOptions', () => { test('it shows the closure options form group', () => { expect( - wrapper - .find('[data-test-subj="case-closure-options-form-group"]') - .first() - .exists() + wrapper.find('[data-test-subj="case-closure-options-form-group"]').first().exists() ).toBe(true); }); test('it shows the closure options form row', () => { - expect( - wrapper - .find('[data-test-subj="case-closure-options-form-row"]') - .first() - .exists() - ).toBe(true); + expect(wrapper.find('[data-test-subj="case-closure-options-form-row"]').first().exists()).toBe( + true + ); }); test('it shows closure options', () => { - expect( - wrapper - .find('[data-test-subj="case-closure-options-radio"]') - .first() - .exists() - ).toBe(true); + expect(wrapper.find('[data-test-subj="case-closure-options-radio"]').first().exists()).toBe( + true + ); }); test('it pass the correct props to child', () => { diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/closure_options.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/configure_cases/closure_options.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.tsx diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/closure_options_radio.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.test.tsx similarity index 94% rename from x-pack/plugins/siem/public/cases/components/configure_cases/closure_options_radio.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.test.tsx index dae2204bc4665..9040cced08532 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/closure_options_radio.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.test.tsx @@ -24,12 +24,9 @@ describe('ClosureOptionsRadio', () => { }); test('it renders', () => { - expect( - wrapper - .find('[data-test-subj="closure-options-radio-group"]') - .first() - .exists() - ).toBe(true); + expect(wrapper.find('[data-test-subj="closure-options-radio-group"]').first().exists()).toBe( + true + ); }); test('it shows the correct number of radio buttons', () => { diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/closure_options_radio.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/configure_cases/closure_options_radio.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.tsx diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/connectors.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx similarity index 87% rename from x-pack/plugins/siem/public/cases/components/configure_cases/connectors.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx index 4f6fad8491206..ac7863d574dc9 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/connectors.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx @@ -32,30 +32,17 @@ describe('Connectors', () => { }); test('it shows the connectors from group', () => { - expect( - wrapper - .find('[data-test-subj="case-connectors-form-group"]') - .first() - .exists() - ).toBe(true); + expect(wrapper.find('[data-test-subj="case-connectors-form-group"]').first().exists()).toBe( + true + ); }); test('it shows the connectors form row', () => { - expect( - wrapper - .find('[data-test-subj="case-connectors-form-row"]') - .first() - .exists() - ).toBe(true); + expect(wrapper.find('[data-test-subj="case-connectors-form-row"]').first().exists()).toBe(true); }); test('it shows the connectors dropdown', () => { - expect( - wrapper - .find('[data-test-subj="case-connectors-dropdown"]') - .first() - .exists() - ).toBe(true); + expect(wrapper.find('[data-test-subj="case-connectors-dropdown"]').first().exists()).toBe(true); }); test('it pass the correct props to child', () => { diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/connectors.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx similarity index 97% rename from x-pack/plugins/siem/public/cases/components/configure_cases/connectors.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx index 76e816a2909ab..947d68fd04f7b 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/connectors.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx @@ -47,7 +47,7 @@ const ConnectorsComponent: React.FC = ({ handleShowEditFlyout, }) => { const connectorsName = useMemo( - () => connectors.find(c => c.id === selectedConnector)?.name ?? 'none', + () => connectors.find((c) => c.id === selectedConnector)?.name ?? 'none', [connectors, selectedConnector] ); diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/cases/components/configure_cases/connectors_dropdown.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.test.tsx index da20078dde0d0..f817cb0ffb617 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/connectors_dropdown.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.test.tsx @@ -27,12 +27,7 @@ describe('ConnectorsDropdown', () => { }); test('it renders', () => { - expect( - wrapper - .find('[data-test-subj="dropdown-connectors"]') - .first() - .exists() - ).toBe(true); + expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().exists()).toBe(true); }); test('it formats the connectors correctly', () => { @@ -62,10 +57,7 @@ describe('ConnectorsDropdown', () => { }); expect( - newWrapper - .find('[data-test-subj="dropdown-connectors"]') - .first() - .prop('disabled') + newWrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled') ).toEqual(true); }); @@ -75,10 +67,7 @@ describe('ConnectorsDropdown', () => { }); expect( - newWrapper - .find('[data-test-subj="dropdown-connectors"]') - .first() - .prop('isLoading') + newWrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') ).toEqual(true); }); diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/connectors_dropdown.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/configure_cases/connectors_dropdown.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx similarity index 87% rename from x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx index 7f9ad87706693..67963c7487826 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx @@ -31,17 +31,11 @@ describe('FieldMappingRow', () => { test('it renders', () => { expect( - wrapper - .find('[data-test-subj="case-configure-field-mapping-cols"]') - .first() - .exists() + wrapper.find('[data-test-subj="case-configure-field-mapping-cols"]').first().exists() ).toBe(true); expect( - wrapper - .find('[data-test-subj="case-configure-field-mapping-row-wrapper"]') - .first() - .exists() + wrapper.find('[data-test-subj="case-configure-field-mapping-row-wrapper"]').first().exists() ).toBe(true); expect(wrapper.find(FieldMappingRow).length).toEqual(3); @@ -58,7 +52,7 @@ describe('FieldMappingRow', () => { test('it pass the corrects props to mapping row', () => { const rows = wrapper.find(FieldMappingRow); rows.forEach((row, index) => { - expect(row.prop('siemField')).toEqual(mapping[index].source); + expect(row.prop('securitySolutionField')).toEqual(mapping[index].source); expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType); expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target); }); @@ -74,7 +68,7 @@ describe('FieldMappingRow', () => { const rows = newWrapper.find(FieldMappingRow); rows.forEach((row, index) => { - expect(row.prop('siemField')).toEqual(defaultMapping[index].source); + expect(row.prop('securitySolutionField')).toEqual(defaultMapping[index].source); expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType); expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target); }); diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx similarity index 97% rename from x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx index 0eab690915f40..415faa96eeedd 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx @@ -127,12 +127,12 @@ const FieldMappingComponent: React.FC = ({ - {(mapping ?? defaultMapping).map(item => ( + {(mapping ?? defaultMapping).map((item) => ( { const props: RowProps = { id: 'title', disabled: false, - siemField: 'title', + securitySolutionField: 'title', thirdPartyOptions, actionTypeOptions, onChangeActionType, @@ -66,25 +66,16 @@ describe('FieldMappingRow', () => { test('it renders', () => { expect( - wrapper - .find('[data-test-subj="case-configure-third-party-select-title"]') - .first() - .exists() + wrapper.find('[data-test-subj="case-configure-third-party-select-title"]').first().exists() ).toBe(true); expect( - wrapper - .find('[data-test-subj="case-configure-action-type-select-title"]') - .first() - .exists() + wrapper.find('[data-test-subj="case-configure-action-type-select-title"]').first().exists() ).toBe(true); }); test('it passes thirdPartyOptions correctly', () => { - const selectProps = wrapper - .find(EuiSuperSelect) - .first() - .props(); + const selectProps = wrapper.find(EuiSuperSelect).first().props(); expect(selectProps.options).toEqual( expect.arrayContaining([ @@ -101,10 +92,7 @@ describe('FieldMappingRow', () => { }); test('it passes the correct actionTypeOptions', () => { - const selectProps = wrapper - .find(EuiSuperSelect) - .at(1) - .props(); + const selectProps = wrapper.find(EuiSuperSelect).at(1).props(); expect(selectProps.options).toEqual( expect.arrayContaining([ diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping_row.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx similarity index 85% rename from x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping_row.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx index 922ea7222efce..6e688b213f82c 100644 --- a/x-pack/plugins/siem/public/cases/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx @@ -20,7 +20,7 @@ import { AllThirdPartyFields } from '../../../common/lib/connectors/types'; export interface RowProps { id: string; disabled: boolean; - siemField: CaseField; + securitySolutionField: CaseField; thirdPartyOptions: Array>; actionTypeOptions: Array>; onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void; @@ -32,7 +32,7 @@ export interface RowProps { const FieldMappingRowComponent: React.FC = ({ id, disabled, - siemField, + securitySolutionField, thirdPartyOptions, actionTypeOptions, onChangeActionType, @@ -40,13 +40,15 @@ const FieldMappingRowComponent: React.FC = ({ selectedActionType, selectedThirdParty, }) => { - const siemFieldCapitalized = useMemo(() => capitalize(siemField), [siemField]); + const securitySolutionFieldCapitalized = useMemo(() => capitalize(securitySolutionField), [ + securitySolutionField, + ]); return ( - {siemFieldCapitalized} + {securitySolutionFieldCapitalized} @@ -58,7 +60,7 @@ const FieldMappingRowComponent: React.FC = ({ disabled={disabled} options={thirdPartyOptions} valueOfSelected={selectedThirdParty} - onChange={onChangeThirdParty.bind(null, siemField)} + onChange={onChangeThirdParty.bind(null, securitySolutionField)} data-test-subj={`case-configure-third-party-select-${id}`} /> @@ -67,7 +69,7 @@ const FieldMappingRowComponent: React.FC = ({ disabled={disabled} options={actionTypeOptions} valueOfSelected={selectedActionType} - onChange={onChangeActionType.bind(null, siemField)} + onChange={onChangeActionType.bind(null, securitySolutionField)} data-test-subj={`case-configure-action-type-select-${id}`} /> diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx new file mode 100644 index 0000000000000..f070431a34f21 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx @@ -0,0 +1,475 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; + +import { ConfigureCases } from '.'; +import { TestProviders } from '../../../common/mock'; +import { Connectors } from './connectors'; +import { ClosureOptions } from './closure_options'; +import { + ActionsConnectorsContextProvider, + ConnectorAddFlyout, + ConnectorEditFlyout, +} from '../../../../../triggers_actions_ui/public'; + +import { useKibana } from '../../../common/lib/kibana'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { useCaseConfigure } from '../../containers/configure/use_configure'; +import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; + +import { + connectors, + searchURL, + useCaseConfigureResponse, + useConnectorsResponse, + kibanaMockImplementationArgs, +} from './__mock__'; + +jest.mock('../../../common/lib/kibana'); +jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_configure'); +jest.mock('../../../common/components/navigation/use_get_url_search'); + +const useKibanaMock = useKibana as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; +const useCaseConfigureMock = useCaseConfigure as jest.Mock; +const useGetUrlSearchMock = useGetUrlSearch as jest.Mock; +describe('ConfigureCases', () => { + describe('rendering', () => { + let wrapper: ReactWrapper; + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the Connectors', () => { + expect(wrapper.find('[data-test-subj="dropdown-connectors"]').exists()).toBeTruthy(); + }); + + test('it renders the ClosureType', () => { + expect(wrapper.find('[data-test-subj="closure-options-radio-group"]').exists()).toBeTruthy(); + }); + + test('it renders the ActionsConnectorsContextProvider', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ActionsConnectorsContextProvider).exists()).toBeTruthy(); + }); + + test('it renders the ConnectorAddFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorAddFlyout).exists()).toBeTruthy(); + }); + + test('it does NOT render the ConnectorEditFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeFalsy(); + }); + + test('it does NOT render the EuiCallOut', () => { + expect( + wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists() + ).toBeFalsy(); + }); + }); + + describe('Unhappy path', () => { + let wrapper: ReactWrapper; + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + closureType: 'close-by-user', + connectorId: 'not-id', + connectorName: 'unchanged', + currentConfiguration: { + connectorName: 'unchanged', + connectorId: 'not-id', + closureType: 'close-by-user', + }, + })); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows the warning callout when configuration is invalid', () => { + expect( + wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists() + ).toBeTruthy(); + }); + + test('it hides the update connector button when the connectorId is invalid', () => { + expect( + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .exists() + ).toBeFalsy(); + }); + }); + + describe('Happy path', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: connectors[0].config.casesConfiguration.mapping, + closureType: 'close-by-user', + connectorId: 'servicenow-1', + connectorName: 'unchanged', + currentConfiguration: { + connectorName: 'unchanged', + connectorId: 'servicenow-1', + closureType: 'close-by-user', + }, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the ConnectorEditFlyout', () => { + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeTruthy(); + }); + + test('it renders with correct props', () => { + // Connector + expect(wrapper.find(Connectors).prop('connectors')).toEqual(connectors); + expect(wrapper.find(Connectors).prop('disabled')).toBe(false); + expect(wrapper.find(Connectors).prop('isLoading')).toBe(false); + expect(wrapper.find(Connectors).prop('selectedConnector')).toBe('servicenow-1'); + + // ClosureOptions + expect(wrapper.find(ClosureOptions).prop('disabled')).toBe(false); + expect(wrapper.find(ClosureOptions).prop('closureTypeSelected')).toBe('close-by-user'); + + // Flyouts + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([ + expect.objectContaining({ + id: '.servicenow', + }), + expect.objectContaining({ + id: '.jira', + }), + ]); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorEditFlyout).prop('initialConnector')).toEqual(connectors[0]); + }); + + test('it disables correctly when the user cannot crud', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('button[data-test-subj="dropdown-connectors"]').prop('disabled')).toBe( + true + ); + + expect( + newWrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .prop('disabled') + ).toBe(true); + + // Two closure options + expect( + newWrapper + .find('[data-test-subj="closure-options-radio-group"] input') + .first() + .prop('disabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="closure-options-radio-group"] input') + .at(1) + .prop('disabled') + ).toBe(true); + }); + }); + + describe('loading connectors', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: connectors[1].config.casesConfiguration.mapping, + closureType: 'close-by-user', + connectorId: 'servicenow-2', + connectorName: 'unchanged', + currentConfiguration: { + connectorName: 'unchanged', + connectorId: 'servicenow-1', + closureType: 'close-by-user', + }, + })); + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it disables correctly Connector when loading connectors', () => { + expect( + wrapper.find('button[data-test-subj="dropdown-connectors"]').prop('disabled') + ).toBeTruthy(); + }); + + test('it pass the correct value to isLoading attribute on Connector', () => { + expect(wrapper.find(Connectors).prop('isLoading')).toBe(true); + }); + + test('it disables correctly ClosureOptions when loading connectors', () => { + expect(wrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it hides the update connector button when loading the connectors', () => { + expect( + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .prop('disabled') + ).toBe(true); + }); + }); + + describe('saving configuration', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + connectorId: 'servicenow-1', + persistLoading: true, + })); + + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it disables correctly Connector when saving configuration', () => { + expect(wrapper.find(Connectors).prop('disabled')).toBe(true); + }); + + test('it disables correctly ClosureOptions when saving configuration', () => { + expect( + wrapper + .find('[data-test-subj="closure-options-radio-group"] input') + .first() + .prop('disabled') + ).toBe(true); + + expect( + wrapper.find('[data-test-subj="closure-options-radio-group"] input').at(1).prop('disabled') + ).toBe(true); + }); + + test('it disables the update connector button when saving the configuration', () => { + expect( + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .prop('disabled') + ).toBe(true); + }); + }); + + describe('loading configuration', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + loading: true, + })); + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it hides the update connector button when loading the configuration', () => { + expect( + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .exists() + ).toBeFalsy(); + }); + }); + + describe('connectors', () => { + let wrapper: ReactWrapper; + const persistCaseConfigure = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: connectors[0].config.casesConfiguration.mapping, + closureType: 'close-by-user', + connectorId: 'servicenow-1', + connectorName: 'My connector', + currentConfiguration: { + connectorName: 'My connector', + connectorId: 'My connector', + closureType: 'close-by-user', + }, + persistCaseConfigure, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it submits the configuration correctly when changing connector', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click'); + wrapper.update(); + + expect(persistCaseConfigure).toHaveBeenCalled(); + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connectorId: 'servicenow-2', + connectorName: 'My Connector 2', + closureType: 'close-by-user', + }); + }); + + test('the text of the update button is changed successfully', () => { + useCaseConfigureMock + .mockImplementationOnce(() => ({ + ...useCaseConfigureResponse, + connectorId: 'servicenow-1', + })) + .mockImplementation(() => ({ + ...useCaseConfigureResponse, + connectorId: 'servicenow-2', + })); + + wrapper = mount(, { wrappingComponent: TestProviders }); + + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click'); + wrapper.update(); + + expect( + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .text() + ).toBe('Update My Connector 2'); + }); + }); +}); + +describe('closure options', () => { + let wrapper: ReactWrapper; + const persistCaseConfigure = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: connectors[0].config.casesConfiguration.mapping, + closureType: 'close-by-user', + connectorId: 'servicenow-1', + connectorName: 'My connector', + currentConfiguration: { + connectorName: 'My connector', + connectorId: 'My connector', + closureType: 'close-by-user', + }, + persistCaseConfigure, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it submits the configuration correctly when changing closure type', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + expect(persistCaseConfigure).toHaveBeenCalled(); + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connectorId: 'servicenow-1', + connectorName: 'My Connector', + closureType: 'close-by-pushing', + }); + }); +}); + +describe('user interactions', () => { + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: connectors[1].config.casesConfiguration.mapping, + closureType: 'close-by-user', + connectorId: 'servicenow-2', + connectorName: 'unchanged', + currentConfiguration: { + connectorName: 'unchanged', + connectorId: 'servicenow-2', + closureType: 'close-by-user', + }, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + }); + + test('it show the add flyout when pressing the add connector button', () => { + const wrapper = mount(, { wrappingComponent: TestProviders }); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-add-connector"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + }); + + test('it show the edit flyout when pressing the update connector button', () => { + const wrapper = mount(, { wrappingComponent: TestProviders }); + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx new file mode 100644 index 0000000000000..6e1ef293fd5c4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx @@ -0,0 +1,216 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useEffect, useState, Dispatch, SetStateAction } from 'react'; +import styled, { css } from 'styled-components'; + +import { EuiCallOut } from '@elastic/eui'; + +import { useKibana } from '../../../common/lib/kibana'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { useCaseConfigure } from '../../containers/configure/use_configure'; +import { + ActionsConnectorsContextProvider, + ActionType, + ConnectorAddFlyout, + ConnectorEditFlyout, +} from '../../../../../triggers_actions_ui/public'; + +import { ClosureType } from '../../containers/configure/types'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ActionConnectorTableItem } from '../../../../../triggers_actions_ui/public/types'; +import { connectorsConfiguration } from '../../../common/lib/connectors/config'; + +import { Connectors } from './connectors'; +import { ClosureOptions } from './closure_options'; +import { SectionWrapper } from '../wrappers'; +import * as i18n from './translations'; + +const FormWrapper = styled.div` + ${({ theme }) => css` + & > * { + margin-top 40px; + } + + & > :first-child { + margin-top: 0; + } + + padding-top: ${theme.eui.paddingSizes.xl}; + padding-bottom: ${theme.eui.paddingSizes.xl}; + `} +`; + +const actionTypes: ActionType[] = Object.values(connectorsConfiguration); + +interface ConfigureCasesComponentProps { + userCanCrud: boolean; +} + +const ConfigureCasesComponent: React.FC = ({ userCanCrud }) => { + const { http, triggers_actions_ui, notifications, application, docLinks } = useKibana().services; + + const [connectorIsValid, setConnectorIsValid] = useState(true); + const [addFlyoutVisible, setAddFlyoutVisibility] = useState(false); + const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); + const [editedConnectorItem, setEditedConnectorItem] = useState( + null + ); + + const { + connectorId, + closureType, + currentConfiguration, + loading: loadingCaseConfigure, + persistLoading, + version, + persistCaseConfigure, + setConnector, + setClosureType, + } = useCaseConfigure(); + + const { loading: isLoadingConnectors, connectors, refetchConnectors } = useConnectors(); + + // ActionsConnectorsContextProvider reloadConnectors prop expects a Promise. + // TODO: Fix it if reloadConnectors type change. + const reloadConnectors = useCallback(async () => refetchConnectors(), []); + const isLoadingAny = isLoadingConnectors || persistLoading || loadingCaseConfigure; + const updateConnectorDisabled = isLoadingAny || !connectorIsValid || connectorId === 'none'; + + const onClickUpdateConnector = useCallback(() => { + setEditFlyoutVisibility(true); + }, []); + + const handleSetAddFlyoutVisibility = useCallback( + (isVisible: boolean) => { + setAddFlyoutVisibility(isVisible); + }, + [currentConfiguration, connectorId, closureType] + ); + + const handleSetEditFlyoutVisibility = useCallback( + (isVisible: boolean) => { + setEditFlyoutVisibility(isVisible); + }, + [currentConfiguration, connectorId, closureType] + ); + + const onChangeConnector = useCallback( + (id: string) => { + if (id === 'add-connector') { + setAddFlyoutVisibility(true); + return; + } + + setConnector(id); + persistCaseConfigure({ + connectorId: id, + connectorName: connectors.find((c) => c.id === id)?.name ?? '', + closureType, + }); + }, + [connectorId, closureType, version] + ); + + const onChangeClosureType = useCallback( + (type: ClosureType) => { + setClosureType(type); + persistCaseConfigure({ + connectorId, + connectorName: connectors.find((c) => c.id === connectorId)?.name ?? '', + closureType: type, + }); + }, + [connectorId, closureType, version] + ); + + useEffect(() => { + if ( + !isLoadingConnectors && + connectorId !== 'none' && + !connectors.some((c) => c.id === connectorId) + ) { + setConnectorIsValid(false); + } else if ( + !isLoadingConnectors && + (connectorId === 'none' || connectors.some((c) => c.id === connectorId)) + ) { + setConnectorIsValid(true); + } + }, [connectors, connectorId]); + + useEffect(() => { + if (!isLoadingConnectors && connectorId !== 'none') { + setEditedConnectorItem( + connectors.find((c) => c.id === connectorId) as ActionConnectorTableItem + ); + } + }, [connectors, connectorId]); + + return ( + + {!connectorIsValid && ( + + + {i18n.WARNING_NO_CONNECTOR_MESSAGE} + + + )} + + + + + + + + >} + actionTypes={actionTypes} + /> + {editedConnectorItem && ( + > + } + /> + )} + + + ); +}; + +export const ConfigureCases = React.memo(ConfigureCasesComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx new file mode 100644 index 0000000000000..0bbee42a4f2ef --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { TestProviders } from '../../../common/mock'; +import { Mapping, MappingProps } from './mapping'; +import { mapping } from './__mock__'; + +describe('Mapping', () => { + let wrapper: ReactWrapper; + const onChangeMapping = jest.fn(); + const setEditFlyoutVisibility = jest.fn(); + const props: MappingProps = { + disabled: false, + mapping, + updateConnectorDisabled: false, + onChangeMapping, + setEditFlyoutVisibility, + connectorActionTypeId: '.servicenow', + }; + + beforeEach(() => { + jest.clearAllMocks(); + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + afterEach(() => { + wrapper.unmount(); + }); + + describe('Common', () => { + test('it shows mapping form group', () => { + expect(wrapper.find('[data-test-subj="case-mapping-form-group"]').first().exists()).toBe( + true + ); + }); + + test('it shows mapping form row', () => { + expect(wrapper.find('[data-test-subj="case-mapping-form-row"]').first().exists()).toBe(true); + }); + + test('it shows the update button', () => { + expect( + wrapper.find('[data-test-subj="case-mapping-update-connector-button"]').first().exists() + ).toBe(true); + }); + + test('it shows the field mapping', () => { + expect(wrapper.find('[data-test-subj="case-mapping-field"]').first().exists()).toBe(true); + }); + + test('it updates thirdParty correctly', () => { + wrapper + .find('button[data-test-subj="case-configure-third-party-select-title"]') + .simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-mapping-description"]').simulate('click'); + wrapper.update(); + + expect(onChangeMapping).toHaveBeenCalledWith([ + { source: 'title', target: 'description', actionType: 'overwrite' }, + { source: 'description', target: 'not_mapped', actionType: 'append' }, + { source: 'comments', target: 'comments', actionType: 'append' }, + ]); + }); + + test('it updates actionType correctly', () => { + wrapper + .find('button[data-test-subj="case-configure-action-type-select-title"]') + .simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="edit-update-option-nothing"]').simulate('click'); + wrapper.update(); + + expect(onChangeMapping).toHaveBeenCalledWith([ + { source: 'title', target: 'short_description', actionType: 'nothing' }, + { source: 'description', target: 'description', actionType: 'append' }, + { source: 'comments', target: 'comments', actionType: 'append' }, + ]); + }); + + test('it shows the correct action types', () => { + wrapper + .find('button[data-test-subj="case-configure-action-type-select-title"]') + .simulate('click'); + wrapper.update(); + expect( + wrapper.find('button[data-test-subj="edit-update-option-nothing"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="edit-update-option-overwrite"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="edit-update-option-append"]').first().exists() + ).toBeTruthy(); + }); + }); + + describe('Connectors', () => { + describe('ServiceNow', () => { + test('it shows the correct thirdParty fields for title', () => { + wrapper + .find('button[data-test-subj="case-configure-third-party-select-title"]') + .simulate('click'); + wrapper.update(); + + expect( + wrapper + .find('button[data-test-subj="dropdown-mapping-short_description"]') + .first() + .exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-description"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-not_mapped"]').first().exists() + ).toBeTruthy(); + }); + + test('it shows the correct thirdParty fields for description', () => { + wrapper + .find('button[data-test-subj="case-configure-third-party-select-description"]') + .simulate('click'); + wrapper.update(); + + expect( + wrapper + .find('button[data-test-subj="dropdown-mapping-short_description"]') + .first() + .exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-description"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-not_mapped"]').first().exists() + ).toBeTruthy(); + }); + + test('it shows the correct thirdParty fields for comments', () => { + wrapper + .find('button[data-test-subj="case-configure-third-party-select-comments"]') + .simulate('click'); + wrapper.update(); + + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-comments"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-not_mapped"]').first().exists() + ).toBeTruthy(); + }); + }); + + describe('Jira', () => { + beforeEach(() => { + wrapper = mount(, { + wrappingComponent: TestProviders, + }); + }); + + test('it shows the correct thirdParty fields for title', () => { + wrapper + .find('button[data-test-subj="case-configure-third-party-select-title"]') + .simulate('click'); + wrapper.update(); + + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-summary"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-description"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-not_mapped"]').first().exists() + ).toBeTruthy(); + }); + + test('it shows the correct thirdParty fields for description', () => { + wrapper + .find('button[data-test-subj="case-configure-third-party-select-description"]') + .simulate('click'); + wrapper.update(); + + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-summary"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-description"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-not_mapped"]').first().exists() + ).toBeTruthy(); + }); + + test('it shows the correct thirdParty fields for comments', () => { + wrapper + .find('button[data-test-subj="case-configure-third-party-select-comments"]') + .simulate('click'); + wrapper.update(); + + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-comments"]').first().exists() + ).toBeTruthy(); + expect( + wrapper.find('button[data-test-subj="dropdown-mapping-not_mapped"]').first().exists() + ).toBeTruthy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/mapping.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/configure_cases/mapping.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts new file mode 100644 index 0000000000000..9ef6ce2f3d4a9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const INCIDENT_MANAGEMENT_SYSTEM_TITLE = i18n.translate( + 'xpack.securitySolution.case.configureCases.incidentManagementSystemTitle', + { + defaultMessage: 'Connect to external incident management system', + } +); + +export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate( + 'xpack.securitySolution.case.configureCases.incidentManagementSystemDesc', + { + defaultMessage: + 'You may optionally connect Security cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', + } +); + +export const INCIDENT_MANAGEMENT_SYSTEM_LABEL = i18n.translate( + 'xpack.securitySolution.case.configureCases.incidentManagementSystemLabel', + { + defaultMessage: 'Incident management system', + } +); + +export const NO_CONNECTOR = i18n.translate( + 'xpack.securitySolution.case.configureCases.noConnector', + { + defaultMessage: 'No connector selected', + } +); + +export const ADD_NEW_CONNECTOR = i18n.translate( + 'xpack.securitySolution.case.configureCases.addNewConnector', + { + defaultMessage: 'Add new connector', + } +); + +export const CASE_CLOSURE_OPTIONS_TITLE = i18n.translate( + 'xpack.securitySolution.case.configureCases.caseClosureOptionsTitle', + { + defaultMessage: 'Case Closures', + } +); + +export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate( + 'xpack.securitySolution.case.configureCases.caseClosureOptionsDesc', + { + defaultMessage: + 'Define how you wish Security cases to be closed. Automated case closures require an established connection to an external incident management system.', + } +); + +export const CASE_CLOSURE_OPTIONS_LABEL = i18n.translate( + 'xpack.securitySolution.case.configureCases.caseClosureOptionsLabel', + { + defaultMessage: 'Case closure options', + } +); + +export const CASE_CLOSURE_OPTIONS_MANUAL = i18n.translate( + 'xpack.securitySolution.case.configureCases.caseClosureOptionsManual', + { + defaultMessage: 'Manually close Security cases', + } +); + +export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate( + 'xpack.securitySolution.case.configureCases.caseClosureOptionsNewIncident', + { + defaultMessage: + 'Automatically close Security cases when pushing new incident to external system', + } +); + +export const CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT = i18n.translate( + 'xpack.securitySolution.case.configureCases.caseClosureOptionsClosedIncident', + { + defaultMessage: 'Automatically close Security cases when incident is closed in external system', + } +); + +export const FIELD_MAPPING_TITLE = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingTitle', + { + defaultMessage: 'Field mappings', + } +); + +export const FIELD_MAPPING_DESC = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingDesc', + { + defaultMessage: + 'Map Security case fields when pushing data to a third-party. Field mappings require an established connection to an external incident management system.', + } +); + +export const FIELD_MAPPING_FIRST_COL = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingFirstCol', + { + defaultMessage: 'Security case field', + } +); + +export const FIELD_MAPPING_SECOND_COL = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingSecondCol', + { + defaultMessage: 'External incident field', + } +); + +export const FIELD_MAPPING_THIRD_COL = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingThirdCol', + { + defaultMessage: 'On edit and update', + } +); + +export const FIELD_MAPPING_EDIT_NOTHING = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingEditNothing', + { + defaultMessage: 'Nothing', + } +); + +export const FIELD_MAPPING_EDIT_OVERWRITE = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingEditOverwrite', + { + defaultMessage: 'Overwrite', + } +); + +export const FIELD_MAPPING_EDIT_APPEND = i18n.translate( + 'xpack.securitySolution.case.configureCases.fieldMappingEditAppend', + { + defaultMessage: 'Append', + } +); + +export const CANCEL = i18n.translate('xpack.securitySolution.case.configureCases.cancelButton', { + defaultMessage: 'Cancel', +}); + +export const WARNING_NO_CONNECTOR_TITLE = i18n.translate( + 'xpack.securitySolution.case.configureCases.warningTitle', + { + defaultMessage: 'Warning', + } +); + +export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate( + 'xpack.securitySolution.case.configureCases.warningMessage', + { + defaultMessage: + 'The selected connector has been deleted. Either select a different connector or create a new one.', + } +); + +export const MAPPING_FIELD_NOT_MAPPED = i18n.translate( + 'xpack.securitySolution.case.configureCases.mappingFieldNotMapped', + { + defaultMessage: 'Not mapped', + } +); + +export const UPDATE_CONNECTOR = i18n.translate( + 'xpack.securitySolution.case.configureCases.updateConnector', + { + defaultMessage: 'Update connector', + } +); + +export const UPDATE_SELECTED_CONNECTOR = (connectorName: string): string => { + return i18n.translate('xpack.securitySolution.case.configureCases.updateSelectedConnector', { + values: { connectorName }, + defaultMessage: 'Update { connectorName }', + }); +}; diff --git a/x-pack/plugins/siem/public/cases/components/configure_cases/utils.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/configure_cases/utils.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts b/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts new file mode 100644 index 0000000000000..d51c3e00eb01f --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + CaseField, + ActionType, + CasesConfigurationMapping, + ThirdPartyField, +} from '../../containers/configure/types'; + +export const setActionTypeToMapping = ( + caseField: CaseField, + newActionType: ActionType, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => { + const findItemIndex = mapping.findIndex((item) => item.source === caseField); + + if (findItemIndex >= 0) { + return [ + ...mapping.slice(0, findItemIndex), + { ...mapping[findItemIndex], actionType: newActionType }, + ...mapping.slice(findItemIndex + 1), + ]; + } + + return [...mapping]; +}; + +export const setThirdPartyToMapping = ( + caseField: CaseField, + newThirdPartyField: ThirdPartyField, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => + mapping.map((item) => { + if (item.source !== caseField && item.target === newThirdPartyField) { + return { ...item, target: 'not_mapped' }; + } else if (item.source === caseField) { + return { ...item, target: newThirdPartyField }; + } + return item; + }); diff --git a/x-pack/plugins/siem/public/cases/components/confirm_delete_case/index.tsx b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/confirm_delete_case/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts new file mode 100644 index 0000000000000..ad51d13206809 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +export * from '../../translations'; + +export const DELETE_TITLE = (caseTitle: string) => + i18n.translate('xpack.securitySolution.case.confirmDeleteCase.deleteTitle', { + values: { caseTitle }, + defaultMessage: 'Delete "{caseTitle}"', + }); + +export const CONFIRM_QUESTION = i18n.translate( + 'xpack.securitySolution.case.confirmDeleteCase.confirmQuestion', + { + defaultMessage: + 'By deleting this case, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?', + } +); +export const DELETE_SELECTED_CASES = i18n.translate( + 'xpack.securitySolution.case.confirmDeleteCase.selectedCases', + { + defaultMessage: 'Delete selected cases', + } +); + +export const CONFIRM_QUESTION_PLURAL = i18n.translate( + 'xpack.securitySolution.case.confirmDeleteCase.confirmQuestionPlural', + { + defaultMessage: + 'By deleting these cases, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?', + } +); diff --git a/x-pack/plugins/siem/public/cases/components/connector_selector/form.tsx b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/connector_selector/form.tsx rename to x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx new file mode 100644 index 0000000000000..78108f7d74658 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { Create } from '.'; +import { TestProviders } from '../../../common/mock'; +import { getFormMock } from '../__mock__/form'; +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; + +import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; +import { usePostCase } from '../../containers/use_post_case'; +import { useGetTags } from '../../containers/use_get_tags'; + +jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'); +jest.mock('../../containers/use_post_case'); +import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { wait } from '../../../common/lib/helpers'; +import { SiemPageName } from '../../../app/types'; +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); +jest.mock('../../containers/use_get_tags'); +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider', + () => ({ + FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) => + children({ tags: ['rad', 'dude'] }), + }) +); + +export const useFormMock = useForm as jest.Mock; + +const useInsertTimelineMock = useInsertTimeline as jest.Mock; +const usePostCaseMock = usePostCase as jest.Mock; + +const postCase = jest.fn(); +const handleCursorChange = jest.fn(); +const handleOnTimelineChange = jest.fn(); + +const defaultInsertTimeline = { + cursorPosition: { + start: 0, + end: 0, + }, + handleCursorChange, + handleOnTimelineChange, +}; + +const sampleTags = ['coke', 'pepsi']; +const sampleData = { + description: 'what a great description', + tags: sampleTags, + title: 'what a cool title', +}; +const defaultPostCase = { + isLoading: false, + isError: false, + caseData: null, + postCase, +}; +describe('Create case', () => { + // Suppress warnings about "noSuggestions" prop + /* eslint-disable no-console */ + const originalError = console.error; + beforeAll(() => { + console.error = jest.fn(); + }); + afterAll(() => { + console.error = originalError; + }); + /* eslint-enable no-console */ + const fetchTags = jest.fn(); + const formHookMock = getFormMock(sampleData); + beforeEach(() => { + jest.resetAllMocks(); + useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); + usePostCaseMock.mockImplementation(() => defaultPostCase); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); + (useGetTags as jest.Mock).mockImplementation(() => ({ + tags: sampleTags, + fetchTags, + })); + }); + + it('should post case on submit click', async () => { + const wrapper = mount( + + + + + + ); + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + await wait(); + expect(postCase).toBeCalledWith(sampleData); + }); + + it('should redirect to all cases on cancel click', () => { + const wrapper = mount( + + + + + + ); + wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click'); + expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual(`/${SiemPageName.case}`); + }); + it('should redirect to new case when caseData is there', () => { + const sampleId = '777777'; + usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, caseData: { id: sampleId } })); + mount( + + + + + + ); + expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual( + `/${SiemPageName.case}/${sampleId}` + ); + }); + + it('should render spinner when loading', () => { + usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, isLoading: true })); + const wrapper = mount( + + + + + + ); + expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy(); + }); + it('Tag options render with new tags added', () => { + const wrapper = mount( + + + + + + ); + expect( + wrapper.find(`[data-test-subj="caseTags"] [data-test-subj="input"]`).first().prop('options') + ).toEqual([{ label: 'coke' }, { label: 'pepsi' }, { label: 'rad' }, { label: 'dude' }]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx new file mode 100644 index 0000000000000..ae0ffe498c391 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useCallback, useEffect, useState } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPanel, +} from '@elastic/eui'; +import styled, { css } from 'styled-components'; +import { Redirect } from 'react-router-dom'; + +import { isEqual } from 'lodash/fp'; +import { CasePostRequest } from '../../../../../case/common/api'; +import { + Field, + Form, + getUseField, + useForm, + UseField, + FormDataProvider, +} from '../../../shared_imports'; +import { usePostCase } from '../../containers/use_post_case'; +import { schema } from './schema'; +import { InsertTimelinePopover } from '../../../timelines/components/timeline/insert_timeline_popover'; +import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; +import * as i18n from '../../translations'; +import { SiemPageName } from '../../../app/types'; +import { MarkdownEditorForm } from '../../../common/components//markdown_editor/form'; +import { useGetTags } from '../../containers/use_get_tags'; + +export const CommonUseField = getUseField({ component: Field }); + +const ContainerBig = styled.div` + ${({ theme }) => css` + margin-top: ${theme.eui.euiSizeXL}; + `} +`; + +const Container = styled.div` + ${({ theme }) => css` + margin-top: ${theme.eui.euiSize}; + `} +`; +const MySpinner = styled(EuiLoadingSpinner)` + position: absolute; + top: 50%; + left: 50%; + z-index: 99; +`; + +const initialCaseValue: CasePostRequest = { + description: '', + tags: [], + title: '', +}; + +export const Create = React.memo(() => { + const { caseData, isLoading, postCase } = usePostCase(); + const [isCancel, setIsCancel] = useState(false); + const { form } = useForm({ + defaultValue: initialCaseValue, + options: { stripEmptyFields: false }, + schema, + }); + const { tags: tagOptions } = useGetTags(); + const [options, setOptions] = useState( + tagOptions.map((label) => ({ + label, + })) + ); + useEffect( + () => + setOptions( + tagOptions.map((label) => ({ + label, + })) + ), + [tagOptions] + ); + const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline( + form, + 'description' + ); + + const onSubmit = useCallback(async () => { + const { isValid, data } = await form.submit(); + if (isValid) { + await postCase(data); + } + }, [form]); + + const handleSetIsCancel = useCallback(() => { + setIsCancel(true); + }, []); + + if (caseData != null && caseData.id) { + return ; + } + + if (isCancel) { + return ; + } + + return ( + + {isLoading && } +
    + + + + + + + ), + }} + /> + + + {({ tags: anotherTags }) => { + const current: string[] = options.map((opt) => opt.label); + const newOptions = anotherTags.reduce((acc: string[], item: string) => { + if (!acc.includes(item)) { + return [...acc, item]; + } + return acc; + }, current); + if (!isEqual(current, newOptions)) { + setOptions( + newOptions.map((label: string) => ({ + label, + })) + ); + } + return null; + }} + + + + + + + {i18n.CANCEL} + + + + + {i18n.CREATE_CASE} + + + + +
    + ); +}); + +Create.displayName = 'Create'; diff --git a/x-pack/plugins/siem/public/cases/components/create/optional_field_label/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/create/optional_field_label/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx diff --git a/x-pack/plugins/siem/public/cases/components/create/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/create/schema.tsx rename to x-pack/plugins/security_solution/public/cases/components/create/schema.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.test.tsx new file mode 100644 index 0000000000000..251d0b6e81bf8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.test.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { EditConnector } from './index'; +import { getFormMock, useFormMock } from '../__mock__/form'; +import { TestProviders } from '../../../common/mock'; +import { connectorsMock } from '../../containers/configure/mock'; +import { wait } from '../../../common/lib/helpers'; +import { act } from 'react-dom/test-utils'; +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); +const onSubmit = jest.fn(); +const defaultProps = { + connectors: connectorsMock, + disabled: false, + isLoading: false, + onSubmit, + selectedConnector: 'none', +}; + +describe('EditConnector ', () => { + const sampleConnector = '123'; + const formHookMock = getFormMock({ connector: sampleConnector }); + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + }); + it('Renders no connector, and then edit', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`span[data-test-subj="dropdown-connector-no-connector"]`).last().exists() + ).toBeTruthy(); + + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().exists()).toBeTruthy(); + }); + + it('Edit external service on submit', async () => { + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().exists()).toBeTruthy(); + + await act(async () => { + wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click'); + await wait(); + expect(onSubmit).toBeCalledWith(sampleConnector); + }); + }); + + it('Resets selector on cancel', async () => { + const props = { + ...defaultProps, + }; + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click'); + wrapper.update(); + + await act(async () => { + wrapper.find(`[data-test-subj="edit-connectors-cancel"]`).last().simulate('click'); + await wait(); + wrapper.update(); + expect(formHookMock.setFieldValue).toBeCalledWith( + 'connector', + defaultProps.selectedConnector + ); + }); + }); + + it('Renders loading spinner', () => { + const props = { ...defaultProps, isLoading: true }; + const wrapper = mount( + + + + ); + expect(wrapper.find(`[data-test-subj="connector-loading"]`).last().exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx new file mode 100644 index 0000000000000..ba0b97b6088a8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useState } from 'react'; +import { + EuiText, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiLoadingSpinner, +} from '@elastic/eui'; +import styled, { css } from 'styled-components'; +import * as i18n from '../../translations'; +import { Form, UseField, useForm } from '../../../shared_imports'; +import { schema } from './schema'; +import { ConnectorSelector } from '../connector_selector/form'; +import { Connector } from '../../../../../case/common/api/cases'; + +interface EditConnectorProps { + connectors: Connector[]; + disabled?: boolean; + isLoading: boolean; + onSubmit: (a: string[]) => void; + selectedConnector: string; +} + +const MyFlexGroup = styled(EuiFlexGroup)` + ${({ theme }) => css` + margin-top: ${theme.eui.euiSizeM}; + p { + font-size: ${theme.eui.euiSizeM}; + } + `} +`; + +export const EditConnector = React.memo( + ({ + connectors, + disabled = false, + isLoading, + onSubmit, + selectedConnector, + }: EditConnectorProps) => { + const { form } = useForm({ + defaultValue: { connectors }, + options: { stripEmptyFields: false }, + schema, + }); + const [connectorHasChanged, setConnectorHasChanged] = useState(false); + const onChangeConnector = useCallback( + (connectorId) => { + setConnectorHasChanged(selectedConnector !== connectorId); + }, + [selectedConnector] + ); + + const onCancelConnector = useCallback(() => { + form.setFieldValue('connector', selectedConnector); + setConnectorHasChanged(false); + }, [form, selectedConnector]); + + const onSubmitConnector = useCallback(async () => { + const { isValid, data: newData } = await form.submit(); + if (isValid && newData.connector) { + onSubmit(newData.connector); + setConnectorHasChanged(false); + } + }, [form, onSubmit]); + return ( + + + +

    {i18n.CONNECTORS}

    +
    + {isLoading && } +
    + + + + +
    + + + + + +
    +
    + {connectorHasChanged && ( + + + + + {i18n.SAVE} + + + + + {i18n.CANCEL} + + + + + )} +
    +
    +
    + ); + } +); + +EditConnector.displayName = 'EditConnector'; diff --git a/x-pack/plugins/siem/public/cases/components/edit_connector/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/edit_connector/schema.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/edit_connector/schema.tsx rename to x-pack/plugins/security_solution/public/cases/components/edit_connector/schema.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/filter_popover/index.tsx b/x-pack/plugins/security_solution/public/cases/components/filter_popover/index.tsx new file mode 100644 index 0000000000000..7b66bcffc89a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/filter_popover/index.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Dispatch, SetStateAction, useCallback, useState } from 'react'; +import { + EuiFilterButton, + EuiFilterSelectItem, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiPopover, + EuiText, +} from '@elastic/eui'; +import styled from 'styled-components'; + +interface FilterPopoverProps { + buttonLabel: string; + onSelectedOptionsChanged: Dispatch>; + options: string[]; + optionsEmptyLabel: string; + selectedOptions: string[]; +} + +const ScrollableDiv = styled.div` + max-height: 250px; + overflow: auto; +`; + +const toggleSelectedGroup = (group: string, selectedGroups: string[]): string[] => { + const selectedGroupIndex = selectedGroups.indexOf(group); + if (selectedGroupIndex >= 0) { + return [ + ...selectedGroups.slice(0, selectedGroupIndex), + ...selectedGroups.slice(selectedGroupIndex + 1), + ]; + } + return [...selectedGroups, group]; +}; + +/** + * Popover for selecting a field to filter on + * + * @param buttonLabel label on dropdwon button + * @param onSelectedOptionsChanged change listener to be notified when option selection changes + * @param options to display for filtering + * @param optionsEmptyLabel shows when options empty + * @param selectedOptions manage state of selectedOptions + */ +export const FilterPopoverComponent = ({ + buttonLabel, + onSelectedOptionsChanged, + options, + optionsEmptyLabel, + selectedOptions, +}: FilterPopoverProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const setIsPopoverOpenCb = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); + const toggleSelectedGroupCb = useCallback( + (option) => onSelectedOptionsChanged(toggleSelectedGroup(option, selectedOptions)), + [selectedOptions, onSelectedOptionsChanged] + ); + + return ( + 0} + numActiveFilters={selectedOptions.length} + aria-label={buttonLabel} + > + {buttonLabel} + + } + isOpen={isPopoverOpen} + closePopover={setIsPopoverOpenCb} + panelPaddingSize="none" + > + + {options.map((option, index) => ( + + {option} + + ))} + + {options.length === 0 && ( + + + + {optionsEmptyLabel} + + + + )} + + ); +}; + +FilterPopoverComponent.displayName = 'FilterPopoverComponent'; + +export const FilterPopover = React.memo(FilterPopoverComponent); + +FilterPopover.displayName = 'FilterPopover'; diff --git a/x-pack/plugins/siem/public/cases/components/open_closed_stats/index.tsx b/x-pack/plugins/security_solution/public/cases/components/open_closed_stats/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/open_closed_stats/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/open_closed_stats/index.tsx diff --git a/x-pack/plugins/siem/public/cases/components/property_actions/constants.ts b/x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/components/property_actions/constants.ts rename to x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts diff --git a/x-pack/plugins/siem/public/cases/components/property_actions/index.tsx b/x-pack/plugins/security_solution/public/cases/components/property_actions/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/property_actions/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/property_actions/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts b/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts new file mode 100644 index 0000000000000..a00abcc7845b0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ACTIONS_ARIA = i18n.translate( + 'xpack.securitySolution.case.caseView.editActionsLinkAria', + { + defaultMessage: 'click to see all actions', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.test.tsx new file mode 100644 index 0000000000000..950dd6f377945 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.test.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; + +import { TagList } from '.'; +import { getFormMock } from '../__mock__/form'; +import { TestProviders } from '../../../common/mock'; +import { wait } from '../../../common/lib/helpers'; +import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { useGetTags } from '../../containers/use_get_tags'; + +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); +jest.mock('../../containers/use_get_tags'); +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider', + () => ({ + FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) => + children({ tags: ['rad', 'dude'] }), + }) +); +const onSubmit = jest.fn(); +const defaultProps = { + disabled: false, + isLoading: false, + onSubmit, + tags: [], +}; + +describe('TagList ', () => { + // Suppress warnings about "noSuggestions" prop + /* eslint-disable no-console */ + const originalError = console.error; + beforeAll(() => { + console.error = jest.fn(); + }); + afterAll(() => { + console.error = originalError; + }); + /* eslint-enable no-console */ + const sampleTags = ['coke', 'pepsi']; + const fetchTags = jest.fn(); + const formHookMock = getFormMock({ tags: sampleTags }); + beforeEach(() => { + jest.resetAllMocks(); + (useForm as jest.Mock).mockImplementation(() => ({ form: formHookMock })); + + (useGetTags as jest.Mock).mockImplementation(() => ({ + tags: sampleTags, + fetchTags, + })); + }); + it('Renders no tags, and then edit', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find(`[data-test-subj="no-tags"]`).last().exists()).toBeTruthy(); + wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click'); + expect(wrapper.find(`[data-test-subj="no-tags"]`).last().exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="edit-tags"]`).last().exists()).toBeTruthy(); + }); + it('Edit tag on submit', async () => { + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click'); + await act(async () => { + wrapper.find(`[data-test-subj="edit-tags-submit"]`).last().simulate('click'); + await wait(); + expect(onSubmit).toBeCalledWith(sampleTags); + }); + }); + it('Tag options render with new tags added', () => { + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click'); + expect( + wrapper.find(`[data-test-subj="caseTags"] [data-test-subj="input"]`).first().prop('options') + ).toEqual([{ label: 'coke' }, { label: 'pepsi' }, { label: 'rad' }, { label: 'dude' }]); + }); + it('Cancels on cancel', async () => { + const props = { + ...defaultProps, + tags: ['pepsi'], + }; + const wrapper = mount( + + + + ); + expect(wrapper.find(`[data-test-subj="case-tag"]`).last().exists()).toBeTruthy(); + wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click'); + await act(async () => { + expect(wrapper.find(`[data-test-subj="case-tag"]`).last().exists()).toBeFalsy(); + wrapper.find(`[data-test-subj="edit-tags-cancel"]`).last().simulate('click'); + await wait(); + wrapper.update(); + expect(wrapper.find(`[data-test-subj="case-tag"]`).last().exists()).toBeTruthy(); + }); + }); + it('Renders disabled button', () => { + const props = { ...defaultProps, disabled: true }; + const wrapper = mount( + + + + ); + expect( + wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().prop('disabled') + ).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx new file mode 100644 index 0000000000000..5f8404ca2dcc4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useEffect, useState } from 'react'; +import { + EuiText, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiBadge, + EuiButton, + EuiButtonEmpty, + EuiButtonIcon, + EuiLoadingSpinner, +} from '@elastic/eui'; +import styled, { css } from 'styled-components'; +import { isEqual } from 'lodash/fp'; +import * as i18n from './translations'; +import { Form, FormDataProvider, useForm } from '../../../shared_imports'; +import { schema } from './schema'; +import { CommonUseField } from '../create'; +import { useGetTags } from '../../containers/use_get_tags'; + +interface TagListProps { + disabled?: boolean; + isLoading: boolean; + onSubmit: (a: string[]) => void; + tags: string[]; +} + +const MyFlexGroup = styled(EuiFlexGroup)` + ${({ theme }) => css` + margin-top: ${theme.eui.euiSizeM}; + p { + font-size: ${theme.eui.euiSizeM}; + } + `} +`; + +export const TagList = React.memo( + ({ disabled = false, isLoading, onSubmit, tags }: TagListProps) => { + const { form } = useForm({ + defaultValue: { tags }, + options: { stripEmptyFields: false }, + schema, + }); + const [isEditTags, setIsEditTags] = useState(false); + + const onSubmitTags = useCallback(async () => { + const { isValid, data: newData } = await form.submit(); + if (isValid && newData.tags) { + onSubmit(newData.tags); + setIsEditTags(false); + } + }, [form, onSubmit]); + const { tags: tagOptions } = useGetTags(); + const [options, setOptions] = useState( + tagOptions.map((label) => ({ + label, + })) + ); + + useEffect( + () => + setOptions( + tagOptions.map((label) => ({ + label, + })) + ), + [tagOptions] + ); + + return ( + + + +

    {i18n.TAGS}

    +
    + {isLoading && } + {!isLoading && ( + + + + )} +
    + + + {tags.length === 0 && !isEditTags &&

    {i18n.NO_TAGS}

    } + {tags.length > 0 && + !isEditTags && + tags.map((tag, key) => ( + + + {tag} + + + ))} + {isEditTags && ( + + +
    + + + {({ tags: anotherTags }) => { + const current: string[] = options.map((opt) => opt.label); + const newOptions = anotherTags.reduce((acc: string[], item: string) => { + if (!acc.includes(item)) { + return [...acc, item]; + } + return acc; + }, current); + if (!isEqual(current, newOptions)) { + setOptions( + newOptions.map((label: string) => ({ + label, + })) + ); + } + return null; + }} + + +
    + + + + + {i18n.SAVE} + + + + + {i18n.CANCEL} + + + + +
    + )} +
    +
    + ); + } +); + +TagList.displayName = 'TagList'; diff --git a/x-pack/plugins/siem/public/cases/components/tag_list/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/tag_list/schema.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/tag_list/schema.tsx rename to x-pack/plugins/security_solution/public/cases/components/tag_list/schema.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts b/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts new file mode 100644 index 0000000000000..3aeb6391267f6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export * from '../../translations'; + +export const EDIT_TAGS_ARIA = i18n.translate( + 'xpack.securitySolution.case.caseView.editTagsLinkAria', + { + defaultMessage: 'click to edit tags', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx new file mode 100644 index 0000000000000..919231d2f6034 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import * as i18n from './translations'; +import { ActionLicense } from '../../containers/types'; + +export const getLicenseError = () => ({ + title: i18n.PUSH_DISABLE_BY_LICENSE_TITLE, + description: ( + + {i18n.LINK_CLOUD_DEPLOYMENT} + + ), + }} + /> + ), +}); + +export const getKibanaConfigError = () => ({ + title: i18n.PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE, + description: ( + + {'coming soon...'} + + ), + }} + /> + ), +}); + +export const getActionLicenseError = ( + actionLicense: ActionLicense | null +): Array<{ title: string; description: JSX.Element }> => { + let errors: Array<{ title: string; description: JSX.Element }> = []; + if (actionLicense != null && !actionLicense.enabledInLicense) { + errors = [...errors, getLicenseError()]; + } + if (actionLicense != null && !actionLicense.enabledInConfig) { + errors = [...errors, getKibanaConfigError()]; + } + return errors; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx new file mode 100644 index 0000000000000..4391db1a0a0a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx @@ -0,0 +1,224 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable react/display-name */ +import React from 'react'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { usePushToService, ReturnUsePushToService, UsePushToService } from '.'; +import { TestProviders } from '../../../common/mock'; +import { usePostPushToService } from '../../containers/use_post_push_to_service'; +import { basicPush, actionLicenses } from '../../containers/mock'; +import * as i18n from './translations'; +import { useGetActionLicense } from '../../containers/use_get_action_license'; +import { getKibanaConfigError, getLicenseError } from './helpers'; +import { connectorsMock } from '../../containers/configure/mock'; +jest.mock('../../containers/use_get_action_license'); +jest.mock('../../containers/use_post_push_to_service'); +jest.mock('../../containers/configure/api'); + +describe('usePushToService', () => { + const caseId = '12345'; + const updateCase = jest.fn(); + const postPushToService = jest.fn(); + const mockPostPush = { + isLoading: false, + postPushToService, + }; + const mockConnector = connectorsMock[0]; + const actionLicense = actionLicenses[0]; + const caseServices = { + '123': { + ...basicPush, + firstPushIndex: 0, + lastPushIndex: 0, + commentsToUpdate: [], + hasDataToPush: true, + }, + }; + const defaultArgs = { + caseConnectorId: mockConnector.id, + caseConnectorName: mockConnector.name, + caseId, + caseServices, + caseStatus: 'open', + connectors: connectorsMock, + updateCase, + userCanCrud: true, + isValidConnector: true, + }; + + beforeEach(() => { + jest.resetAllMocks(); + (usePostPushToService as jest.Mock).mockImplementation(() => mockPostPush); + (useGetActionLicense as jest.Mock).mockImplementation(() => ({ + isLoading: false, + actionLicense, + })); + }); + + it('push case button posts the push with correct args', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + result.current.pushButton.props.children.props.onClick(); + expect(postPushToService).toBeCalledWith({ + caseId, + caseServices, + connectorId: mockConnector.id, + connectorName: mockConnector.name, + updateCase, + }); + expect(result.current.pushCallouts).toBeNull(); + }); + }); + + it('Displays message when user does not have premium license', async () => { + (useGetActionLicense as jest.Mock).mockImplementation(() => ({ + isLoading: false, + actionLicense: { + ...actionLicense, + enabledInLicense: false, + }, + })); + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(getLicenseError().title); + }); + }); + + it('Displays message when user does not have case enabled in config', async () => { + (useGetActionLicense as jest.Mock).mockImplementation(() => ({ + isLoading: false, + actionLicense: { + ...actionLicense, + enabledInConfig: false, + }, + })); + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(getKibanaConfigError().title); + }); + }); + + it('Displays message when user does not have any connector configured', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => + usePushToService({ + ...defaultArgs, + connectors: [], + caseConnectorId: 'none', + }), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BY_NO_CONFIG_TITLE); + }); + }); + + it('Displays message when user does have a connector but is configured to none', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => + usePushToService({ + ...defaultArgs, + caseConnectorId: 'none', + }), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE); + }); + }); + + it('Displays message when connector is deleted', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => + usePushToService({ + ...defaultArgs, + caseConnectorId: 'not-exist', + isValidConnector: false, + }), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE); + }); + }); + + it('Displays message when connector is deleted with empty connectors', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => + usePushToService({ + ...defaultArgs, + connectors: [], + caseConnectorId: 'not-exist', + isValidConnector: false, + }), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE); + }); + }); + + it('Displays message when case is closed', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => + usePushToService({ + ...defaultArgs, + caseStatus: 'closed', + }), + { + wrapper: ({ children }) => {children}, + } + ); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx new file mode 100644 index 0000000000000..2e9e3cce4306a --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiLink, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useCallback, useMemo } from 'react'; + +import { Case } from '../../containers/types'; +import { useGetActionLicense } from '../../containers/use_get_action_license'; +import { usePostPushToService } from '../../containers/use_post_push_to_service'; +import { getConfigureCasesUrl } from '../../../common/components/link_to'; +import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; +import { navTabs } from '../../../app/home/home_navigations'; +import { CaseCallOut } from '../callout'; +import { getLicenseError, getKibanaConfigError } from './helpers'; +import * as i18n from './translations'; +import { Connector } from '../../../../../case/common/api/cases'; +import { CaseServices } from '../../containers/use_get_case_user_actions'; + +export interface UsePushToService { + caseId: string; + caseStatus: string; + caseConnectorId: string; + caseConnectorName: string; + caseServices: CaseServices; + connectors: Connector[]; + updateCase: (newCase: Case) => void; + userCanCrud: boolean; + isValidConnector: boolean; +} + +export interface ReturnUsePushToService { + pushButton: JSX.Element; + pushCallouts: JSX.Element | null; +} + +export const usePushToService = ({ + caseConnectorId, + caseConnectorName, + caseId, + caseServices, + caseStatus, + connectors, + updateCase, + userCanCrud, + isValidConnector, +}: UsePushToService): ReturnUsePushToService => { + const urlSearch = useGetUrlSearch(navTabs.case); + + const { isLoading, postPushToService } = usePostPushToService(); + + const { isLoading: loadingLicense, actionLicense } = useGetActionLicense(); + + const handlePushToService = useCallback(() => { + if (caseConnectorId != null && caseConnectorId !== 'none') { + postPushToService({ + caseId, + caseServices, + connectorId: caseConnectorId, + connectorName: caseConnectorName, + updateCase, + }); + } + }, [caseId, caseServices, caseConnectorId, caseConnectorName, postPushToService, updateCase]); + + const errorsMsg = useMemo(() => { + let errors: Array<{ + title: string; + description: JSX.Element; + errorType?: 'primary' | 'success' | 'warning' | 'danger'; + }> = []; + if (actionLicense != null && !actionLicense.enabledInLicense) { + errors = [...errors, getLicenseError()]; + } + if (connectors.length === 0 && caseConnectorId === 'none' && !loadingLicense) { + errors = [ + ...errors, + { + title: i18n.PUSH_DISABLE_BY_NO_CONFIG_TITLE, + description: ( + + {i18n.LINK_CONNECTOR_CONFIGURE} + + ), + }} + /> + ), + }, + ]; + } else if (caseConnectorId === 'none' && !loadingLicense) { + errors = [ + ...errors, + { + title: i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE, + description: ( + + ), + }, + ]; + } else if (!isValidConnector && !loadingLicense) { + errors = [ + ...errors, + { + title: i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE, + description: ( + + ), + errorType: 'danger', + }, + ]; + } + if (caseStatus === 'closed') { + errors = [ + ...errors, + { + title: i18n.PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE, + description: ( + + ), + }, + ]; + } + if (actionLicense != null && !actionLicense.enabledInConfig) { + errors = [...errors, getKibanaConfigError()]; + } + return errors; + }, [actionLicense, caseStatus, connectors.length, caseConnectorId, loadingLicense, urlSearch]); + + const pushToServiceButton = useMemo(() => { + return ( + 0 || !userCanCrud || !isValidConnector + } + isLoading={isLoading} + > + {caseServices[caseConnectorId] + ? i18n.UPDATE_THIRD(caseConnectorName) + : i18n.PUSH_THIRD(caseConnectorName)} + + ); + }, [ + caseConnectorId, + caseConnectorName, + connectors, + errorsMsg, + handlePushToService, + isLoading, + loadingLicense, + userCanCrud, + isValidConnector, + ]); + + const objToReturn = useMemo(() => { + return { + pushButton: + errorsMsg.length > 0 ? ( + {errorsMsg[0].description}

    } + > + {pushToServiceButton} +
    + ) : ( + <>{pushToServiceButton} + ), + pushCallouts: + errorsMsg.length > 0 ? ( + + ) : null, + }; + }, [errorsMsg, pushToServiceButton]); + + return objToReturn; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts new file mode 100644 index 0000000000000..f4539b8019d43 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.errorsPushServiceCallOutTitle', + { + defaultMessage: 'To send cases to external systems, you need to:', + } +); +export const PUSH_THIRD = (thirdParty: string) => { + if (thirdParty === 'none') { + return i18n.translate('xpack.securitySolution.case.caseView.pushThirdPartyIncident', { + defaultMessage: 'Push as external incident', + }); + } + + return i18n.translate('xpack.securitySolution.case.caseView.pushNamedIncident', { + values: { thirdParty }, + defaultMessage: 'Push as { thirdParty } incident', + }); +}; + +export const UPDATE_THIRD = (thirdParty: string) => { + if (thirdParty === 'none') { + return i18n.translate('xpack.securitySolution.case.caseView.updateThirdPartyIncident', { + defaultMessage: 'Update external incident', + }); + } + + return i18n.translate('xpack.securitySolution.case.caseView.updateNamedIncident', { + values: { thirdParty }, + defaultMessage: 'Update { thirdParty } incident', + }); +}; + +export const PUSH_DISABLE_BY_NO_CONFIG_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.pushToServiceDisableByNoConfigTitle', + { + defaultMessage: 'Configure external connector', + } +); + +export const PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.pushToServiceDisableByNoCaseConfigTitle', + { + defaultMessage: 'Select external connector', + } +); + +export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.pushToServiceDisableBecauseCaseClosedTitle', + { + defaultMessage: 'Reopen the case', + } +); + +export const PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.pushToServiceDisableByConfigTitle', + { + defaultMessage: 'Enable external service in Kibana configuration file', + } +); + +export const PUSH_DISABLE_BY_LICENSE_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.pushToServiceDisableByLicenseTitle', + { + defaultMessage: 'Upgrade to Elastic Platinum', + } +); + +export const LINK_CLOUD_DEPLOYMENT = i18n.translate( + 'xpack.securitySolution.case.caseView.cloudDeploymentLink', + { + defaultMessage: 'cloud deployment', + } +); + +export const LINK_CONNECTOR_CONFIGURE = i18n.translate( + 'xpack.securitySolution.case.caseView.connectorConfigureLink', + { + defaultMessage: 'connector', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx new file mode 100644 index 0000000000000..b5be84db59920 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { basicPush, getUserAction } from '../../containers/mock'; +import { getLabelTitle } from './helpers'; +import * as i18n from '../case_view/translations'; +import { mount } from 'enzyme'; +import { connectorsMock } from '../../containers/configure/mock'; + +describe('User action tree helpers', () => { + const connectors = connectorsMock; + it('label title generated for update tags', () => { + const action = getUserAction(['title'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'tags', + firstPush: false, + }); + + const wrapper = mount(<>{result}); + expect(wrapper.find(`[data-test-subj="ua-tags-label"]`).first().text()).toEqual( + ` ${i18n.TAGS.toLowerCase()}` + ); + + expect(wrapper.find(`[data-test-subj="ua-tag"]`).first().text()).toEqual(action.newValue); + }); + it('label title generated for update title', () => { + const action = getUserAction(['title'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'title', + firstPush: false, + }); + + expect(result).toEqual( + `${i18n.CHANGED_FIELD.toLowerCase()} ${i18n.CASE_NAME.toLowerCase()} ${i18n.TO} "${ + action.newValue + }"` + ); + }); + it('label title generated for update description', () => { + const action = getUserAction(['description'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'description', + firstPush: false, + }); + + expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`); + }); + it('label title generated for update status to open', () => { + const action = { ...getUserAction(['status'], 'update'), newValue: 'open' }; + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'status', + firstPush: false, + }); + + expect(result).toEqual(`${i18n.REOPENED_CASE.toLowerCase()} ${i18n.CASE}`); + }); + it('label title generated for update status to closed', () => { + const action = { ...getUserAction(['status'], 'update'), newValue: 'closed' }; + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'status', + firstPush: false, + }); + + expect(result).toEqual(`${i18n.CLOSED_CASE.toLowerCase()} ${i18n.CASE}`); + }); + it('label title generated for update comment', () => { + const action = getUserAction(['comment'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'comment', + firstPush: false, + }); + + expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`); + }); + it('label title generated for pushed incident', () => { + const action = getUserAction(['pushed'], 'push-to-service'); + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'pushed', + firstPush: true, + }); + + const wrapper = mount(<>{result}); + expect(wrapper.find(`[data-test-subj="pushed-label"]`).first().text()).toEqual( + `${i18n.PUSHED_NEW_INCIDENT} ${basicPush.connectorName}` + ); + expect(wrapper.find(`[data-test-subj="pushed-value"]`).first().prop('href')).toEqual( + JSON.parse(action.newValue).external_url + ); + }); + it('label title generated for needs update incident', () => { + const action = getUserAction(['pushed'], 'push-to-service'); + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'pushed', + firstPush: false, + }); + + const wrapper = mount(<>{result}); + expect(wrapper.find(`[data-test-subj="pushed-label"]`).first().text()).toEqual( + `${i18n.UPDATE_INCIDENT} ${basicPush.connectorName}` + ); + expect(wrapper.find(`[data-test-subj="pushed-value"]`).first().prop('href')).toEqual( + JSON.parse(action.newValue).external_url + ); + }); + it('label title generated for update connector', () => { + const action = getUserAction(['connector_id'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + connectors, + field: 'tags', + firstPush: false, + }); + + const wrapper = mount(<>{result}); + expect(wrapper.find(`[data-test-subj="ua-tags-label"]`).first().text()).toEqual( + ` ${i18n.TAGS.toLowerCase()}` + ); + + expect(wrapper.find(`[data-test-subj="ua-tag"]`).first().text()).toEqual(action.newValue); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx new file mode 100644 index 0000000000000..a6286693423c8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiLink } from '@elastic/eui'; +import React from 'react'; + +import { CaseFullExternalService, Connector } from '../../../../../case/common/api'; +import { CaseUserActions } from '../../containers/types'; +import * as i18n from '../case_view/translations'; + +interface LabelTitle { + action: CaseUserActions; + connectors: Connector[]; + field: string; + firstPush: boolean; +} + +export const getLabelTitle = ({ action, connectors, field, firstPush }: LabelTitle) => { + if (field === 'tags') { + return getTagsLabelTitle(action); + } else if (field === 'title' && action.action === 'update') { + return `${i18n.CHANGED_FIELD.toLowerCase()} ${i18n.CASE_NAME.toLowerCase()} ${i18n.TO} "${ + action.newValue + }"`; + } else if (field === 'connector_id' && action.action === 'update') { + const newConnector = connectors.find((c) => c.id === action.newValue); + return action.newValue != null && action.newValue !== 'none' && newConnector != null + ? i18n.SELECTED_THIRD_PARTY(newConnector.name) + : i18n.REMOVED_THIRD_PARTY; + } else if (field === 'description' && action.action === 'update') { + return `${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`; + } else if (field === 'status' && action.action === 'update') { + return `${ + action.newValue === 'open' ? i18n.REOPENED_CASE.toLowerCase() : i18n.CLOSED_CASE.toLowerCase() + } ${i18n.CASE}`; + } else if (field === 'comment' && action.action === 'update') { + return `${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`; + } else if (field === 'pushed' && action.action === 'push-to-service' && action.newValue != null) { + return getPushedServiceLabelTitle(action, firstPush); + } + return ''; +}; + +const getTagsLabelTitle = (action: CaseUserActions) => ( + + + {action.action === 'add' && i18n.ADDED_FIELD} + {action.action === 'delete' && i18n.REMOVED_FIELD} {i18n.TAGS.toLowerCase()} + + {action.newValue != null && + action.newValue.split(',').map((tag) => ( + + + {tag} + + + ))} + +); + +const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: boolean) => { + const pushedVal = JSON.parse(action.newValue ?? '') as CaseFullExternalService; + return ( + + + {`${firstPush ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT} ${ + pushedVal?.connector_name + }`} + + + + {pushedVal?.external_title} + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx new file mode 100644 index 0000000000000..285584cf2233c --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx @@ -0,0 +1,328 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; +import { getFormMock, useFormMock } from '../__mock__/form'; +import { useUpdateComment } from '../../containers/use_update_comment'; +import { basicCase, basicPush, getUserAction } from '../../containers/mock'; +import { UserActionTree } from '.'; +import { TestProviders } from '../../../common/mock'; +import { wait } from '../../../common/lib/helpers'; +import { act } from 'react-dom/test-utils'; + +const fetchUserActions = jest.fn(); +const onUpdateField = jest.fn(); +const updateCase = jest.fn(); +const defaultProps = { + caseServices: {}, + caseUserActions: [], + connectors: [], + data: basicCase, + fetchUserActions, + isLoadingDescription: false, + isLoadingUserActions: false, + onUpdateField, + updateCase, + userCanCrud: true, +}; +const useUpdateCommentMock = useUpdateComment as jest.Mock; +jest.mock('../../containers/use_update_comment'); + +const patchComment = jest.fn(); +describe('UserActionTree ', () => { + const sampleData = { + content: 'what a great comment update', + }; + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + useUpdateCommentMock.mockImplementation(() => ({ + isLoadingIds: [], + patchComment, + })); + const formHookMock = getFormMock(sampleData); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); + }); + + it('Loading spinner when user actions loading and displays fullName/username', () => { + const wrapper = mount( + + + + + + ); + expect(wrapper.find(`[data-test-subj="user-actions-loading"]`).exists()).toBeTruthy(); + + expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().prop('name')).toEqual( + defaultProps.data.createdBy.fullName + ); + expect(wrapper.find(`[data-test-subj="user-action-title"] strong`).first().text()).toEqual( + defaultProps.data.createdBy.username + ); + }); + it('Renders service now update line with top and bottom when push is required', () => { + const ourActions = [ + getUserAction(['pushed'], 'push-to-service'), + getUserAction(['comment'], 'update'), + ]; + const props = { + ...defaultProps, + caseServices: { + '123': { + ...basicPush, + firstPushIndex: 0, + lastPushIndex: 0, + commentsToUpdate: [`${ourActions[ourActions.length - 1].commentId}`], + hasDataToPush: true, + }, + }, + caseUserActions: ourActions, + }; + const wrapper = mount( + + + + + + ); + expect(wrapper.find(`[data-test-subj="show-top-footer"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="show-bottom-footer"]`).exists()).toBeTruthy(); + }); + it('Renders service now update line with top only when push is up to date', () => { + const ourActions = [getUserAction(['pushed'], 'push-to-service')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + caseServices: { + '123': { + ...basicPush, + firstPushIndex: 0, + lastPushIndex: 0, + commentsToUpdate: [], + hasDataToPush: false, + }, + }, + }; + const wrapper = mount( + + + + + + ); + expect(wrapper.find(`[data-test-subj="show-top-footer"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="show-bottom-footer"]`).exists()).toBeFalsy(); + }); + + it('Outlines comment when update move to link is clicked', () => { + const ourActions = [getUserAction(['comment'], 'create'), getUserAction(['comment'], 'update')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + + + + + + ); + expect( + wrapper.find(`[data-test-subj="comment-create-action"]`).first().prop('idToOutline') + ).toEqual(''); + wrapper + .find(`[data-test-subj="comment-update-action"] [data-test-subj="move-to-link"]`) + .first() + .simulate('click'); + expect( + wrapper.find(`[data-test-subj="comment-create-action"]`).first().prop('idToOutline') + ).toEqual(ourActions[0].commentId); + }); + + it('Switches to markdown when edit is clicked and back to panel when canceled', () => { + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + + + + + + ); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(true); + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-cancel-markdown"]` + ) + .first() + .simulate('click'); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + }); + + it('calls update comment when comment markdown is saved', async () => { + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + + + + + + ); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-save-markdown"]` + ) + .first() + .simulate('click'); + await act(async () => { + await wait(); + wrapper.update(); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + expect(patchComment).toBeCalledWith({ + commentUpdate: sampleData.content, + caseId: props.data.id, + commentId: props.data.comments[0].id, + fetchUserActions, + updateCase, + version: props.data.comments[0].version, + }); + }); + }); + + it('calls update description when description markdown is saved', async () => { + const props = defaultProps; + const wrapper = mount( + + + + + + ); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + wrapper + .find( + `[data-test-subj="user-action-description"] [data-test-subj="user-action-save-markdown"]` + ) + .first() + .simulate('click'); + await act(async () => { + await wait(); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + expect(onUpdateField).toBeCalledWith('description', sampleData.content); + }); + }); + + it('quotes', async () => { + const commentData = { + comment: '', + }; + const formHookMock = getFormMock(commentData); + const setFieldValue = jest.fn(); + useFormMock.mockImplementation(() => ({ form: { ...formHookMock, setFieldValue } })); + const props = defaultProps; + const wrapper = mount( + + + + + + ); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-quote"]`) + .first() + .simulate('click'); + expect(setFieldValue).toBeCalledWith('comment', `> ${props.data.description} \n`); + }); + it('Outlines comment when url param is provided', () => { + const commentId = 'neat-comment-id'; + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + jest.spyOn(routeData, 'useParams').mockReturnValue({ commentId }); + const wrapper = mount( + + + + + + ); + expect( + wrapper.find(`[data-test-subj="comment-create-action"]`).first().prop('idToOutline') + ).toEqual(commentId); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx new file mode 100644 index 0000000000000..da49014eb0204 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx @@ -0,0 +1,303 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import styled from 'styled-components'; + +import * as i18n from '../case_view/translations'; + +import { Case, CaseUserActions } from '../../containers/types'; +import { useUpdateComment } from '../../containers/use_update_comment'; +import { useCurrentUser } from '../../../common/lib/kibana'; +import { AddComment } from '../add_comment'; +import { getLabelTitle } from './helpers'; +import { UserActionItem } from './user_action_item'; +import { UserActionMarkdown } from './user_action_markdown'; +import { Connector } from '../../../../../case/common/api/cases'; +import { CaseServices } from '../../containers/use_get_case_user_actions'; +import { parseString } from '../../containers/utils'; + +export interface UserActionTreeProps { + caseServices: CaseServices; + caseUserActions: CaseUserActions[]; + connectors: Connector[]; + data: Case; + fetchUserActions: () => void; + isLoadingDescription: boolean; + isLoadingUserActions: boolean; + onUpdateField: (updateKey: keyof Case, updateValue: string | string[]) => void; + updateCase: (newCase: Case) => void; + userCanCrud: boolean; +} + +const MyEuiFlexGroup = styled(EuiFlexGroup)` + margin-bottom: 8px; +`; + +const DESCRIPTION_ID = 'description'; +const NEW_ID = 'newComment'; + +export const UserActionTree = React.memo( + ({ + data: caseData, + caseServices, + caseUserActions, + connectors, + fetchUserActions, + isLoadingDescription, + isLoadingUserActions, + onUpdateField, + updateCase, + userCanCrud, + }: UserActionTreeProps) => { + const { commentId } = useParams(); + const handlerTimeoutId = useRef(0); + const [initLoading, setInitLoading] = useState(true); + const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); + const { isLoadingIds, patchComment } = useUpdateComment(); + const currentUser = useCurrentUser(); + const [manageMarkdownEditIds, setManangeMardownEditIds] = useState([]); + const [insertQuote, setInsertQuote] = useState(null); + const handleManageMarkdownEditId = useCallback( + (id: string) => { + if (!manageMarkdownEditIds.includes(id)) { + setManangeMardownEditIds([...manageMarkdownEditIds, id]); + } else { + setManangeMardownEditIds(manageMarkdownEditIds.filter((myId) => id !== myId)); + } + }, + [manageMarkdownEditIds] + ); + + const handleSaveComment = useCallback( + ({ id, version }: { id: string; version: string }, content: string) => { + patchComment({ + caseId: caseData.id, + commentId: id, + commentUpdate: content, + fetchUserActions, + version, + updateCase, + }); + }, + [caseData, handleManageMarkdownEditId, patchComment, updateCase] + ); + + const handleOutlineComment = useCallback( + (id: string) => { + const moveToTarget = document.getElementById(`${id}-permLink`); + if (moveToTarget != null) { + const yOffset = -60; + const y = moveToTarget.getBoundingClientRect().top + window.pageYOffset + yOffset; + window.scrollTo({ + top: y, + behavior: 'smooth', + }); + if (id === 'add-comment') { + moveToTarget.getElementsByTagName('textarea')[0].focus(); + } + } + window.clearTimeout(handlerTimeoutId.current); + setSelectedOutlineCommentId(id); + handlerTimeoutId.current = window.setTimeout(() => { + setSelectedOutlineCommentId(''); + window.clearTimeout(handlerTimeoutId.current); + }, 2400); + }, + [handlerTimeoutId.current] + ); + + const handleManageQuote = useCallback( + (quote: string) => { + const addCarrots = quote.replace(new RegExp('\r?\n', 'g'), ' \n> '); + setInsertQuote(`> ${addCarrots} \n`); + handleOutlineComment('add-comment'); + }, + [handleOutlineComment] + ); + + const handleUpdate = useCallback( + (newCase: Case) => { + updateCase(newCase); + fetchUserActions(); + }, + [fetchUserActions, updateCase] + ); + + const MarkdownDescription = useMemo( + () => ( + { + onUpdateField(DESCRIPTION_ID, content); + }} + onChangeEditable={handleManageMarkdownEditId} + /> + ), + [caseData.description, handleManageMarkdownEditId, manageMarkdownEditIds, onUpdateField] + ); + + const MarkdownNewComment = useMemo( + () => ( + + ), + [caseData.id, handleUpdate, insertQuote, userCanCrud] + ); + + useEffect(() => { + if (initLoading && !isLoadingUserActions && isLoadingIds.length === 0) { + setInitLoading(false); + if (commentId != null) { + handleOutlineComment(commentId); + } + } + }, [commentId, initLoading, isLoadingUserActions, isLoadingIds]); + return ( + <> + {i18n.ADDED_DESCRIPTION}} + fullName={caseData.createdBy.fullName ?? caseData.createdBy.username ?? ''} + markdown={MarkdownDescription} + onEdit={handleManageMarkdownEditId.bind(null, DESCRIPTION_ID)} + onQuote={handleManageQuote.bind(null, caseData.description)} + username={caseData.createdBy.username ?? i18n.UNKNOWN} + /> + + {caseUserActions.map((action, index) => { + if (action.commentId != null && action.action === 'create') { + const comment = caseData.comments.find((c) => c.id === action.commentId); + if (comment != null) { + return ( + {i18n.ADDED_COMMENT}} + fullName={comment.createdBy.fullName ?? comment.createdBy.username ?? ''} + markdown={ + + } + onEdit={handleManageMarkdownEditId.bind(null, comment.id)} + onQuote={handleManageQuote.bind(null, comment.comment)} + outlineComment={handleOutlineComment} + username={comment.createdBy.username ?? ''} + updatedAt={comment.updatedAt} + /> + ); + } + } + if (action.actionField.length === 1) { + const myField = action.actionField[0]; + const parsedValue = parseString(`${action.newValue}`); + const { firstPush, parsedConnectorId, parsedConnectorName } = + parsedValue != null + ? { + firstPush: caseServices[parsedValue.connector_id].firstPushIndex === index, + parsedConnectorId: parsedValue.connector_id, + parsedConnectorName: parsedValue.connector_name, + } + : { + firstPush: false, + parsedConnectorId: 'none', + parsedConnectorName: 'none', + }; + const labelTitle: string | JSX.Element = getLabelTitle({ + action, + field: myField, + firstPush, + connectors, + }); + + return ( + {labelTitle}} + linkId={ + action.action === 'update' && action.commentId != null ? action.commentId : null + } + fullName={action.actionBy.fullName ?? action.actionBy.username ?? ''} + outlineComment={handleOutlineComment} + showTopFooter={ + action.action === 'push-to-service' && + index === caseServices[parsedConnectorId].lastPushIndex + } + showBottomFooter={ + action.action === 'push-to-service' && + index === caseServices[parsedConnectorId].lastPushIndex && + caseServices[parsedConnectorId].hasDataToPush + } + username={action.actionBy.username ?? ''} + /> + ); + } + return null; + })} + {(isLoadingUserActions || isLoadingIds.includes(NEW_ID)) && ( + + + + + + )} + + + ); + } +); + +UserActionTree.displayName = 'UserActionTree'; diff --git a/x-pack/plugins/siem/public/cases/components/user_action_tree/schema.ts b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/schema.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/components/user_action_tree/schema.ts rename to x-pack/plugins/security_solution/public/cases/components/user_action_tree/schema.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts new file mode 100644 index 0000000000000..73c94477b4e73 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export * from '../case_view/translations'; + +export const ALREADY_PUSHED_TO_SERVICE = (externalService: string) => + i18n.translate('xpack.securitySolution.case.caseView.alreadyPushedToExternalService', { + values: { externalService }, + defaultMessage: 'Already pushed to { externalService } incident', + }); + +export const REQUIRED_UPDATE_TO_SERVICE = (externalService: string) => + i18n.translate('xpack.securitySolution.case.caseView.requiredUpdateToExternalService', { + values: { externalService }, + defaultMessage: 'Requires update to { externalService } incident', + }); + +export const COPY_REFERENCE_LINK = i18n.translate( + 'xpack.securitySolution.case.caseView.copyCommentLinkAria', + { + defaultMessage: 'Copy reference link', + } +); + +export const MOVE_TO_ORIGINAL_COMMENT = i18n.translate( + 'xpack.securitySolution.case.caseView.moveToCommentAria', + { + defaultMessage: 'Highlight the referenced comment', + } +); diff --git a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_avatar.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_avatar.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_avatar.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_avatar.tsx diff --git a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_item.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_item.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_item.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_item.tsx diff --git a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_markdown.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_markdown.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx index 27438207bed97..ae9f1ec7469e4 100644 --- a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_markdown.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx @@ -18,7 +18,7 @@ const onSaveContent = jest.fn(); const timelineId = '1e10f150-949b-11ea-b63c-2bc51864784c'; const defaultProps = { - content: `A link to a timeline [timeline](http://localhost:5601/app/siem#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`, + content: `A link to a timeline [timeline](http://localhost:5601/app/security#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`, id: 'markdown-id', isEditable: false, onChangeEditable, @@ -40,10 +40,7 @@ describe('UserActionMarkdown ', () => { ); - wrapper - .find(`[data-test-subj="markdown-timeline-link"]`) - .first() - .simulate('click'); + wrapper.find(`[data-test-subj="markdown-timeline-link"]`).first().simulate('click'); expect(queryTimelineByIdSpy).toBeCalledWith({ apolloClient: mockUseApolloClient(), @@ -61,14 +58,8 @@ describe('UserActionMarkdown ', () => { ); - wrapper - .find(`[data-test-subj="preview-tab"]`) - .first() - .simulate('click'); - wrapper - .find(`[data-test-subj="markdown-timeline-link"]`) - .first() - .simulate('click'); + wrapper.find(`[data-test-subj="preview-tab"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="markdown-timeline-link"]`).first().simulate('click'); expect(queryTimelineByIdSpy).toBeCalledWith({ apolloClient: mockUseApolloClient(), timelineId, diff --git a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_markdown.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_markdown.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx diff --git a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_title.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.test.tsx similarity index 94% rename from x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_title.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.test.tsx index cf29fa061e419..0bb02ce69a544 100644 --- a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_title.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.test.tsx @@ -48,10 +48,7 @@ describe('UserActionTitle ', () => { ); - wrapper - .find(`[data-test-subj="copy-link"]`) - .first() - .simulate('click'); + wrapper.find(`[data-test-subj="copy-link"]`).first().simulate('click'); expect(copy).toBeCalledTimes(1); }); }); diff --git a/x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_title.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/user_action_tree/user_action_title.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.tsx diff --git a/x-pack/plugins/siem/public/cases/components/user_list/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_list/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/user_list/index.test.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_list/index.test.tsx diff --git a/x-pack/plugins/siem/public/cases/components/user_list/index.tsx b/x-pack/plugins/security_solution/public/cases/components/user_list/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/user_list/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/user_list/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts b/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts new file mode 100644 index 0000000000000..3439d58eb41f0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SEND_EMAIL_ARIA = (user: string) => + i18n.translate('xpack.securitySolution.case.caseView.sendEmalLinkAria', { + values: { user }, + defaultMessage: 'click to send an email to {user}', + }); diff --git a/x-pack/plugins/siem/public/cases/components/wrappers/index.tsx b/x-pack/plugins/security_solution/public/cases/components/wrappers/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/components/wrappers/index.tsx rename to x-pack/plugins/security_solution/public/cases/components/wrappers/index.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/__mocks__/api.ts b/x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/__mocks__/api.ts rename to x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts diff --git a/x-pack/plugins/siem/public/cases/containers/api.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx similarity index 98% rename from x-pack/plugins/siem/public/cases/containers/api.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/api.test.tsx index b4f0c2198b458..321d330758e53 100644 --- a/x-pack/plugins/siem/public/cases/containers/api.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx @@ -88,7 +88,7 @@ describe('Case Configuration API', () => { }); test('check url, method, signal', async () => { await getActionLicense(abortCtrl.signal); - expect(fetchMock).toHaveBeenCalledWith(`/api/action/types`, { + expect(fetchMock).toHaveBeenCalledWith(`/api/actions/list_action_types`, { method: 'GET', signal: abortCtrl.signal, }); @@ -416,7 +416,7 @@ describe('Case Configuration API', () => { const connectorId = 'connectorId'; test('check url, method, signal', async () => { await pushToService(connectorId, casePushParams, abortCtrl.signal); - expect(fetchMock).toHaveBeenCalledWith(`/api/action/${connectorId}/_execute`, { + expect(fetchMock).toHaveBeenCalledWith(`/api/actions/action/${connectorId}/_execute`, { method: 'POST', body: JSON.stringify({ params: { subAction: 'pushToService', subActionParams: casePushParams }, diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts new file mode 100644 index 0000000000000..4f7ef290370cc --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -0,0 +1,268 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CaseResponse, + CasesResponse, + CasesFindResponse, + CasePatchRequest, + CasePostRequest, + CasesStatusResponse, + CommentRequest, + User, + CaseUserActionsResponse, + CaseExternalServiceRequest, + ServiceConnectorCaseParams, + ServiceConnectorCaseResponse, + ActionTypeExecutorResult, +} from '../../../../case/common/api'; + +import { + CASE_STATUS_URL, + CASES_URL, + CASE_TAGS_URL, + CASE_REPORTERS_URL, + ACTION_TYPES_URL, + ACTION_URL, +} from '../../../../case/common/constants'; + +import { + getCaseDetailsUrl, + getCaseUserActionUrl, + getCaseCommentsUrl, +} from '../../../../case/common/api/helpers'; + +import { KibanaServices } from '../../common/lib/kibana'; + +import { + ActionLicense, + AllCases, + BulkUpdateStatus, + Case, + CasesStatus, + FetchCasesProps, + SortFieldCase, + CaseUserActions, +} from './types'; + +import { + convertToCamelCase, + convertAllCasesToCamel, + convertArrayToCamelCase, + decodeCaseResponse, + decodeCasesResponse, + decodeCasesFindResponse, + decodeCasesStatusResponse, + decodeCaseUserActionsResponse, + decodeServiceConnectorCaseResponse, +} from './utils'; + +import * as i18n from './translations'; + +export const getCase = async ( + caseId: string, + includeComments: boolean = true, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch(getCaseDetailsUrl(caseId), { + method: 'GET', + query: { + includeComments, + }, + signal, + }); + return convertToCamelCase(decodeCaseResponse(response)); +}; + +export const getCasesStatus = async (signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(CASE_STATUS_URL, { + method: 'GET', + signal, + }); + return convertToCamelCase(decodeCasesStatusResponse(response)); +}; + +export const getTags = async (signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(CASE_TAGS_URL, { + method: 'GET', + signal, + }); + return response ?? []; +}; + +export const getReporters = async (signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(CASE_REPORTERS_URL, { + method: 'GET', + signal, + }); + return response ?? []; +}; + +export const getCaseUserActions = async ( + caseId: string, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + getCaseUserActionUrl(caseId), + { + method: 'GET', + signal, + } + ); + return convertArrayToCamelCase(decodeCaseUserActionsResponse(response)) as CaseUserActions[]; +}; + +export const getCases = async ({ + filterOptions = { + search: '', + reporters: [], + status: 'open', + tags: [], + }, + queryParams = { + page: 1, + perPage: 20, + sortField: SortFieldCase.createdAt, + sortOrder: 'desc', + }, + signal, +}: FetchCasesProps): Promise => { + const query = { + reporters: filterOptions.reporters.map((r) => r.username ?? '').filter((r) => r !== ''), + tags: filterOptions.tags, + ...(filterOptions.status !== '' ? { status: filterOptions.status } : {}), + ...(filterOptions.search.length > 0 ? { search: filterOptions.search } : {}), + ...queryParams, + }; + const response = await KibanaServices.get().http.fetch(`${CASES_URL}/_find`, { + method: 'GET', + query, + signal, + }); + return convertAllCasesToCamel(decodeCasesFindResponse(response)); +}; + +export const postCase = async (newCase: CasePostRequest, signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(CASES_URL, { + method: 'POST', + body: JSON.stringify(newCase), + signal, + }); + return convertToCamelCase(decodeCaseResponse(response)); +}; + +export const patchCase = async ( + caseId: string, + updatedCase: Pick, + version: string, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch(CASES_URL, { + method: 'PATCH', + body: JSON.stringify({ cases: [{ ...updatedCase, id: caseId, version }] }), + signal, + }); + return convertToCamelCase(decodeCasesResponse(response)); +}; + +export const patchCasesStatus = async ( + cases: BulkUpdateStatus[], + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch(CASES_URL, { + method: 'PATCH', + body: JSON.stringify({ cases }), + signal, + }); + return convertToCamelCase(decodeCasesResponse(response)); +}; + +export const postComment = async ( + newComment: CommentRequest, + caseId: string, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + `${CASES_URL}/${caseId}/comments`, + { + method: 'POST', + body: JSON.stringify(newComment), + signal, + } + ); + return convertToCamelCase(decodeCaseResponse(response)); +}; + +export const patchComment = async ( + caseId: string, + commentId: string, + commentUpdate: string, + version: string, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch(getCaseCommentsUrl(caseId), { + method: 'PATCH', + body: JSON.stringify({ comment: commentUpdate, id: commentId, version }), + signal, + }); + return convertToCamelCase(decodeCaseResponse(response)); +}; + +export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(CASES_URL, { + method: 'DELETE', + query: { ids: JSON.stringify(caseIds) }, + signal, + }); + return response; +}; + +export const pushCase = async ( + caseId: string, + push: CaseExternalServiceRequest, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + `${getCaseDetailsUrl(caseId)}/_push`, + { + method: 'POST', + body: JSON.stringify(push), + signal, + } + ); + return convertToCamelCase(decodeCaseResponse(response)); +}; + +export const pushToService = async ( + connectorId: string, + casePushParams: ServiceConnectorCaseParams, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + `${ACTION_URL}/action/${connectorId}/_execute`, + { + method: 'POST', + body: JSON.stringify({ + params: { subAction: 'pushToService', subActionParams: casePushParams }, + }), + signal, + } + ); + + if (response.status === 'error') { + throw new Error(response.serviceMessage ?? response.message ?? i18n.ERROR_PUSH_TO_SERVICE); + } + + return decodeServiceConnectorCaseResponse(response.data); +}; + +export const getActionLicense = async (signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(ACTION_TYPES_URL, { + method: 'GET', + signal, + }); + return response; +}; diff --git a/x-pack/plugins/siem/public/cases/containers/configure/__mocks__/api.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/__mocks__/api.ts rename to x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts diff --git a/x-pack/plugins/siem/public/cases/containers/configure/api.test.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/api.test.ts rename to x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts diff --git a/x-pack/plugins/siem/public/cases/containers/configure/api.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/api.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/api.ts rename to x-pack/plugins/security_solution/public/cases/containers/configure/api.ts diff --git a/x-pack/plugins/siem/public/cases/containers/configure/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/mock.ts rename to x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts new file mode 100644 index 0000000000000..ee21167021e19 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export * from '../translations'; + +export const SUCCESS_CONFIGURE = i18n.translate( + 'xpack.securitySolution.case.configure.successSaveToast', + { + defaultMessage: 'Saved external connection settings', + } +); diff --git a/x-pack/plugins/siem/public/cases/containers/configure/types.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/types.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/types.ts rename to x-pack/plugins/security_solution/public/cases/containers/configure/types.ts diff --git a/x-pack/plugins/siem/public/cases/containers/configure/use_configure.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/use_configure.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/configure/use_configure.tsx b/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/use_configure.tsx rename to x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/configure/use_connectors.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/use_connectors.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/configure/use_connectors.tsx b/x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/configure/use_connectors.tsx rename to x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/constants.ts b/x-pack/plugins/security_solution/public/cases/containers/constants.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/constants.ts rename to x-pack/plugins/security_solution/public/cases/containers/constants.ts diff --git a/x-pack/plugins/siem/public/cases/containers/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/mock.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/mock.ts rename to x-pack/plugins/security_solution/public/cases/containers/mock.ts diff --git a/x-pack/plugins/security_solution/public/cases/containers/translations.ts b/x-pack/plugins/security_solution/public/cases/containers/translations.ts new file mode 100644 index 0000000000000..96d2ec2f874db --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/containers/translations.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_TITLE = i18n.translate('xpack.securitySolution.containers.case.errorTitle', { + defaultMessage: 'Error fetching data', +}); + +export const ERROR_DELETING = i18n.translate( + 'xpack.securitySolution.containers.case.errorDeletingTitle', + { + defaultMessage: 'Error deleting data', + } +); + +export const UPDATED_CASE = (caseTitle: string) => + i18n.translate('xpack.securitySolution.containers.case.updatedCase', { + values: { caseTitle }, + defaultMessage: 'Updated "{caseTitle}"', + }); + +export const DELETED_CASES = (totalCases: number, caseTitle?: string) => + i18n.translate('xpack.securitySolution.containers.case.deletedCases', { + values: { caseTitle, totalCases }, + defaultMessage: 'Deleted {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const CLOSED_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.securitySolution.containers.case.closedCases', { + values: { caseTitle, totalCases }, + defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const REOPENED_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.securitySolution.containers.case.reopenedCases', { + values: { caseTitle, totalCases }, + defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) => + i18n.translate('xpack.securitySolution.containers.case.pushToExternalService', { + values: { serviceName }, + defaultMessage: 'Successfully sent to { serviceName }', + }); + +export const ERROR_PUSH_TO_SERVICE = i18n.translate( + 'xpack.securitySolution.case.configure.errorPushingToService', + { + defaultMessage: 'Error pushing to service', + } +); diff --git a/x-pack/plugins/siem/public/cases/containers/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/types.ts rename to x-pack/plugins/security_solution/public/cases/containers/types.ts diff --git a/x-pack/plugins/siem/public/cases/containers/use_bulk_update_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_bulk_update_case.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_bulk_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx similarity index 97% rename from x-pack/plugins/siem/public/cases/containers/use_bulk_update_case.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx index b9b64aa77493a..ef68f4f48b1a0 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_bulk_update_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx @@ -115,7 +115,7 @@ export const useUpdateCases = (): UseUpdateCases => { }, []); const updateBulkStatus = useCallback((cases: Case[], status: string) => { - const updateCasesStatus: BulkUpdateStatus[] = cases.map(theCase => ({ + const updateCasesStatus: BulkUpdateStatus[] = cases.map((theCase) => ({ status, id: theCase.id, version: theCase.version, diff --git a/x-pack/plugins/siem/public/cases/containers/use_delete_cases.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_delete_cases.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_delete_cases.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx similarity index 98% rename from x-pack/plugins/siem/public/cases/containers/use_delete_cases.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx index 31a73351de8f5..2e8c7dfae2313 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_delete_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx @@ -85,7 +85,7 @@ export const useDeleteCases = (): UseDeleteCase => { const deleteData = async () => { try { dispatch({ type: 'FETCH_INIT' }); - const caseIds = cases.map(theCase => theCase.id); + const caseIds = cases.map((theCase) => theCase.id); await deleteCases(caseIds, abortCtrl.signal); if (!cancel) { dispatch({ type: 'FETCH_SUCCESS', payload: true }); diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_action_license.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_action_license.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_action_license.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.tsx similarity index 96% rename from x-pack/plugins/siem/public/cases/containers/use_get_action_license.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.tsx index c09cc8dedd379..563e2a4a58c70 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_get_action_license.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.tsx @@ -40,7 +40,7 @@ export const useGetActionLicense = (): ActionLicenseState => { const response = await getActionLicense(abortCtrl.signal); if (!didCancel) { setActionLicensesState({ - actionLicense: response.find(l => l.id === '.servicenow') ?? null, + actionLicense: response.find((l) => l.id === '.servicenow') ?? null, isLoading: false, isError: false, }); diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_case.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_case.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_case.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_case_user_actions.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_case_user_actions.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_case_user_actions.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx similarity index 97% rename from x-pack/plugins/siem/public/cases/containers/use_get_case_user_actions.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx index 2848d56378cd2..050b2815dc511 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_get_case_user_actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx @@ -63,7 +63,7 @@ export const getPushedInfo = ( } => { const hasDataToPushForConnector = (connectorId: string) => { const userActionsForPushLessServiceUpdates = caseUserActions.filter( - mua => + (mua) => (mua.action !== 'push-to-service' && !(mua.action === 'update' && mua.actionField[0] === 'connector_id')) || (mua.action === 'push-to-service' && @@ -131,7 +131,7 @@ export const getPushedInfo = ( (bacc, currentComment) => currentComment.commentIndex > caseServices[key].lastPushIndex ? bacc.indexOf(currentComment.commentId) > -1 - ? [...bacc.filter(e => e !== currentComment.commentId), currentComment.commentId] + ? [...bacc.filter((e) => e !== currentComment.commentId), currentComment.commentId] : [...bacc, currentComment.commentId] : bacc, [] @@ -174,7 +174,7 @@ export const useGetCaseUserActions = ( // We are removing the first item because it will always be the creation of the case // and we do not want it to simplify our life const participants = !isEmpty(response) - ? uniqBy('actionBy.username', response).map(cau => cau.actionBy) + ? uniqBy('actionBy.username', response).map((cau) => cau.actionBy) : []; const caseUserActions = !isEmpty(response) ? response.slice(1) : []; diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_cases.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_cases.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_cases.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.tsx similarity index 95% rename from x-pack/plugins/siem/public/cases/containers/use_get_cases.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_cases.tsx index b0701c71b857e..45b571a8fe7e2 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_get_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.tsx @@ -45,25 +45,25 @@ const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesS return { ...state, isError: false, - loading: [...state.loading.filter(e => e !== action.payload), action.payload], + loading: [...state.loading.filter((e) => e !== action.payload), action.payload], }; case 'FETCH_UPDATE_CASE_SUCCESS': return { ...state, - loading: state.loading.filter(e => e !== 'caseUpdate'), + loading: state.loading.filter((e) => e !== 'caseUpdate'), }; case 'FETCH_CASES_SUCCESS': return { ...state, data: action.payload, isError: false, - loading: state.loading.filter(e => e !== 'cases'), + loading: state.loading.filter((e) => e !== 'cases'), }; case 'FETCH_FAILURE': return { ...state, isError: true, - loading: state.loading.filter(e => e !== action.payload), + loading: state.loading.filter((e) => e !== action.payload), }; case 'UPDATE_FILTER_OPTIONS': return { diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_cases_status.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_cases_status.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_cases_status.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_cases_status.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_reporters.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_reporters.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_reporters.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx similarity index 93% rename from x-pack/plugins/siem/public/cases/containers/use_get_reporters.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx index 5bfc8c84d1ecc..d723b8cc37c23 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_get_reporters.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx @@ -46,8 +46,10 @@ export const useGetReporters = (): UseGetReporters => { try { const response = await getReporters(abortCtrl.signal); const myReporters = response - .map(r => (r.full_name == null || isEmpty(r.full_name) ? r.username ?? '' : r.full_name)) - .filter(u => !isEmpty(u)); + .map((r) => + r.full_name == null || isEmpty(r.full_name) ? r.username ?? '' : r.full_name + ) + .filter((u) => !isEmpty(u)); if (!didCancel) { setReporterState({ reporters: myReporters, diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_tags.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_tags.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_tags.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_tags.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_get_tags.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_tags.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_get_tags.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_get_tags.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_post_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_post_case.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_post_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_post_case.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_post_comment.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_post_comment.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_post_comment.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_post_comment.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_post_push_to_service.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_post_push_to_service.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_post_push_to_service.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx similarity index 97% rename from x-pack/plugins/siem/public/cases/containers/use_post_push_to_service.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx index def324dcf442e..4d25ac7fbf0db 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_post_push_to_service.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx @@ -178,9 +178,10 @@ export const formatServiceRequestData = ( }, comments: comments .filter( - c => actualExternalService == null || actualExternalService.commentsToUpdate.includes(c.id) + (c) => + actualExternalService == null || actualExternalService.commentsToUpdate.includes(c.id) ) - .map(c => ({ + .map((c) => ({ commentId: c.id, comment: c.comment, createdAt: c.createdAt, diff --git a/x-pack/plugins/siem/public/cases/containers/use_update_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_update_case.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_update_case.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_update_comment.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/containers/use_update_comment.test.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx diff --git a/x-pack/plugins/siem/public/cases/containers/use_update_comment.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx similarity index 94% rename from x-pack/plugins/siem/public/cases/containers/use_update_comment.tsx rename to x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx index 66064faea27d7..821fd5523a751 100644 --- a/x-pack/plugins/siem/public/cases/containers/use_update_comment.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx @@ -37,13 +37,13 @@ const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpd case 'FETCH_SUCCESS': return { ...state, - isLoadingIds: state.isLoadingIds.filter(id => action.payload.commentId !== id), + isLoadingIds: state.isLoadingIds.filter((id) => action.payload.commentId !== id), isError: false, }; case 'FETCH_FAILURE': return { ...state, - isLoadingIds: state.isLoadingIds.filter(id => action.payload !== id), + isLoadingIds: state.isLoadingIds.filter((id) => action.payload !== id), isError: true, }; default: diff --git a/x-pack/plugins/security_solution/public/cases/containers/utils.ts b/x-pack/plugins/security_solution/public/cases/containers/utils.ts new file mode 100644 index 0000000000000..ef4e1ff05118b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/containers/utils.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { camelCase, isArray, isObject, set } from 'lodash'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { + CasesFindResponse, + CasesFindResponseRt, + CaseResponse, + CaseResponseRt, + CasesResponse, + CasesResponseRt, + CasesStatusResponseRt, + CasesStatusResponse, + throwErrors, + CasesConfigureResponse, + CaseConfigureResponseRt, + CaseUserActionsResponse, + CaseUserActionsResponseRt, + ServiceConnectorCaseResponseRt, + ServiceConnectorCaseResponse, +} from '../../../../case/common/api'; +import { ToasterError } from '../../common/components/toasters'; +import { AllCases, Case } from './types'; + +export const getTypedPayload = (a: unknown): T => a as T; + +export const parseString = (params: string) => { + try { + return JSON.parse(params); + } catch { + return null; + } +}; + +export const convertArrayToCamelCase = (arrayOfSnakes: unknown[]): unknown[] => + arrayOfSnakes.reduce((acc: unknown[], value) => { + if (isArray(value)) { + return [...acc, convertArrayToCamelCase(value)]; + } else if (isObject(value)) { + return [...acc, convertToCamelCase(value)]; + } else { + return [...acc, value]; + } + }, []); + +export const convertToCamelCase = (snakeCase: T): U => + Object.entries(snakeCase).reduce((acc, [key, value]) => { + if (isArray(value)) { + set(acc, camelCase(key), convertArrayToCamelCase(value)); + } else if (isObject(value)) { + set(acc, camelCase(key), convertToCamelCase(value)); + } else { + set(acc, camelCase(key), value); + } + return acc; + }, {} as U); + +export const convertAllCasesToCamel = (snakeCases: CasesFindResponse): AllCases => ({ + cases: snakeCases.cases.map((snakeCase) => convertToCamelCase(snakeCase)), + countClosedCases: snakeCases.count_closed_cases, + countOpenCases: snakeCases.count_open_cases, + page: snakeCases.page, + perPage: snakeCases.per_page, + total: snakeCases.total, +}); + +export const decodeCasesStatusResponse = (respCase?: CasesStatusResponse) => + pipe( + CasesStatusResponseRt.decode(respCase), + fold(throwErrors(createToasterPlainError), identity) + ); + +export const createToasterPlainError = (message: string) => new ToasterError([message]); + +export const decodeCaseResponse = (respCase?: CaseResponse) => + pipe(CaseResponseRt.decode(respCase), fold(throwErrors(createToasterPlainError), identity)); + +export const decodeCasesResponse = (respCase?: CasesResponse) => + pipe(CasesResponseRt.decode(respCase), fold(throwErrors(createToasterPlainError), identity)); + +export const decodeCasesFindResponse = (respCases?: CasesFindResponse) => + pipe(CasesFindResponseRt.decode(respCases), fold(throwErrors(createToasterPlainError), identity)); + +export const decodeCaseConfigureResponse = (respCase?: CasesConfigureResponse) => + pipe( + CaseConfigureResponseRt.decode(respCase), + fold(throwErrors(createToasterPlainError), identity) + ); + +export const decodeCaseUserActionsResponse = (respUserActions?: CaseUserActionsResponse) => + pipe( + CaseUserActionsResponseRt.decode(respUserActions), + fold(throwErrors(createToasterPlainError), identity) + ); + +export const decodeServiceConnectorCaseResponse = (respPushCase?: ServiceConnectorCaseResponse) => + pipe( + ServiceConnectorCaseResponseRt.decode(respPushCase), + fold(throwErrors(createToasterPlainError), identity) + ); diff --git a/x-pack/plugins/siem/public/cases/index.ts b/x-pack/plugins/security_solution/public/cases/index.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/index.ts rename to x-pack/plugins/security_solution/public/cases/index.ts diff --git a/x-pack/plugins/siem/public/cases/pages/case.tsx b/x-pack/plugins/security_solution/public/cases/pages/case.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/pages/case.tsx rename to x-pack/plugins/security_solution/public/cases/pages/case.tsx diff --git a/x-pack/plugins/siem/public/cases/pages/case_details.tsx b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/pages/case_details.tsx rename to x-pack/plugins/security_solution/public/cases/pages/case_details.tsx diff --git a/x-pack/plugins/siem/public/cases/pages/configure_cases.tsx b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/pages/configure_cases.tsx rename to x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx diff --git a/x-pack/plugins/siem/public/cases/pages/create_case.tsx b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/pages/create_case.tsx rename to x-pack/plugins/security_solution/public/cases/pages/create_case.tsx diff --git a/x-pack/plugins/siem/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/pages/index.tsx rename to x-pack/plugins/security_solution/public/cases/pages/index.tsx diff --git a/x-pack/plugins/siem/public/cases/pages/saved_object_no_permissions.tsx b/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/pages/saved_object_no_permissions.tsx rename to x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx diff --git a/x-pack/plugins/security_solution/public/cases/pages/translations.ts b/x-pack/plugins/security_solution/public/cases/pages/translations.ts new file mode 100644 index 0000000000000..5b595c5892ef2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/pages/translations.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SAVED_OBJECT_NO_PERMISSIONS_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseSavedObjectNoPermissionsTitle', + { + defaultMessage: 'Kibana feature privileges required', + } +); + +export const SAVED_OBJECT_NO_PERMISSIONS_MSG = i18n.translate( + 'xpack.securitySolution.case.caseSavedObjectNoPermissionsMessage', + { + defaultMessage: + 'To view cases, you must have privileges for the Saved Object Management feature in the Kibana space. For more information, contact your Kibana administrator.', + } +); + +export const BACK_TO_ALL = i18n.translate('xpack.securitySolution.case.caseView.backLabel', { + defaultMessage: 'Back to cases', +}); + +export const CANCEL = i18n.translate('xpack.securitySolution.case.caseView.cancel', { + defaultMessage: 'Cancel', +}); + +export const DELETE_CASE = i18n.translate( + 'xpack.securitySolution.case.confirmDeleteCase.deleteCase', + { + defaultMessage: 'Delete case', + } +); + +export const DELETE_CASES = i18n.translate( + 'xpack.securitySolution.case.confirmDeleteCase.deleteCases', + { + defaultMessage: 'Delete cases', + } +); + +export const NAME = i18n.translate('xpack.securitySolution.case.caseView.name', { + defaultMessage: 'Name', +}); + +export const OPENED_ON = i18n.translate('xpack.securitySolution.case.caseView.openedOn', { + defaultMessage: 'Opened on', +}); + +export const CLOSED_ON = i18n.translate('xpack.securitySolution.case.caseView.closedOn', { + defaultMessage: 'Closed on', +}); + +export const REPORTER = i18n.translate('xpack.securitySolution.case.caseView.reporterLabel', { + defaultMessage: 'Reporter', +}); + +export const PARTICIPANTS = i18n.translate( + 'xpack.securitySolution.case.caseView.particpantsLabel', + { + defaultMessage: 'Participants', + } +); + +export const CREATE_BC_TITLE = i18n.translate('xpack.securitySolution.case.caseView.breadcrumb', { + defaultMessage: 'Create', +}); + +export const CREATE_TITLE = i18n.translate('xpack.securitySolution.case.caseView.create', { + defaultMessage: 'Create new case', +}); + +export const DESCRIPTION = i18n.translate('xpack.securitySolution.case.caseView.description', { + defaultMessage: 'Description', +}); + +export const DESCRIPTION_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.createCase.descriptionFieldRequiredError', + { + defaultMessage: 'A description is required.', + } +); + +export const COMMENT_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.caseView.commentFieldRequiredError', + { + defaultMessage: 'A comment is required.', + } +); + +export const REQUIRED_FIELD = i18n.translate( + 'xpack.securitySolution.case.caseView.fieldRequiredError', + { + defaultMessage: 'Required field', + } +); + +export const EDIT = i18n.translate('xpack.securitySolution.case.caseView.edit', { + defaultMessage: 'Edit', +}); + +export const OPTIONAL = i18n.translate('xpack.securitySolution.case.caseView.optional', { + defaultMessage: 'Optional', +}); + +export const PAGE_TITLE = i18n.translate('xpack.securitySolution.case.pageTitle', { + defaultMessage: 'Cases', +}); + +export const CREATE_CASE = i18n.translate('xpack.securitySolution.case.caseView.createCase', { + defaultMessage: 'Create case', +}); + +export const CLOSED_CASE = i18n.translate('xpack.securitySolution.case.caseView.closedCase', { + defaultMessage: 'Closed case', +}); + +export const CLOSE_CASE = i18n.translate('xpack.securitySolution.case.caseView.closeCase', { + defaultMessage: 'Close case', +}); + +export const REOPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView.reopenCase', { + defaultMessage: 'Reopen case', +}); + +export const REOPENED_CASE = i18n.translate('xpack.securitySolution.case.caseView.reopenedCase', { + defaultMessage: 'Reopened case', +}); + +export const CASE_NAME = i18n.translate('xpack.securitySolution.case.caseView.caseName', { + defaultMessage: 'Case name', +}); + +export const TO = i18n.translate('xpack.securitySolution.case.caseView.to', { + defaultMessage: 'to', +}); + +export const TAGS = i18n.translate('xpack.securitySolution.case.caseView.tags', { + defaultMessage: 'Tags', +}); + +export const ACTIONS = i18n.translate('xpack.securitySolution.case.allCases.actions', { + defaultMessage: 'Actions', +}); + +export const NO_TAGS_AVAILABLE = i18n.translate( + 'xpack.securitySolution.case.allCases.noTagsAvailable', + { + defaultMessage: 'No tags available', + } +); + +export const NO_REPORTERS_AVAILABLE = i18n.translate( + 'xpack.securitySolution.case.caseView.noReportersAvailable', + { + defaultMessage: 'No reporters available.', + } +); + +export const COMMENTS = i18n.translate('xpack.securitySolution.case.allCases.comments', { + defaultMessage: 'Comments', +}); + +export const TAGS_HELP = i18n.translate( + 'xpack.securitySolution.case.createCase.fieldTagsHelpText', + { + defaultMessage: + 'Type one or more custom identifying tags for this case. Press enter after each tag to begin a new one.', + } +); + +export const NO_TAGS = i18n.translate('xpack.securitySolution.case.caseView.noTags', { + defaultMessage: 'No tags are currently assigned to this case.', +}); + +export const TITLE_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.createCase.titleFieldRequiredError', + { + defaultMessage: 'A title is required.', + } +); + +export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.case.configureCases.headerTitle', + { + defaultMessage: 'Configure cases', + } +); + +export const CONFIGURE_CASES_BUTTON = i18n.translate( + 'xpack.securitySolution.case.configureCasesButton', + { + defaultMessage: 'Edit external connection', + } +); + +export const ADD_COMMENT = i18n.translate( + 'xpack.securitySolution.case.caseView.comment.addComment', + { + defaultMessage: 'Add comment', + } +); + +export const ADD_COMMENT_HELP_TEXT = i18n.translate( + 'xpack.securitySolution.case.caseView.comment.addCommentHelpText', + { + defaultMessage: 'Add a new comment...', + } +); + +export const SAVE = i18n.translate('xpack.securitySolution.case.caseView.description.save', { + defaultMessage: 'Save', +}); + +export const GO_TO_DOCUMENTATION = i18n.translate( + 'xpack.securitySolution.case.caseView.goToDocumentationButton', + { + defaultMessage: 'View documentation', + } +); + +export const CONNECTORS = i18n.translate('xpack.securitySolution.case.caseView.connectors', { + defaultMessage: 'External incident management system', +}); + +export const EDIT_CONNECTOR = i18n.translate('xpack.securitySolution.case.caseView.editConnector', { + defaultMessage: 'Change external incident management system', +}); diff --git a/x-pack/plugins/siem/public/cases/pages/utils.ts b/x-pack/plugins/security_solution/public/cases/pages/utils.ts similarity index 100% rename from x-pack/plugins/siem/public/cases/pages/utils.ts rename to x-pack/plugins/security_solution/public/cases/pages/utils.ts diff --git a/x-pack/plugins/siem/public/cases/routes.tsx b/x-pack/plugins/security_solution/public/cases/routes.tsx similarity index 100% rename from x-pack/plugins/siem/public/cases/routes.tsx rename to x-pack/plugins/security_solution/public/cases/routes.tsx diff --git a/x-pack/plugins/security_solution/public/cases/translations.ts b/x-pack/plugins/security_solution/public/cases/translations.ts new file mode 100644 index 0000000000000..5b595c5892ef2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/translations.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SAVED_OBJECT_NO_PERMISSIONS_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseSavedObjectNoPermissionsTitle', + { + defaultMessage: 'Kibana feature privileges required', + } +); + +export const SAVED_OBJECT_NO_PERMISSIONS_MSG = i18n.translate( + 'xpack.securitySolution.case.caseSavedObjectNoPermissionsMessage', + { + defaultMessage: + 'To view cases, you must have privileges for the Saved Object Management feature in the Kibana space. For more information, contact your Kibana administrator.', + } +); + +export const BACK_TO_ALL = i18n.translate('xpack.securitySolution.case.caseView.backLabel', { + defaultMessage: 'Back to cases', +}); + +export const CANCEL = i18n.translate('xpack.securitySolution.case.caseView.cancel', { + defaultMessage: 'Cancel', +}); + +export const DELETE_CASE = i18n.translate( + 'xpack.securitySolution.case.confirmDeleteCase.deleteCase', + { + defaultMessage: 'Delete case', + } +); + +export const DELETE_CASES = i18n.translate( + 'xpack.securitySolution.case.confirmDeleteCase.deleteCases', + { + defaultMessage: 'Delete cases', + } +); + +export const NAME = i18n.translate('xpack.securitySolution.case.caseView.name', { + defaultMessage: 'Name', +}); + +export const OPENED_ON = i18n.translate('xpack.securitySolution.case.caseView.openedOn', { + defaultMessage: 'Opened on', +}); + +export const CLOSED_ON = i18n.translate('xpack.securitySolution.case.caseView.closedOn', { + defaultMessage: 'Closed on', +}); + +export const REPORTER = i18n.translate('xpack.securitySolution.case.caseView.reporterLabel', { + defaultMessage: 'Reporter', +}); + +export const PARTICIPANTS = i18n.translate( + 'xpack.securitySolution.case.caseView.particpantsLabel', + { + defaultMessage: 'Participants', + } +); + +export const CREATE_BC_TITLE = i18n.translate('xpack.securitySolution.case.caseView.breadcrumb', { + defaultMessage: 'Create', +}); + +export const CREATE_TITLE = i18n.translate('xpack.securitySolution.case.caseView.create', { + defaultMessage: 'Create new case', +}); + +export const DESCRIPTION = i18n.translate('xpack.securitySolution.case.caseView.description', { + defaultMessage: 'Description', +}); + +export const DESCRIPTION_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.createCase.descriptionFieldRequiredError', + { + defaultMessage: 'A description is required.', + } +); + +export const COMMENT_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.caseView.commentFieldRequiredError', + { + defaultMessage: 'A comment is required.', + } +); + +export const REQUIRED_FIELD = i18n.translate( + 'xpack.securitySolution.case.caseView.fieldRequiredError', + { + defaultMessage: 'Required field', + } +); + +export const EDIT = i18n.translate('xpack.securitySolution.case.caseView.edit', { + defaultMessage: 'Edit', +}); + +export const OPTIONAL = i18n.translate('xpack.securitySolution.case.caseView.optional', { + defaultMessage: 'Optional', +}); + +export const PAGE_TITLE = i18n.translate('xpack.securitySolution.case.pageTitle', { + defaultMessage: 'Cases', +}); + +export const CREATE_CASE = i18n.translate('xpack.securitySolution.case.caseView.createCase', { + defaultMessage: 'Create case', +}); + +export const CLOSED_CASE = i18n.translate('xpack.securitySolution.case.caseView.closedCase', { + defaultMessage: 'Closed case', +}); + +export const CLOSE_CASE = i18n.translate('xpack.securitySolution.case.caseView.closeCase', { + defaultMessage: 'Close case', +}); + +export const REOPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView.reopenCase', { + defaultMessage: 'Reopen case', +}); + +export const REOPENED_CASE = i18n.translate('xpack.securitySolution.case.caseView.reopenedCase', { + defaultMessage: 'Reopened case', +}); + +export const CASE_NAME = i18n.translate('xpack.securitySolution.case.caseView.caseName', { + defaultMessage: 'Case name', +}); + +export const TO = i18n.translate('xpack.securitySolution.case.caseView.to', { + defaultMessage: 'to', +}); + +export const TAGS = i18n.translate('xpack.securitySolution.case.caseView.tags', { + defaultMessage: 'Tags', +}); + +export const ACTIONS = i18n.translate('xpack.securitySolution.case.allCases.actions', { + defaultMessage: 'Actions', +}); + +export const NO_TAGS_AVAILABLE = i18n.translate( + 'xpack.securitySolution.case.allCases.noTagsAvailable', + { + defaultMessage: 'No tags available', + } +); + +export const NO_REPORTERS_AVAILABLE = i18n.translate( + 'xpack.securitySolution.case.caseView.noReportersAvailable', + { + defaultMessage: 'No reporters available.', + } +); + +export const COMMENTS = i18n.translate('xpack.securitySolution.case.allCases.comments', { + defaultMessage: 'Comments', +}); + +export const TAGS_HELP = i18n.translate( + 'xpack.securitySolution.case.createCase.fieldTagsHelpText', + { + defaultMessage: + 'Type one or more custom identifying tags for this case. Press enter after each tag to begin a new one.', + } +); + +export const NO_TAGS = i18n.translate('xpack.securitySolution.case.caseView.noTags', { + defaultMessage: 'No tags are currently assigned to this case.', +}); + +export const TITLE_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.createCase.titleFieldRequiredError', + { + defaultMessage: 'A title is required.', + } +); + +export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate( + 'xpack.securitySolution.case.configureCases.headerTitle', + { + defaultMessage: 'Configure cases', + } +); + +export const CONFIGURE_CASES_BUTTON = i18n.translate( + 'xpack.securitySolution.case.configureCasesButton', + { + defaultMessage: 'Edit external connection', + } +); + +export const ADD_COMMENT = i18n.translate( + 'xpack.securitySolution.case.caseView.comment.addComment', + { + defaultMessage: 'Add comment', + } +); + +export const ADD_COMMENT_HELP_TEXT = i18n.translate( + 'xpack.securitySolution.case.caseView.comment.addCommentHelpText', + { + defaultMessage: 'Add a new comment...', + } +); + +export const SAVE = i18n.translate('xpack.securitySolution.case.caseView.description.save', { + defaultMessage: 'Save', +}); + +export const GO_TO_DOCUMENTATION = i18n.translate( + 'xpack.securitySolution.case.caseView.goToDocumentationButton', + { + defaultMessage: 'View documentation', + } +); + +export const CONNECTORS = i18n.translate('xpack.securitySolution.case.caseView.connectors', { + defaultMessage: 'External incident management system', +}); + +export const EDIT_CONNECTOR = i18n.translate('xpack.securitySolution.case.caseView.editConnector', { + defaultMessage: 'Change external incident management system', +}); diff --git a/x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/helpers.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/helpers.test.tsx rename to x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/helpers.test.tsx diff --git a/x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/helpers.ts b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/helpers.ts similarity index 100% rename from x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/helpers.ts rename to x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/helpers.ts diff --git a/x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/index.test.tsx rename to x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx diff --git a/x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/add_filter_to_global_search_bar/index.tsx rename to x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/translations.ts b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/translations.ts new file mode 100644 index 0000000000000..c4f36bd7dd881 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const FILTER_FOR_VALUE = i18n.translate( + 'xpack.securitySolution.add_filter_to_global_search_bar.filterForValueHoverAction', + { + defaultMessage: 'Filter for value', + } +); + +export const FILTER_OUT_VALUE = i18n.translate( + 'xpack.securitySolution.add_filter_to_global_search_bar.filterOutValueHoverAction', + { + defaultMessage: 'Filter out value', + } +); diff --git a/x-pack/plugins/siem/public/common/components/alerts_viewer/alerts_table.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx similarity index 86% rename from x-pack/plugins/siem/public/common/components/alerts_viewer/alerts_table.tsx rename to x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx index dd608babef48f..b19343a9f4a5c 100644 --- a/x-pack/plugins/siem/public/common/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { Filter } from '../../../../../../../src/plugins/data/public'; import { StatefulEventsViewer } from '../events_viewer'; -import * as i18n from './translations'; import { alertsDefaultModel } from './default_headers'; - +import { useManageTimeline } from '../../../timelines/components/manage_timeline'; +import * as i18n from './translations'; export interface OwnProps { end: number; id: string; @@ -59,16 +59,17 @@ interface Props { const AlertsTableComponent: React.FC = ({ endDate, startDate, pageFilters = [] }) => { const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); - const timelineTypeContext = useMemo( - () => ({ + const { initializeTimeline } = useManageTimeline(); + + useEffect(() => { + initializeTimeline({ + id: ALERTS_TABLE_ID, documentType: i18n.ALERTS_DOCUMENT_TYPE, footerText: i18n.TOTAL_COUNT_OF_ALERTS, title: i18n.ALERTS_TABLE_TITLE, unit: i18n.UNIT, - }), - [] - ); - + }); + }, []); return ( = ({ endDate, startDate, pageFilters end={endDate} id={ALERTS_TABLE_ID} start={startDate} - timelineTypeContext={timelineTypeContext} /> ); }; diff --git a/x-pack/plugins/siem/public/common/components/alerts_viewer/default_headers.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts similarity index 100% rename from x-pack/plugins/siem/public/common/components/alerts_viewer/default_headers.ts rename to x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/histogram_configs.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/histogram_configs.ts new file mode 100644 index 0000000000000..363c7ee7e058b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/histogram_configs.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as i18n from './translations'; +import { MatrixHistogramOption, MatrixHisrogramConfigs } from '../matrix_histogram/types'; +import { HistogramType } from '../../../graphql/types'; + +export const alertsStackByOptions: MatrixHistogramOption[] = [ + { + text: 'event.category', + value: 'event.category', + }, + { + text: 'event.module', + value: 'event.module', + }, +]; + +const DEFAULT_STACK_BY = 'event.module'; + +export const histogramConfigs: MatrixHisrogramConfigs = { + defaultStackByOption: + alertsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? alertsStackByOptions[1], + errorMessage: i18n.ERROR_FETCHING_ALERTS_DATA, + histogramType: HistogramType.alerts, + stackByOptions: alertsStackByOptions, + subtitle: undefined, + title: i18n.ALERTS_GRAPH_TITLE, +}; diff --git a/x-pack/plugins/siem/public/common/components/alerts_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/alerts_viewer/index.tsx rename to x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/translations.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/translations.ts new file mode 100644 index 0000000000000..8403050a13114 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/translations.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ALERTS_DOCUMENT_TYPE = i18n.translate( + 'xpack.securitySolution.alertsView.alertsDocumentType', + { + defaultMessage: 'External alerts', + } +); + +export const TOTAL_COUNT_OF_ALERTS = i18n.translate( + 'xpack.securitySolution.alertsView.totalCountOfAlerts', + { + defaultMessage: 'external alerts match the search criteria', + } +); + +export const ALERTS_TABLE_TITLE = i18n.translate( + 'xpack.securitySolution.alertsView.alertsTableTitle', + { + defaultMessage: 'External alerts', + } +); + +export const ALERTS_GRAPH_TITLE = i18n.translate( + 'xpack.securitySolution.alertsView.alertsGraphTitle', + { + defaultMessage: 'External alert count', + } +); + +export const ALERTS_STACK_BY_MODULE = i18n.translate( + 'xpack.securitySolution.alertsView.alertsStackByOptions.module', + { + defaultMessage: 'module', + } +); + +export const SHOWING = i18n.translate('xpack.securitySolution.alertsView.showing', { + defaultMessage: 'Showing', +}); + +export const UNIT = (totalCount: number) => + i18n.translate('xpack.securitySolution.alertsView.unit', { + values: { totalCount }, + defaultMessage: `external {totalCount, plural, =1 {alert} other {alerts}}`, + }); + +export const ERROR_FETCHING_ALERTS_DATA = i18n.translate( + 'xpack.securitySolution.alertsView.errorFetchingAlertsData', + { + defaultMessage: 'Failed to query alerts data', + } +); + +export const CATEGORY = i18n.translate('xpack.securitySolution.alertsView.categoryLabel', { + defaultMessage: 'category', +}); + +export const MODULE = i18n.translate('xpack.securitySolution.alertsView.moduleLabel', { + defaultMessage: 'module', +}); diff --git a/x-pack/plugins/siem/public/common/components/alerts_viewer/types.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts similarity index 100% rename from x-pack/plugins/siem/public/common/components/alerts_viewer/types.ts rename to x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx new file mode 100644 index 0000000000000..f939cf81d1bd3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; + +import { AndOrBadge } from '..'; + +const sampleText = + 'Doggo ipsum i am bekom fat snoot wow such tempt waggy wags floofs, ruff heckin good boys and girls mlem. Ruff heckin good boys and girls mlem stop it fren borkf borking doggo very hand that feed shibe, you are doing me the shock big ol heck smol borking doggo with a long snoot for pats heckin good boys. You are doing me the shock smol borking doggo with a long snoot for pats wow very biscit, length boy. Doggo ipsum i am bekom fat snoot wow such tempt waggy wags floofs, ruff heckin good boys and girls mlem. Ruff heckin good boys and girls mlem stop it fren borkf borking doggo very hand that feed shibe, you are doing me the shock big ol heck smol borking doggo with a long snoot for pats heckin good boys.'; + +storiesOf('components/AndOrBadge', module) + .add('and', () => ( + ({ eui: euiLightVars, darkMode: true })}> + + + )) + .add('or', () => ( + ({ eui: euiLightVars, darkMode: true })}> + + + )) + .add('antennas', () => ( + ({ eui: euiLightVars, darkMode: true })}> + + + + + +

    {sampleText}

    +
    +
    +
    + )); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.test.tsx new file mode 100644 index 0000000000000..ed918a59a514a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { mount } from 'enzyme'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +import { AndOrBadge } from './'; + +describe('AndOrBadge', () => { + test('it renders top and bottom antenna bars when "includeAntennas" is true', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); + expect(wrapper.find('EuiFlexItem[data-test-subj="andOrBadgeBarTop"]')).toHaveLength(1); + expect(wrapper.find('EuiFlexItem[data-test-subj="andOrBadgeBarBottom"]')).toHaveLength(1); + }); + + test('it renders "and" when "type" is "and"', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); + expect(wrapper.find('EuiFlexItem[data-test-subj="and-or-badge-bar"]')).toHaveLength(0); + }); + + test('it renders "or" when "type" is "or"', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); + expect(wrapper.find('EuiFlexItem[data-test-subj="and-or-badge-bar"]')).toHaveLength(0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.tsx new file mode 100644 index 0000000000000..ba3f880d9757e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiBadge, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +import * as i18n from './translations'; + +const AndOrBadgeAntenna = styled(EuiFlexItem)` + ${({ theme }) => css` + background: ${theme.eui.euiColorLightShade}; + position: relative; + width: 2px; + &:after { + background: ${theme.eui.euiColorLightShade}; + content: ''; + height: 8px; + right: -4px; + position: absolute; + width: 9px; + clip-path: circle(); + } + &.topAndOrBadgeAntenna { + &:after { + top: -1px; + } + } + &.bottomAndOrBadgeAntenna { + &:after { + bottom: -1px; + } + } + &.euiFlexItem { + margin: 0 12px 0 0; + } + `} +`; + +const EuiFlexItemWrapper = styled(EuiFlexItem)` + &.euiFlexItem { + margin: 0 12px 0 0; + } +`; + +const RoundedBadge = (styled(EuiBadge)` + align-items: center; + border-radius: 100%; + display: inline-flex; + font-size: 9px; + height: 34px; + justify-content: center; + margin: 0 5px 0 5px; + padding: 7px 6px 4px 6px; + user-select: none; + width: 34px; + .euiBadge__content { + position: relative; + top: -1px; + } + .euiBadge__text { + text-overflow: clip; + } +` as unknown) as typeof EuiBadge; + +RoundedBadge.displayName = 'RoundedBadge'; + +export type AndOr = 'and' | 'or'; + +/** Displays AND / OR in a round badge */ +// Ref: https://github.com/elastic/eui/issues/1655 +export const AndOrBadge = React.memo<{ type: AndOr; includeAntennas?: boolean }>( + ({ type, includeAntennas = false }) => { + const getBadge = () => ( + + {type === 'and' ? i18n.AND : i18n.OR} + + ); + + const getBadgeWithAntennas = () => ( + + + {getBadge()} + + + ); + + return includeAntennas ? getBadgeWithAntennas() : getBadge(); + } +); + +AndOrBadge.displayName = 'AndOrBadge'; diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/translations.ts b/x-pack/plugins/security_solution/public/common/components/and_or_badge/translations.ts new file mode 100644 index 0000000000000..390652738363f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const AND = i18n.translate('xpack.securitySolution.andOrBadge.and', { + defaultMessage: 'AND', +}); + +export const OR = i18n.translate('xpack.securitySolution.andOrBadge.or', { + defaultMessage: 'OR', +}); diff --git a/x-pack/plugins/siem/public/common/components/autocomplete_field/__examples__/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete_field/__examples__/index.stories.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/autocomplete_field/__examples__/index.stories.tsx rename to x-pack/plugins/security_solution/public/common/components/autocomplete_field/__examples__/index.stories.tsx diff --git a/x-pack/plugins/siem/public/common/components/autocomplete_field/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/autocomplete_field/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/autocomplete_field/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/autocomplete_field/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/autocomplete_field/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete_field/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/autocomplete_field/index.test.tsx rename to x-pack/plugins/security_solution/public/common/components/autocomplete_field/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete_field/index.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete_field/index.tsx new file mode 100644 index 0000000000000..f1b7da522fbd0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete_field/index.tsx @@ -0,0 +1,335 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFieldSearch, + EuiFieldSearchProps, + EuiOutsideClickDetector, + EuiPanel, +} from '@elastic/eui'; +import React from 'react'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; + +import euiStyled from '../../../../../../legacy/common/eui_styled_components'; + +import { SuggestionItem } from './suggestion_item'; + +interface AutocompleteFieldProps { + 'data-test-subj'?: string; + isLoadingSuggestions: boolean; + isValid: boolean; + loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; + onSubmit?: (value: string) => void; + onChange?: (value: string) => void; + placeholder?: string; + suggestions: QuerySuggestion[]; + value: string; +} + +interface AutocompleteFieldState { + areSuggestionsVisible: boolean; + isFocused: boolean; + selectedIndex: number | null; +} + +export class AutocompleteField extends React.PureComponent< + AutocompleteFieldProps, + AutocompleteFieldState +> { + public readonly state: AutocompleteFieldState = { + areSuggestionsVisible: false, + isFocused: false, + selectedIndex: null, + }; + + private inputElement: HTMLInputElement | null = null; + + public render() { + const { + 'data-test-subj': dataTestSubj, + suggestions, + isLoadingSuggestions, + isValid, + placeholder, + value, + } = this.props; + const { areSuggestionsVisible, selectedIndex } = this.state; + return ( + + + + {areSuggestionsVisible && !isLoadingSuggestions && suggestions.length > 0 ? ( + + {suggestions.map((suggestion, suggestionIndex) => ( + + ))} + + ) : null} + + + ); + } + + public componentDidUpdate(prevProps: AutocompleteFieldProps, prevState: AutocompleteFieldState) { + const hasNewValue = prevProps.value !== this.props.value; + const hasNewSuggestions = prevProps.suggestions !== this.props.suggestions; + + if (hasNewValue) { + this.updateSuggestions(); + } + + if (hasNewSuggestions && this.state.isFocused) { + this.showSuggestions(); + } + } + + private handleChangeInputRef = (element: HTMLInputElement | null) => { + this.inputElement = element; + }; + + private handleChange = (evt: React.ChangeEvent) => { + this.changeValue(evt.currentTarget.value); + }; + + private handleKeyDown = (evt: React.KeyboardEvent) => { + const { suggestions } = this.props; + switch (evt.key) { + case 'ArrowUp': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState( + composeStateUpdaters(withSuggestionsVisible, withPreviousSuggestionSelected) + ); + } + break; + case 'ArrowDown': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState(composeStateUpdaters(withSuggestionsVisible, withNextSuggestionSelected)); + } else { + this.updateSuggestions(); + } + break; + case 'Enter': + evt.preventDefault(); + if (this.state.selectedIndex !== null) { + this.applySelectedSuggestion(); + } else { + this.submit(); + } + break; + case 'Tab': + evt.preventDefault(); + if (this.state.areSuggestionsVisible && this.props.suggestions.length === 1) { + this.applySuggestionAt(0)(); + } else if (this.state.selectedIndex !== null) { + this.applySelectedSuggestion(); + } + break; + case 'Escape': + evt.preventDefault(); + evt.stopPropagation(); + this.setState(withSuggestionsHidden); + break; + } + }; + + private handleKeyUp = (evt: React.KeyboardEvent) => { + switch (evt.key) { + case 'ArrowLeft': + case 'ArrowRight': + case 'Home': + case 'End': + this.updateSuggestions(); + break; + } + }; + + private handleFocus = () => { + this.setState(composeStateUpdaters(withSuggestionsVisible, withFocused)); + }; + + private handleBlur = () => { + this.setState(composeStateUpdaters(withSuggestionsHidden, withUnfocused)); + }; + + private selectSuggestionAt = (index: number) => () => { + this.setState(withSuggestionAtIndexSelected(index)); + }; + + private applySelectedSuggestion = () => { + if (this.state.selectedIndex !== null) { + this.applySuggestionAt(this.state.selectedIndex)(); + } + }; + + private applySuggestionAt = (index: number) => () => { + const { value, suggestions } = this.props; + const selectedSuggestion = suggestions[index]; + + if (!selectedSuggestion) { + return; + } + + const newValue = + value.substr(0, selectedSuggestion.start) + + selectedSuggestion.text + + value.substr(selectedSuggestion.end); + + this.setState(withSuggestionsHidden); + this.changeValue(newValue); + this.focusInputElement(); + }; + + private changeValue = (value: string) => { + const { onChange } = this.props; + + if (onChange) { + onChange(value); + } + }; + + private focusInputElement = () => { + if (this.inputElement) { + this.inputElement.focus(); + } + }; + + private showSuggestions = () => { + this.setState(withSuggestionsVisible); + }; + + private submit = () => { + const { isValid, onSubmit, value } = this.props; + + if (isValid && onSubmit) { + onSubmit(value); + } + + this.setState(withSuggestionsHidden); + }; + + private updateSuggestions = () => { + const inputCursorPosition = this.inputElement ? this.inputElement.selectionStart || 0 : 0; + this.props.loadSuggestions(this.props.value, inputCursorPosition, 10); + }; +} + +type StateUpdater = ( + prevState: Readonly, + prevProps: Readonly +) => State | null; + +function composeStateUpdaters(...updaters: Array>) { + return (state: State, props: Props) => + updaters.reduce((currentState, updater) => updater(currentState, props) || currentState, state); +} + +const withPreviousSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + props.suggestions.length - 1) % props.suggestions.length + : Math.max(props.suggestions.length - 1, 0), +}); + +const withNextSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + 1) % props.suggestions.length + : 0, +}); + +const withSuggestionAtIndexSelected = (suggestionIndex: number) => ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : suggestionIndex >= 0 && suggestionIndex < props.suggestions.length + ? suggestionIndex + : 0, +}); + +const withSuggestionsVisible = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: true, +}); + +const withSuggestionsHidden = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: false, + selectedIndex: null, +}); + +const withFocused = (state: AutocompleteFieldState) => ({ + ...state, + isFocused: true, +}); + +const withUnfocused = (state: AutocompleteFieldState) => ({ + ...state, + isFocused: false, +}); + +export const FixedEuiFieldSearch: React.FC< + React.InputHTMLAttributes & + EuiFieldSearchProps & { + inputRef?: (element: HTMLInputElement | null) => void; + onSearch: (value: string) => void; + } +> = EuiFieldSearch as any; // eslint-disable-line @typescript-eslint/no-explicit-any + +const AutocompleteContainer = euiStyled.div` + position: relative; +`; + +AutocompleteContainer.displayName = 'AutocompleteContainer'; + +const SuggestionsPanel = euiStyled(EuiPanel).attrs(() => ({ + paddingSize: 'none', + hasShadow: true, +}))` + position: absolute; + width: 100%; + margin-top: 2px; + overflow: hidden; + z-index: ${(props) => props.theme.eui.euiZLevel1}; +`; + +SuggestionsPanel.displayName = 'SuggestionsPanel'; diff --git a/x-pack/plugins/siem/public/common/components/autocomplete_field/suggestion_item.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete_field/suggestion_item.tsx similarity index 86% rename from x-pack/plugins/siem/public/common/components/autocomplete_field/suggestion_item.tsx rename to x-pack/plugins/security_solution/public/common/components/autocomplete_field/suggestion_item.tsx index b305663dd48be..56d25cbdda024 100644 --- a/x-pack/plugins/siem/public/common/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete_field/suggestion_item.tsx @@ -44,10 +44,10 @@ const SuggestionItemContainer = euiStyled.div<{ }>` display: flex; flex-direction: row; - font-size: ${props => props.theme.eui.euiFontSizeS}; - height: ${props => props.theme.eui.euiSizeXL}; + font-size: ${(props) => props.theme.eui.euiFontSizeS}; + height: ${(props) => props.theme.eui.euiSizeXL}; white-space: nowrap; - background-color: ${props => + background-color: ${(props) => props.isSelected ? props.theme.eui.euiColorLightestShade : 'transparent'}; `; @@ -58,26 +58,26 @@ const SuggestionItemField = euiStyled.div` cursor: pointer; display: flex; flex-direction: row; - height: ${props => props.theme.eui.euiSizeXL}; - padding: ${props => props.theme.eui.euiSizeXS}; + height: ${(props) => props.theme.eui.euiSizeXL}; + padding: ${(props) => props.theme.eui.euiSizeXS}; `; SuggestionItemField.displayName = 'SuggestionItemField'; const SuggestionItemIconField = styled(SuggestionItemField)<{ suggestionType: string }>` - background-color: ${props => + background-color: ${(props) => transparentize(0.9, getEuiIconColor(props.theme, props.suggestionType))}; - color: ${props => getEuiIconColor(props.theme, props.suggestionType)}; + color: ${(props) => getEuiIconColor(props.theme, props.suggestionType)}; flex: 0 0 auto; justify-content: center; - width: ${props => props.theme.eui.euiSizeXL}; + width: ${(props) => props.theme.eui.euiSizeXL}; `; SuggestionItemIconField.displayName = 'SuggestionItemIconField'; const SuggestionItemTextField = styled(SuggestionItemField)` flex: 2 0 0; - font-family: ${props => props.theme.eui.euiCodeFontFamily}; + font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; `; SuggestionItemTextField.displayName = 'SuggestionItemTextField'; @@ -89,7 +89,7 @@ const SuggestionItemDescriptionField = styled(SuggestionItemField)` display: inline; span { - font-family: ${props => props.theme.eui.euiCodeFontFamily}; + font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; } } `; diff --git a/x-pack/plugins/siem/public/common/components/charts/__snapshots__/areachart.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/charts/__snapshots__/areachart.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/charts/__snapshots__/areachart.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/charts/__snapshots__/areachart.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/charts/__snapshots__/barchart.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/charts/__snapshots__/barchart.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/charts/__snapshots__/barchart.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/charts/__snapshots__/barchart.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/charts/areachart.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/areachart.test.tsx similarity index 87% rename from x-pack/plugins/siem/public/common/components/charts/areachart.test.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/areachart.test.tsx index 3c2de28ae423c..626d485fb166b 100644 --- a/x-pack/plugins/siem/public/common/components/charts/areachart.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/areachart.test.tsx @@ -208,39 +208,23 @@ describe('AreaChartBaseComponent', () => { }); it('should render AreaSeries with given xScaleType', () => { - expect( - shallowWrapper - .find(AreaSeries) - .first() - .prop('xScaleType') - ).toEqual(configs.series.xScaleType); + expect(shallowWrapper.find(AreaSeries).first().prop('xScaleType')).toEqual( + configs.series.xScaleType + ); }); it('should render AreaSeries with given yScaleType', () => { - expect( - shallowWrapper - .find(AreaSeries) - .first() - .prop('yScaleType') - ).toEqual(configs.series.yScaleType); + expect(shallowWrapper.find(AreaSeries).first().prop('yScaleType')).toEqual( + configs.series.yScaleType + ); }); it('should render xAxis with given tick formatter', () => { - expect( - shallowWrapper - .find(Axis) - .first() - .prop('tickFormat') - ).toEqual(mockTimeFormatter); + expect(shallowWrapper.find(Axis).first().prop('tickFormat')).toEqual(mockTimeFormatter); }); it('should render yAxis with given tick formatter', () => { - expect( - shallowWrapper - .find(Axis) - .last() - .prop('tickFormat') - ).toEqual(mockNumberFormatter); + expect(shallowWrapper.find(Axis).last().prop('tickFormat')).toEqual(mockNumberFormatter); }); }); @@ -261,39 +245,19 @@ describe('AreaChartBaseComponent', () => { }); it('should render AreaSeries with default xScaleType: Linear', () => { - expect( - shallowWrapper - .find(AreaSeries) - .first() - .prop('xScaleType') - ).toEqual(ScaleType.Linear); + expect(shallowWrapper.find(AreaSeries).first().prop('xScaleType')).toEqual(ScaleType.Linear); }); it('should render AreaSeries with default yScaleType: Linear', () => { - expect( - shallowWrapper - .find(AreaSeries) - .first() - .prop('yScaleType') - ).toEqual(ScaleType.Linear); + expect(shallowWrapper.find(AreaSeries).first().prop('yScaleType')).toEqual(ScaleType.Linear); }); it('should not format xTicks value', () => { - expect( - shallowWrapper - .find(Axis) - .last() - .prop('tickFormat') - ).toBeUndefined(); + expect(shallowWrapper.find(Axis).last().prop('tickFormat')).toBeUndefined(); }); it('should not format yTicks value', () => { - expect( - shallowWrapper - .find(Axis) - .last() - .prop('tickFormat') - ).toBeUndefined(); + expect(shallowWrapper.find(Axis).last().prop('tickFormat')).toBeUndefined(); }); }); @@ -325,7 +289,7 @@ describe('AreaChart', () => { }, customHeight: 324, }; - describe.each(chartDataSets as Array<[ChartSeriesData[]]>)('with valid data [%o]', data => { + describe.each(chartDataSets as Array<[ChartSeriesData[]]>)('with valid data [%o]', (data) => { beforeAll(() => { shallowWrapper = shallow(); }); @@ -338,7 +302,7 @@ describe('AreaChart', () => { describe.each(chartHolderDataSets as Array<[ChartSeriesData[] | null | undefined]>)( 'with invalid data [%o]', - data => { + (data) => { beforeAll(() => { shallowWrapper = shallow(); }); diff --git a/x-pack/plugins/siem/public/common/components/charts/areachart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx similarity index 99% rename from x-pack/plugins/siem/public/common/components/charts/areachart.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx index b555d69966d63..b503d63553835 100644 --- a/x-pack/plugins/siem/public/common/components/charts/areachart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx @@ -86,7 +86,7 @@ export const AreaChartBaseComponent = ({
    - {data.map(series => { + {data.map((series) => { const seriesKey = series.key; return checkIfAllTheDataInTheSeriesAreValid(series) ? ( { }); it('should render BarSeries with given xScaleType', () => { - expect( - shallowWrapper - .find(BarSeries) - .first() - .prop('xScaleType') - ).toEqual(configs.series.xScaleType); + expect(shallowWrapper.find(BarSeries).first().prop('xScaleType')).toEqual( + configs.series.xScaleType + ); }); it('should render BarSeries with given yScaleType', () => { - expect( - shallowWrapper - .find(BarSeries) - .first() - .prop('yScaleType') - ).toEqual(configs.series.yScaleType); + expect(shallowWrapper.find(BarSeries).first().prop('yScaleType')).toEqual( + configs.series.yScaleType + ); }); it('should render xAxis with given tick formatter', () => { - expect( - shallowWrapper - .find(Axis) - .first() - .prop('tickFormat') - ).toBeUndefined(); + expect(shallowWrapper.find(Axis).first().prop('tickFormat')).toBeUndefined(); }); it('should render yAxis with given tick formatter', () => { - expect( - shallowWrapper - .find(Axis) - .last() - .prop('tickFormat') - ).toEqual(mockNumberFormatter); + expect(shallowWrapper.find(Axis).last().prop('tickFormat')).toEqual(mockNumberFormatter); }); }); @@ -247,39 +231,19 @@ describe('BarChartBaseComponent', () => { }); it('should render BarSeries with default xScaleType: Linear', () => { - expect( - shallowWrapper - .find(BarSeries) - .first() - .prop('xScaleType') - ).toEqual(ScaleType.Linear); + expect(shallowWrapper.find(BarSeries).first().prop('xScaleType')).toEqual(ScaleType.Linear); }); it('should render BarSeries with default yScaleType: Linear', () => { - expect( - shallowWrapper - .find(BarSeries) - .first() - .prop('yScaleType') - ).toEqual(ScaleType.Linear); + expect(shallowWrapper.find(BarSeries).first().prop('yScaleType')).toEqual(ScaleType.Linear); }); it('should not format xTicks value', () => { - expect( - shallowWrapper - .find(Axis) - .last() - .prop('tickFormat') - ).toBeUndefined(); + expect(shallowWrapper.find(Axis).last().prop('tickFormat')).toBeUndefined(); }); it('should not format yTicks value', () => { - expect( - shallowWrapper - .find(Axis) - .last() - .prop('tickFormat') - ).toBeUndefined(); + expect(shallowWrapper.find(Axis).last().prop('tickFormat')).toBeUndefined(); }); }); @@ -296,7 +260,7 @@ describe('BarChartBaseComponent', () => { }); }); -describe.each(chartDataSets)('BarChart with valid data [%o]', data => { +describe.each(chartDataSets)('BarChart with valid data [%o]', (data) => { let shallowWrapper: ShallowWrapper; beforeAll(() => { @@ -378,23 +342,20 @@ describe.each(chartDataSets)('BarChart with stackByField', () => { }); }); - data.forEach(datum => { + data.forEach((datum) => { test(`it renders the expected draggable legend text for datum ${datum.key}`, () => { const dataProviderId = `draggableId.content.draggable-legend-item-uuid_v4()-${escapeDataProviderId( stackByField )}-${escapeDataProviderId(datum.key)}`; expect( - wrapper - .find(`div [data-rbd-draggable-id="${dataProviderId}"]`) - .first() - .text() + wrapper.find(`div [data-rbd-draggable-id="${dataProviderId}"]`).first().text() ).toEqual(datum.key); }); }); }); -describe.each(chartHolderDataSets)('BarChart with invalid data [%o]', data => { +describe.each(chartHolderDataSets)('BarChart with invalid data [%o]', (data) => { let shallowWrapper: ShallowWrapper; beforeAll(() => { diff --git a/x-pack/plugins/siem/public/common/components/charts/barchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx similarity index 99% rename from x-pack/plugins/siem/public/common/components/charts/barchart.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx index 64d15cd6731cb..42fc2ac9b8453 100644 --- a/x-pack/plugins/siem/public/common/components/charts/barchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx @@ -74,7 +74,7 @@ export const BarChartBaseComponent = ({ return chartConfigs.width && chartConfigs.height ? ( - {data.map(series => { + {data.map((series) => { const barSeriesKey = series.key; return checkIfAllTheDataInTheSeriesAreValid(series) ? ( { shallowWrapper = shallow( ); - expect( - shallowWrapper - .find(`[data-test-subj="chartHolderText"]`) - .childAt(0) - .text() - ).toEqual('All values returned zero'); + expect(shallowWrapper.find(`[data-test-subj="chartHolderText"]`).childAt(0).text()).toEqual( + 'All values returned zero' + ); }); it('should render correct wording when unexpected value exists', () => { @@ -94,11 +91,8 @@ describe('ChartPlaceHolder', () => { data={mockDataUnexpectedValue as ChartSeriesData[]} /> ); - expect( - shallowWrapper - .find(`[data-test-subj="chartHolderText"]`) - .childAt(0) - .text() - ).toEqual('Chart Data Not Available'); + expect(shallowWrapper.find(`[data-test-subj="chartHolderText"]`).childAt(0).text()).toEqual( + 'Chart Data Not Available' + ); }); }); diff --git a/x-pack/plugins/siem/public/common/components/charts/chart_place_holder.tsx b/x-pack/plugins/security_solution/public/common/components/charts/chart_place_holder.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/charts/chart_place_holder.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/chart_place_holder.tsx diff --git a/x-pack/plugins/siem/public/common/components/charts/common.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx similarity index 97% rename from x-pack/plugins/siem/public/common/components/charts/common.test.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx index 3748a2505c115..023768c6af07d 100644 --- a/x-pack/plugins/siem/public/common/components/charts/common.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx @@ -143,7 +143,7 @@ describe('checkIfAllValuesAreZero', () => { ], ]; - describe.each(mockInvalidDataSets)('with data [%o]', data => { + describe.each(mockInvalidDataSets)('with data [%o]', (data) => { let result: boolean; beforeAll(() => { result = checkIfAllValuesAreZero(data); @@ -154,7 +154,7 @@ describe('checkIfAllValuesAreZero', () => { }); }); - describe.each(mockValidDataSets)('with data [%o]', data => { + describe.each(mockValidDataSets)('with data [%o]', (data) => { let result: boolean; beforeAll(() => { result = checkIfAllValuesAreZero(data); diff --git a/x-pack/plugins/siem/public/common/components/charts/common.tsx b/x-pack/plugins/security_solution/public/common/components/charts/common.tsx similarity index 98% rename from x-pack/plugins/siem/public/common/components/charts/common.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/common.tsx index 1078040e9efd0..4858af4f4f257 100644 --- a/x-pack/plugins/siem/public/common/components/charts/common.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/common.tsx @@ -58,7 +58,7 @@ export interface ChartSeriesData { } const WrappedByAutoSizerComponent = styled.div<{ height?: string }>` - ${style => + ${(style) => ` height: ${style.height != null ? style.height : defaultChartHeight}; `} @@ -128,6 +128,6 @@ export const getChartWidth = (customWidth?: number, autoSizerWidth?: number): st export const checkIfAllValuesAreZero = (data: ChartSeriesData[] | null | undefined): boolean => Array.isArray(data) && - data.every(series => { + data.every((series) => { return Array.isArray(series.value) && (series.value as ChartData[]).every(({ y }) => y === 0); }); diff --git a/x-pack/plugins/siem/public/common/components/charts/draggable_legend.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.test.tsx similarity index 99% rename from x-pack/plugins/siem/public/common/components/charts/draggable_legend.test.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.test.tsx index 0da0c2bdc35f2..a11fdda3d1b3a 100644 --- a/x-pack/plugins/siem/public/common/components/charts/draggable_legend.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.test.tsx @@ -99,7 +99,7 @@ describe('DraggableLegend', () => { }); it('renders the legend items', () => { - legendItems.forEach(item => + legendItems.forEach((item) => expect( wrapper .find( diff --git a/x-pack/plugins/siem/public/common/components/charts/draggable_legend.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.tsx similarity index 98% rename from x-pack/plugins/siem/public/common/components/charts/draggable_legend.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.tsx index ef3fbb8780d15..33d077f8cf1bc 100644 --- a/x-pack/plugins/siem/public/common/components/charts/draggable_legend.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.tsx @@ -51,7 +51,7 @@ const DraggableLegendComponent: React.FC<{ > - {legendItems.map(item => ( + {legendItems.map((item) => ( diff --git a/x-pack/plugins/siem/public/common/components/charts/draggable_legend_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/common/components/charts/draggable_legend_item.test.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx index 581952a8415f6..8ff75c8ca0780 100644 --- a/x-pack/plugins/siem/public/common/components/charts/draggable_legend_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx @@ -51,20 +51,14 @@ describe('DraggableLegendItem', () => { }); it('renders a colored circle with the expected legend item color', () => { - expect( - wrapper - .find('[data-test-subj="legend-color"]') - .first() - .props().color - ).toEqual(legendItem.color); + expect(wrapper.find('[data-test-subj="legend-color"]').first().props().color).toEqual( + legendItem.color + ); }); it('renders draggable legend item text', () => { expect( - wrapper - .find(`[data-test-subj="legend-item-${legendItem.dataProviderId}"]`) - .first() - .text() + wrapper.find(`[data-test-subj="legend-item-${legendItem.dataProviderId}"]`).first().text() ).toEqual(legendItem.value); }); @@ -95,12 +89,9 @@ describe('DraggableLegendItem', () => { }); it('renders a colored circle with the expected legend item color', () => { - expect( - wrapper - .find('[data-test-subj="legend-color"]') - .first() - .props().color - ).toEqual(allOthersLegendItem.color); + expect(wrapper.find('[data-test-subj="legend-color"]').first().props().color).toEqual( + allOthersLegendItem.color + ); }); it('does NOT render a draggable legend item', () => { @@ -112,12 +103,9 @@ describe('DraggableLegendItem', () => { }); it('renders NON-draggable `All others` legend item text', () => { - expect( - wrapper - .find(`[data-test-subj="all-others-legend-item"]`) - .first() - .text() - ).toEqual(allOthersLegendItem.value); + expect(wrapper.find(`[data-test-subj="all-others-legend-item"]`).first().text()).toEqual( + allOthersLegendItem.value + ); }); }); diff --git a/x-pack/plugins/siem/public/common/components/charts/draggable_legend_item.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/charts/draggable_legend_item.tsx rename to x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/charts/translation.ts b/x-pack/plugins/security_solution/public/common/components/charts/translation.ts new file mode 100644 index 0000000000000..68b90af65aefa --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/charts/translation.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ALL_VALUES_ZEROS_TITLE = i18n.translate( + 'xpack.securitySolution.chart.dataAllValuesZerosTitle', + { + defaultMessage: 'All values returned zero', + } +); + +export const DATA_NOT_AVAILABLE_TITLE = i18n.translate( + 'xpack.securitySolution.chart.dataNotAvailableTitle', + { + defaultMessage: 'Chart Data Not Available', + } +); + +export const ALL_OTHERS = i18n.translate('xpack.securitySolution.chart.allOthersGroupingLabel', { + defaultMessage: 'All others', +}); diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context.tsx diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx similarity index 97% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 3bd2a3da1c88b..c33677e41db0e 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -15,7 +15,7 @@ import { BrowserFields } from '../../containers/source'; import { dragAndDropModel, dragAndDropSelectors } from '../../store'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { IdToDataProvider } from '../../store/drag_and_drop/model'; -import { State } from '../../store/reducer'; +import { State } from '../../store/types'; import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; import { reArrangeProviders } from '../../../timelines/components/timeline/data_providers/helpers'; import { ACTIVE_TIMELINE_REDUX_ID } from '../top_n'; @@ -27,6 +27,7 @@ import { addFieldToTimelineColumns, addProviderToTimeline, fieldWasDroppedOnTimelineColumns, + getTimelineIdFromColumnDroppableId, IS_DRAGGING_CLASS_NAME, IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME, providerWasDroppedOnTimeline, @@ -82,7 +83,7 @@ const onDragEndHandler = ({ browserFields, dispatch, result, - timelineId: ACTIVE_TIMELINE_REDUX_ID, + timelineId: getTimelineIdFromColumnDroppableId(result.destination?.droppableId ?? ''), }); } }; diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper.test.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx similarity index 99% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx index f90a5c1410c34..596bd51070160 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx @@ -172,7 +172,7 @@ export const DraggableWrapper = React.memo( )} > - {droppableProvided => ( + {(droppableProvided) => (
    { + return { + v1: jest.fn(() => 'uuid.v1()'), + v4: jest.fn(() => 'uuid.v4()'), + }; +}); + +jest.mock('../../hooks/use_add_to_timeline'); + +const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings; +const timelineId = 'cool-id'; +const field = 'process.name'; +const value = 'nice'; +const toggleTopN = jest.fn(); +const defaultProps = { + field, + showTopN: false, + timelineId, + toggleTopN, + value, +}; + +describe('DraggableWrapperHoverContent', () => { + beforeAll(() => { + // our mock implementation of the useAddToTimeline hook returns a mock startDragToTimeline function: + (useAddToTimeline as jest.Mock).mockReturnValue(jest.fn()); + }); + + // Suppress warnings about "react-beautiful-dnd" + /* eslint-disable no-console */ + const originalError = console.error; + const originalWarn = console.warn; + beforeEach(() => { + jest.clearAllMocks(); + }); + beforeAll(() => { + console.warn = jest.fn(); + console.error = jest.fn(); + }); + afterAll(() => { + console.error = originalError; + console.warn = originalWarn; + }); + + /** + * The tests for "Filter for value" and "Filter out value" are similar enough + * to combine them into "table tests" using this array + */ + const forOrOut = ['for', 'out']; + + forOrOut.forEach((hoverAction) => { + describe(`Filter ${hoverAction} value`, () => { + test(`it renders the 'Filter ${hoverAction} value' button when showTopN is false`, () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().exists() + ).toBe(true); + }); + + test(`it does NOT render the 'Filter ${hoverAction} value' button when showTopN is true`, () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().exists() + ).toBe(false); + }); + + describe('when run in the context of a timeline', () => { + let filterManager: FilterManager; + let wrapper: ReactWrapper; + let onFilterAdded: () => void; + + beforeEach(() => { + filterManager = new FilterManager(mockUiSettingsForFilterManager); + filterManager.addFilters = jest.fn(); + onFilterAdded = jest.fn(); + const manageTimelineForTesting = { + [timelineId]: { + ...timelineDefaults, + id: timelineId, + filterManager, + }, + }; + + wrapper = mount( + + + + + + ); + }); + test('when clicked, it adds a filter to the timeline when running in the context of a timeline', () => { + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click'); + wrapper.update(); + + expect(filterManager.addFilters).toBeCalledWith({ + meta: { + alias: null, + disabled: false, + key: 'process.name', + negate: hoverAction === 'out' ? true : false, + params: { query: 'nice' }, + type: 'phrase', + value: 'nice', + }, + query: { match: { 'process.name': { query: 'nice', type: 'phrase' } } }, + }); + }); + + test('when clicked, invokes onFilterAdded when running in the context of a timeline', () => { + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click'); + wrapper.update(); + + expect(onFilterAdded).toBeCalled(); + }); + }); + + describe('when NOT run in the context of a timeline', () => { + let wrapper: ReactWrapper; + let onFilterAdded: () => void; + const kibana = useKibana(); + + beforeEach(() => { + kibana.services.data.query.filterManager.addFilters = jest.fn(); + onFilterAdded = jest.fn(); + + wrapper = mount( + + + + ); + }); + + test('when clicked, it adds a filter to the global filters when NOT running in the context of a timeline', () => { + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click'); + wrapper.update(); + + expect(kibana.services.data.query.filterManager.addFilters).toBeCalledWith({ + meta: { + alias: null, + disabled: false, + key: 'process.name', + negate: hoverAction === 'out' ? true : false, + params: { query: 'nice' }, + type: 'phrase', + value: 'nice', + }, + query: { match: { 'process.name': { query: 'nice', type: 'phrase' } } }, + }); + }); + + test('when clicked, invokes onFilterAdded when NOT running in the context of a timeline', () => { + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click'); + wrapper.update(); + + expect(onFilterAdded).toBeCalled(); + }); + }); + + describe('an empty string value when run in the context of a timeline', () => { + let filterManager: FilterManager; + let wrapper: ReactWrapper; + let onFilterAdded: () => void; + + beforeEach(() => { + filterManager = new FilterManager(mockUiSettingsForFilterManager); + filterManager.addFilters = jest.fn(); + onFilterAdded = jest.fn(); + + const manageTimelineForTesting = { + [timelineId]: { + ...timelineDefaults, + id: timelineId, + filterManager, + }, + }; + + wrapper = mount( + + + + + + ); + }); + + const expectedFilterTypeDescription = + hoverAction === 'for' ? 'a "NOT exists"' : 'an "exists"'; + test(`when clicked, it adds ${expectedFilterTypeDescription} filter to the timeline when run in the context of a timeline`, () => { + const expected = + hoverAction === 'for' + ? { + exists: { field: 'process.name' }, + meta: { + alias: null, + disabled: false, + key: 'process.name', + negate: true, + type: 'exists', + value: 'exists', + }, + } + : { + exists: { field: 'process.name' }, + meta: { + alias: null, + disabled: false, + key: 'process.name', + negate: false, + type: 'exists', + value: 'exists', + }, + }; + + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click'); + wrapper.update(); + + expect(filterManager.addFilters).toBeCalledWith(expected); + }); + }); + + describe('an empty string value when NOT run in the context of a timeline', () => { + let wrapper: ReactWrapper; + let onFilterAdded: () => void; + const kibana = useKibana(); + + beforeEach(() => { + kibana.services.data.query.filterManager.addFilters = jest.fn(); + onFilterAdded = jest.fn(); + + wrapper = mount( + + + + ); + }); + + const expectedFilterTypeDescription = + hoverAction === 'for' ? 'a "NOT exists"' : 'an "exists"'; + test(`when clicked, it adds ${expectedFilterTypeDescription} filter to the global filters when NOT running in the context of a timeline`, () => { + const expected = + hoverAction === 'for' + ? { + exists: { field: 'process.name' }, + meta: { + alias: null, + disabled: false, + key: 'process.name', + negate: true, + type: 'exists', + value: 'exists', + }, + } + : { + exists: { field: 'process.name' }, + meta: { + alias: null, + disabled: false, + key: 'process.name', + negate: false, + type: 'exists', + value: 'exists', + }, + }; + + wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click'); + wrapper.update(); + + expect(kibana.services.data.query.filterManager.addFilters).toBeCalledWith(expected); + }); + }); + }); + }); + + describe('Add to timeline', () => { + const aggregatableStringField = 'cloud.account.id'; + const draggableId = 'draggable.id'; + + [false, true].forEach((showTopN) => { + [value, null].forEach((maybeValue) => { + [draggableId, undefined].forEach((maybeDraggableId) => { + const shouldRender = !showTopN && maybeValue != null && maybeDraggableId != null; + const assertion = shouldRender ? 'should render' : 'should NOT render'; + + test(`it ${assertion} the 'Add to timeline investigation' button when showTopN is ${showTopN}, value is ${maybeValue}, and a draggableId is ${maybeDraggableId}`, () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="add-to-timeline"]').first().exists()).toBe( + shouldRender + ); + }); + }); + }); + }); + + test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', () => { + const wrapper = mount( + + + + + + ); + + // The following "startDragToTimeline" function returned by our mock + // useAddToTimeline hook is called when the user clicks the + // Add to timeline investigation action: + const startDragToTimeline = useAddToTimeline({ + draggableId, + fieldName: aggregatableStringField, + }); + + wrapper.find('[data-test-subj="add-to-timeline"]').first().simulate('click'); + wrapper.update(); + + expect(startDragToTimeline).toHaveBeenCalled(); + }); + }); + + describe('Top N', () => { + test(`it renders the 'Show top field' button when showTopN is false and an aggregatable string field is provided`, async () => { + const aggregatableStringField = 'cloud.account.id'; + const wrapper = mount( + + + + + + ); + + await wait(); // https://github.com/apollographql/react-apollo/issues/1711 + wrapper.update(); + + expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); + }); + + test(`it renders the 'Show top field' button when showTopN is false and a whitelisted signal field is provided`, async () => { + const whitelistedField = 'signal.rule.name'; + const wrapper = mount( + + + + + + ); + + await wait(); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); + }); + + test(`it does NOT render the 'Show top field' button when showTopN is false and a field not known to BrowserFields is provided`, async () => { + const notKnownToBrowserFields = 'unknown.field'; + const wrapper = mount( + + + + + + ); + + await wait(); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); + }); + + test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, async () => { + const whitelistedField = 'signal.rule.name'; + const wrapper = mount( + + + + + + ); + + await wait(); + wrapper.update(); + + wrapper.find('[data-test-subj="show-top-field"]').first().simulate('click'); + wrapper.update(); + + expect(toggleTopN).toBeCalled(); + }); + + test(`it does NOT render the Top N histogram when when showTopN is false`, async () => { + const whitelistedField = 'signal.rule.name'; + const wrapper = mount( + + + + + + ); + + await wait(); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="eventsByDatasetOverviewPanel"]').first().exists()).toBe( + false + ); + }); + + test(`it does NOT render the 'Show top field' button when showTopN is true`, async () => { + const whitelistedField = 'signal.rule.name'; + const wrapper = mount( + + + + + + ); + + await wait(); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); + }); + + test(`it renders the Top N histogram when when showTopN is true`, async () => { + const whitelistedField = 'signal.rule.name'; + const wrapper = mount( + + + + + + ); + + await wait(); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="eventsByDatasetOverview-uuid.v4()Panel"]').first().exists() + ).toBe(true); + }); + }); + + describe('Copy to Clipboard', () => { + test(`it renders the 'Copy to Clipboard' button when showTopN is false`, () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="copy-to-clipboard"]`).first().exists()).toBe(true); + }); + + test(`it does NOT render the 'Copy to Clipboard' button when showTopN is true`, () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="copy-to-clipboard"]`).first().exists()).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx similarity index 87% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx index a0546dc64113c..998d18291f638 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx @@ -13,17 +13,18 @@ import { useAddToTimeline } from '../../hooks/use_add_to_timeline'; import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard'; import { useKibana } from '../../lib/kibana'; import { createFilter } from '../add_filter_to_global_search_bar'; -import { useTimelineContext } from '../../../timelines/components/timeline/timeline_context'; import { StatefulTopN } from '../top_n'; import { allowTopN } from './helpers'; import * as i18n from './translations'; +import { useManageTimeline } from '../../../timelines/components/manage_timeline'; interface Props { draggableId?: DraggableId; field: string; onFilterAdded?: () => void; showTopN: boolean; + timelineId?: string; toggleTopN: () => void; value?: string[] | string | null; } @@ -33,20 +34,27 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ field, onFilterAdded, showTopN, + timelineId, toggleTopN, value, }) => { const startDragToTimeline = useAddToTimeline({ draggableId, fieldName: field }); const kibana = useKibana(); - const { filterManager: timelineFilterManager } = useTimelineContext(); - const filterManager = useMemo(() => kibana.services.data.query.filterManager, [ + const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [ kibana.services.data.query.filterManager, ]); - + const { getTimelineFilterManager } = useManageTimeline(); + const filterManager = useMemo( + () => + timelineId + ? getTimelineFilterManager(timelineId) ?? filterManagerBackup + : filterManagerBackup, + [timelineId, getTimelineFilterManager, filterManagerBackup] + ); const filterForValue = useCallback(() => { const filter = value?.length === 0 ? createFilter(field, undefined) : createFilter(field, value); - const activeFilterManager = timelineFilterManager ?? filterManager; + const activeFilterManager = filterManager; if (activeFilterManager != null) { activeFilterManager.addFilters(filter); @@ -55,12 +63,12 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ onFilterAdded(); } } - }, [field, value, timelineFilterManager, filterManager, onFilterAdded]); + }, [field, value, filterManager, onFilterAdded]); const filterOutValue = useCallback(() => { const filter = value?.length === 0 ? createFilter(field, null, false) : createFilter(field, value, true); - const activeFilterManager = timelineFilterManager ?? filterManager; + const activeFilterManager = filterManager; if (activeFilterManager != null) { activeFilterManager.addFilters(filter); @@ -69,7 +77,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ onFilterAdded(); } } - }, [field, value, timelineFilterManager, filterManager, onFilterAdded]); + }, [field, value, filterManager, onFilterAdded]); return ( <> diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/droppable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/droppable_wrapper.test.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/droppable_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.tsx similarity index 97% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/droppable_wrapper.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.tsx index a81954f57564e..30c54a8b44a75 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/droppable_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.tsx @@ -25,10 +25,10 @@ const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string height: ${({ height }) => height}; .flyout-overlay { .euiPanel { - background-color: ${props => props.theme.eui.euiFormBackgroundColor}; + background-color: ${(props) => props.theme.eui.euiFormBackgroundColor}; } } - ${props => + ${(props) => props.isDraggingOver ? ` .drop-and-provider-timeline { diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.test.ts new file mode 100644 index 0000000000000..afdfde6e08224 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.test.ts @@ -0,0 +1,1000 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { omit } from 'lodash/fp'; +import { DropResult } from 'react-beautiful-dnd'; + +import { IdToDataProvider } from '../../store/drag_and_drop/model'; + +import { + addProviderToTimeline, + allowTopN, + destinationIsTimelineButton, + destinationIsTimelineColumns, + destinationIsTimelineProviders, + draggableContentPrefix, + draggableFieldPrefix, + draggableIdPrefix, + draggableIsContent, + draggableIsField, + droppableIdPrefix, + droppableTimelineColumnsPrefix, + droppableTimelineFlyoutButtonPrefix, + droppableTimelineProvidersPrefix, + escapeDataProviderId, + escapeFieldId, + fieldWasDroppedOnTimelineColumns, + getDraggableFieldId, + getDraggableId, + getDroppableId, + getFieldIdFromDraggable, + getProviderIdFromDraggable, + getTimelineIdFromColumnDroppableId, + getTimelineProviderDraggableId, + getTimelineProviderDroppableId, + providerWasDroppedOnTimeline, + reasonIsDrop, + sourceAndDestinationAreSameTimelineProviders, + sourceIsContent, + unEscapeFieldId, + userIsReArrangingProviders, +} from './helpers'; + +const DROPPABLE_ID_TIMELINE_PROVIDERS = `${droppableTimelineProvidersPrefix}timeline`; + +/** a sample droppable id that uniquely identifies a timeline's columns */ +const DROPPABLE_ID_TIMELINE_COLUMNS = `${droppableTimelineColumnsPrefix}timeline-1`; + +describe('helpers', () => { + describe('#getDraggableId', () => { + test('it returns the expected id', () => { + const id = getDraggableId('dataProvider1234'); + const expected = `${draggableContentPrefix}dataProvider1234`; + + expect(id).toEqual(expected); + }); + }); + + describe('#getDraggableFieldId', () => { + test('it returns the expected id', () => { + const id = getDraggableFieldId({ contextId: 'test.context.id', fieldId: 'event.action' }); + const expected = `${draggableFieldPrefix}test_context_id.event!!!DOT!!!action`; + + expect(id).toEqual(expected); + }); + }); + + describe('#getDroppableId', () => { + test('it returns the expected id', () => { + const id = getDroppableId('a-visualization'); + const expected = `${droppableIdPrefix}.content.a-visualization`; + + expect(id).toEqual(expected); + }); + }); + + describe('#sourceIsContent', () => { + test('it returns returns true when the source is content', () => { + expect( + sourceIsContent({ + destination: { droppableId: `${droppableIdPrefix}.timelineProviders.timeline`, index: 0 }, + draggableId: getDraggableId('2119990039033485'), + reason: 'DROP', + source: { index: 0, droppableId: getDroppableId('2119990039033485') }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns false when the source is NOT content', () => { + expect( + sourceIsContent({ + destination: { droppableId: `${droppableIdPrefix}.timelineProviders.timeline`, index: 0 }, + draggableId: `${draggableIdPrefix}.somethingElse.2119990039033485`, + reason: 'DROP', + source: { index: 0, droppableId: `${droppableIdPrefix}.somethingElse.2119990039033485` }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#draggableIsContent', () => { + test('it returns returns true when the draggable is content', () => { + expect( + draggableIsContent({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns false when the draggable is NOT content', () => { + expect( + draggableIsContent({ + destination: undefined, + draggableId: `${draggableIdPrefix}.timeline.timeline.dataProvider.685260508808089`, + reason: 'DROP', + source: { + droppableId: getDroppableId('timeline'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#draggableIsField', () => { + test('it returns returns true when the draggable is a field', () => { + expect( + draggableIsField({ + destination: { + droppableId: 'fake.destination.droppable.id', + index: 0, + }, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns returns false when the draggable is NOT a field', () => { + expect( + draggableIsField({ + destination: { + droppableId: 'fake.destination.droppable.id', + index: 0, + }, + draggableId: getDraggableId('685260508808089'), // content, not a field + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#reasonIsDrop', () => { + test('it returns returns true when the reason is DROP', () => { + expect( + reasonIsDrop({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns false when the reason is NOT DROP', () => { + expect( + reasonIsDrop({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'CANCEL', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#destinationIsTimelineProviders', () => { + test('it returns returns true when the destination is timelineProviders', () => { + expect( + destinationIsTimelineProviders({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns false when the destination is undefined', () => { + expect( + destinationIsTimelineProviders({ + destination: undefined, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('timeline'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns false when the destination is NOT timelineProviders', () => { + expect( + destinationIsTimelineProviders({ + destination: { + droppableId: `${droppableIdPrefix}.somewhere.else`, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#destinationIsTimelineColumns', () => { + test('it returns returns true when the destination is the timeline columns', () => { + expect( + destinationIsTimelineColumns({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_COLUMNS, + index: 0, + }, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns returns false when the destination is undefined', () => { + expect( + destinationIsTimelineColumns({ + destination: undefined, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns returns false when the destination is NOT the timeline columns', () => { + expect( + destinationIsTimelineColumns({ + destination: { + droppableId: `${droppableIdPrefix}.somewhere.else`, + index: 0, + }, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#destinationIsTimelineButton', () => { + test('it returns returns true when the destination is a flyout button', () => { + expect( + destinationIsTimelineButton({ + destination: { + droppableId: `${droppableTimelineFlyoutButtonPrefix}.timeline`, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns false when the destination is undefined', () => { + expect( + destinationIsTimelineButton({ + destination: undefined, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns false when the destination is NOT a flyout button', () => { + expect( + destinationIsTimelineButton({ + destination: { + droppableId: `${droppableIdPrefix}.somewhere.else`, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#getProviderIdFromDraggable', () => { + test('it returns the expected id', () => { + const id = getProviderIdFromDraggable({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + draggableId: getDraggableId('2119990039033485'), + reason: 'DROP', + source: { + droppableId: getDroppableId('2119990039033485'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }); + const expected = '2119990039033485'; + + expect(id).toEqual(expected); + }); + }); + + describe('#getFieldIdFromDraggable', () => { + test('it returns the expected id', () => { + const id = getFieldIdFromDraggable({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_COLUMNS, + index: 0, + }, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }); + const expected = 'event.action'; + + expect(id).toEqual(expected); + }); + }); + + describe('#providerWasDroppedOnTimeline', () => { + test('it returns returns true when a provider was dropped on the timeline', () => { + expect( + providerWasDroppedOnTimeline({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + draggableId: getDraggableId('2119990039033485'), + reason: 'DROP', + source: { + droppableId: getDroppableId('2119990039033485'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns false when the reason is NOT DROP', () => { + expect( + providerWasDroppedOnTimeline({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, + index: 0, + }, + draggableId: getDraggableId('2119990039033485'), + reason: 'CANCEL', + source: { + droppableId: getDroppableId('2119990039033485'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns false when the draggable is NOT content', () => { + expect( + providerWasDroppedOnTimeline({ + destination: undefined, + draggableId: `${draggableIdPrefix}.timeline.timeline.dataProvider.685260508808089`, + reason: 'DROP', + source: { + droppableId: getDroppableId('timeline'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns false when the the source is NOT content', () => { + expect( + providerWasDroppedOnTimeline({ + destination: { droppableId: DROPPABLE_ID_TIMELINE_PROVIDERS, index: 0 }, + draggableId: `${draggableIdPrefix}.somethingElse.2119990039033485`, + reason: 'DROP', + source: { index: 0, droppableId: `${droppableIdPrefix}.somethingElse.2119990039033485` }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns false when the the destination is NOT timeline providers', () => { + expect( + providerWasDroppedOnTimeline({ + destination: { + droppableId: `${droppableIdPrefix}.somewhere.else`, + index: 0, + }, + draggableId: getDraggableId('685260508808089'), + reason: 'DROP', + source: { + droppableId: getDroppableId('685260508808089'), + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#fieldWasDroppedOnTimelineColumns', () => { + test('it returns true when a field was dropped on the timeline columns', () => { + expect( + fieldWasDroppedOnTimelineColumns({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_COLUMNS, + index: 0, + }, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(true); + }); + + test('it returns returns false when the reason is NOT DROP', () => { + expect( + fieldWasDroppedOnTimelineColumns({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_COLUMNS, + index: 0, + }, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'CANCEL', // the reason is NOT drop + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns false when the draggable is NOT a field', () => { + expect( + fieldWasDroppedOnTimelineColumns({ + destination: { + droppableId: DROPPABLE_ID_TIMELINE_COLUMNS, + index: 0, + }, + draggableId: getDraggableId('non.field.content'), // the draggable is not a field + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + + test('it returns returns false when the the destination is NOT timeline columns', () => { + expect( + fieldWasDroppedOnTimelineColumns({ + destination: { + droppableId: `${droppableIdPrefix}.somewhere.else`, + index: 0, + }, + draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), + reason: 'DROP', + source: { + droppableId: 'fake.source.droppable.id', + index: 0, + }, + type: 'DEFAULT', + mode: 'FLUID', + }) + ).toEqual(false); + }); + }); + + describe('#escapeDataProviderId', () => { + test('it should escape dotted notation', () => { + const escaped = escapeDataProviderId('hello.how.are.you'); + expect(escaped).toEqual('hello_how_are_you'); + }); + + test('it should not escape a string without dotted notation', () => { + const escaped = escapeDataProviderId('hello how are you?'); + expect(escaped).toEqual('hello how are you?'); + }); + }); + + describe('#escapeFieldId', () => { + test('it should escape "."s in a field name', () => { + const escaped = escapeFieldId('hello.how.are.you'); + expect(escaped).toEqual('hello!!!DOT!!!how!!!DOT!!!are!!!DOT!!!you'); + }); + + test('it should NOT escape a string when the field name has no "."s', () => { + const escaped = escapeFieldId('hello!!!DOT!!!how!!!DOT!!!are!!!DOT!!!you?'); + expect(escaped).toEqual('hello!!!DOT!!!how!!!DOT!!!are!!!DOT!!!you?'); + }); + }); + + describe('#unEscapeFieldId', () => { + test('it should un-escape a field name containing !!!DOT!!! escape sequences', () => { + const escaped = unEscapeFieldId('hello!!!DOT!!!how!!!DOT!!!are!!!DOT!!!you'); + expect(escaped).toEqual('hello.how.are.you'); + }); + + test('it should NOT escape a string when the field name has no !!!DOT!!! escape characters', () => { + const escaped = unEscapeFieldId('hello.how.are.you?'); + expect(escaped).toEqual('hello.how.are.you?'); + }); + }); + + describe('#allowTopN', () => { + const aggregatableAllowedType = { + category: 'cloud', + description: + 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', + example: '666777888999', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'cloud.account.id', + searchable: true, + type: 'string', + aggregatable: true, + format: '', + }; + + test('it returns true for an aggregatable field that is an allowed type', () => { + expect( + allowTopN({ + browserField: aggregatableAllowedType, + fieldName: aggregatableAllowedType.name, + }) + ).toBe(true); + }); + + test('it returns true for a whitelisted non-BrowserField', () => { + expect( + allowTopN({ + browserField: undefined, + fieldName: 'signal.rule.name', + }) + ).toBe(true); + }); + + test('it returns false for a NON-aggregatable field that is an allowed type', () => { + const nonAggregatableAllowedType = { + ...aggregatableAllowedType, + aggregatable: false, + }; + + expect( + allowTopN({ + browserField: nonAggregatableAllowedType, + fieldName: nonAggregatableAllowedType.name, + }) + ).toBe(false); + }); + + test('it returns false for a aggregatable field that is NOT an allowed type', () => { + const aggregatableNotAllowedType = { + ...aggregatableAllowedType, + type: 'not-an-allowed-type', + }; + + expect( + allowTopN({ + browserField: aggregatableNotAllowedType, + fieldName: aggregatableNotAllowedType.name, + }) + ).toBe(false); + }); + + test('it returns false if the BrowserField is missing the aggregatable property', () => { + const missingAggregatable = omit('aggregatable', aggregatableAllowedType); + + expect( + allowTopN({ + browserField: missingAggregatable, + fieldName: missingAggregatable.name, + }) + ).toBe(false); + }); + + test('it returns false if the BrowserField is missing the type property', () => { + const missingType = omit('type', aggregatableAllowedType); + + expect( + allowTopN({ + browserField: missingType, + fieldName: missingType.name, + }) + ).toBe(false); + }); + + test('it returns false for a non-whitelisted field when a BrowserField is not provided', () => { + expect( + allowTopN({ + browserField: undefined, + fieldName: 'non-whitelisted', + }) + ).toBe(false); + }); + }); + + describe('getTimelineProviderDroppableId', () => { + test('it returns the expected id', () => { + expect( + getTimelineProviderDroppableId({ + groupIndex: 1234, + timelineId: 'i-hope-you-had-the-time-of-your-life', + }) + ).toEqual('droppableId.timelineProviders.i-hope-you-had-the-time-of-your-life.group.1234'); + }); + }); + + describe('getTimelineProviderDraggableId', () => { + test('it returns the expected id', () => { + const dataProviderId = + 'port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828'; + + expect( + getTimelineProviderDraggableId({ + dataProviderId, + groupIndex: 1234, + timelineId: 'time-to-make-the-doughnuts', + }) + ).toEqual( + `draggableId.timelineProviders.time-to-make-the-doughnuts.group.1234.${dataProviderId}` + ); + }); + }); + + describe('sourceAndDestinationAreSameTimelineProviders', () => { + test('it returns true when the source and destination droppable IDs exactly match', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.timelineProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.0' }, + type: 'DEFAULT', + }; + + expect(sourceAndDestinationAreSameTimelineProviders(result)).toBe(true); + }); + + test('it returns true when the source and destination droppable IDs are the same timeline, but different groups', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.timelineProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.1' }, + type: 'DEFAULT', + }; + + expect(sourceAndDestinationAreSameTimelineProviders(result)).toBe(true); + }); + + test('it returns false when destination is undefined', () => { + const result: DropResult = { + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.1' }, + type: 'DEFAULT', + }; + + expect(sourceAndDestinationAreSameTimelineProviders(result)).toBe(false); + }); + + test('it returns false when the source and destination droppable IDs are for different timelines', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.timelineProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-2.group.0' }, + type: 'DEFAULT', + }; + + expect(sourceAndDestinationAreSameTimelineProviders(result)).toBe(false); + }); + + test('it returns false when the destination is NOT timeline providers', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.otherProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.0' }, + type: 'DEFAULT', + }; + + expect(sourceAndDestinationAreSameTimelineProviders(result)).toBe(false); + }); + + test('it returns false when the source is NOT timeline providers', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.timelineProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.otherProviders.timeline-1.group.0' }, + type: 'DEFAULT', + }; + + expect(sourceAndDestinationAreSameTimelineProviders(result)).toBe(false); + }); + }); + + describe('userIsReArrangingProviders', () => { + test('it returns true when reason IS DROP and source + destination are the SAME timeline providers', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.timelineProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.0' }, + type: 'DEFAULT', + }; + + expect(userIsReArrangingProviders(result)).toBe(true); + }); + + test('it returns false when reason IS DROP, but source + destination are NOT the same timeline providers', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.otherProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.0' }, + type: 'DEFAULT', + }; + + expect(userIsReArrangingProviders(result)).toBe(false); + }); + + test('it returns false when the reason is NOT DROP and source + destination are the SAME timeline providers', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.timelineProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'CANCEL', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.0' }, + type: 'DEFAULT', + }; + + expect(userIsReArrangingProviders(result)).toBe(false); + }); + + test('it returns false when the reason is NOT DROP and source + destination are NOT the same timeline providers', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.otherProviders.timeline-1.group.0', index: 1 }, + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'CANCEL', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.0' }, + type: 'DEFAULT', + }; + + expect(userIsReArrangingProviders(result)).toBe(false); + }); + + test('it returns false when reason IS DROP, but destination is undefined', () => { + const result: DropResult = { + draggableId: + 'draggableId.timelineProviders.timeline-1.group.0.port-default-draggable-netflow-renderer-timeline-1-Ib4zD3IBbNV0npT21btr-Ib4zD3IBbNV0npT21btr-source_port-57828', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.timelineProviders.timeline-1.group.1' }, + type: 'DEFAULT', + }; + + expect(userIsReArrangingProviders(result)).toBe(false); + }); + }); + + describe('addProviderToTimeline', () => { + const result: DropResult = { + destination: { droppableId: 'droppableId.timelineProviders.timeline-1', index: 0 }, + draggableId: 'draggableId.content.hosts-table-hostName-ENDPOINT-W-0-01', + mode: 'FLUID', + reason: 'DROP', + source: { index: 0, droppableId: 'droppableId.content.hosts-table-hostName-ENDPOINT-W-0-01' }, + type: 'DEFAULT', + }; + + test('it dispatches the expected UPDATE_PROVIDERS action when the provider to add exists in the `dataProviders` collection of `id -> `DataProvider`', () => { + const dispatch = jest.fn(); + const onAddedToTimeline = jest.fn(); + const dataProviders: IdToDataProvider = { + 'hosts-table-hostName-ENDPOINT-W-0-01': { + and: [], + enabled: true, + excluded: false, + id: 'hosts-table-hostName-ENDPOINT-W-0-01', + kqlQuery: '', + name: 'ENDPOINT-W-0-01', + queryMatch: { field: 'host.name', value: 'ENDPOINT-W-0-01', operator: ':' }, + }, + }; + + addProviderToTimeline({ + activeTimelineDataProviders: [], + dataProviders, + dispatch, + result, + timelineId: 'timeline-1', + onAddedToTimeline, + }); + + expect(dispatch).toBeCalledWith({ + payload: { + id: 'timeline-1', + providers: [ + { + and: [], + enabled: true, + excluded: false, + id: 'hosts-table-hostName-ENDPOINT-W-0-01', + kqlQuery: '', + name: 'ENDPOINT-W-0-01', + queryMatch: { field: 'host.name', operator: ':', value: 'ENDPOINT-W-0-01' }, + }, + ], + }, + type: 'x-pack/security_solution/local/timeline/UPDATE_PROVIDERS', + }); + }); + + test('it dispatches the expected NO_PROVIDER_FOUND action when the provider to add does NOT exist in the `dataProviders` collection of `id -> `DataProvider`', () => { + const dispatch = jest.fn(); + const onAddedToTimeline = jest.fn(); + + addProviderToTimeline({ + activeTimelineDataProviders: [], + dataProviders: {}, // <-- the specified data provider ID does not exist in this empty collection + dispatch, + result, + timelineId: 'timeline-1', + onAddedToTimeline, + }); + + expect(dispatch).toBeCalledWith({ + payload: { + id: 'hosts-table-hostName-ENDPOINT-W-0-01', + }, + type: 'x-pack/security_solution/local/drag_and_drop/NO_PROVIDER_FOUND', + }); + }); + }); + + describe('getTimelineIdFromColumnDroppableId', () => { + test('it returns the expected timelineId from a column droppableId', () => { + expect(getTimelineIdFromColumnDroppableId(DROPPABLE_ID_TIMELINE_COLUMNS)).toEqual( + 'timeline-1' + ); + }); + + test('it returns an empty string when the droppableId is an empty string', () => { + expect(getTimelineIdFromColumnDroppableId('')).toEqual(''); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts new file mode 100644 index 0000000000000..4fb4e5d30ca7a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts @@ -0,0 +1,337 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isString } from 'lodash/fp'; +import { DropResult } from 'react-beautiful-dnd'; +import { Dispatch } from 'redux'; +import { ActionCreator } from 'typescript-fsa'; + +import { BrowserField, BrowserFields, getAllFieldsByName } from '../../containers/source'; +import { dragAndDropActions } from '../../store/actions'; +import { IdToDataProvider } from '../../store/drag_and_drop/model'; +import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; +import { timelineActions } from '../../../timelines/store/timeline'; +import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; +import { addContentToTimeline } from '../../../timelines/components/timeline/data_providers/helpers'; +import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; + +export const draggableIdPrefix = 'draggableId'; + +export const droppableIdPrefix = 'droppableId'; + +export const draggableContentPrefix = `${draggableIdPrefix}.content.`; + +export const draggableTimelineProvidersPrefix = `${draggableIdPrefix}.timelineProviders.`; + +export const draggableFieldPrefix = `${draggableIdPrefix}.field.`; + +export const droppableContentPrefix = `${droppableIdPrefix}.content.`; + +export const droppableFieldPrefix = `${droppableIdPrefix}.field.`; + +export const droppableTimelineProvidersPrefix = `${droppableIdPrefix}.timelineProviders.`; + +export const droppableTimelineColumnsPrefix = `${droppableIdPrefix}.timelineColumns.`; + +export const droppableTimelineFlyoutButtonPrefix = `${droppableIdPrefix}.flyoutButton.`; + +export const getDraggableId = (dataProviderId: string): string => + `${draggableContentPrefix}${dataProviderId}`; + +export const getDraggableFieldId = ({ + contextId, + fieldId, +}: { + contextId: string; + fieldId: string; +}): string => `${draggableFieldPrefix}${escapeContextId(contextId)}.${escapeFieldId(fieldId)}`; + +export const getTimelineProviderDroppableId = ({ + groupIndex, + timelineId, +}: { + groupIndex: number; + timelineId: string; +}): string => `${droppableTimelineProvidersPrefix}${timelineId}.group.${groupIndex}`; + +export const getTimelineProviderDraggableId = ({ + dataProviderId, + groupIndex, + timelineId, +}: { + dataProviderId: string; + groupIndex: number; + timelineId: string; +}): string => + `${draggableTimelineProvidersPrefix}${timelineId}.group.${groupIndex}.${dataProviderId}`; + +export const getDroppableId = (visualizationPlaceholderId: string): string => + `${droppableContentPrefix}${visualizationPlaceholderId}`; + +export const sourceIsContent = (result: DropResult): boolean => + result.source.droppableId.startsWith(droppableContentPrefix); + +export const sourceAndDestinationAreSameTimelineProviders = (result: DropResult): boolean => { + const regex = /^droppableId\.timelineProviders\.(\S+)\./; + const sourceMatches = result.source.droppableId.match(regex) ?? []; + const destinationMatches = result.destination?.droppableId.match(regex) ?? []; + + return ( + sourceMatches.length >= 2 && + destinationMatches.length >= 2 && + sourceMatches[1] === destinationMatches[1] + ); +}; + +export const draggableIsContent = (result: DropResult | { draggableId: string }): boolean => + result.draggableId.startsWith(draggableContentPrefix); + +export const draggableIsField = (result: DropResult | { draggableId: string }): boolean => + result.draggableId.startsWith(draggableFieldPrefix); + +export const reasonIsDrop = (result: DropResult): boolean => result.reason === 'DROP'; + +export const destinationIsTimelineProviders = (result: DropResult): boolean => + result.destination != null && + result.destination.droppableId.startsWith(droppableTimelineProvidersPrefix); + +export const destinationIsTimelineColumns = (result: DropResult): boolean => + result.destination != null && + result.destination.droppableId.startsWith(droppableTimelineColumnsPrefix); + +export const destinationIsTimelineButton = (result: DropResult): boolean => + result.destination != null && + result.destination.droppableId.startsWith(droppableTimelineFlyoutButtonPrefix); + +export const getProviderIdFromDraggable = (result: DropResult): string => + result.draggableId.substring(result.draggableId.lastIndexOf('.') + 1); + +export const getFieldIdFromDraggable = (result: DropResult): string => + unEscapeFieldId(result.draggableId.substring(result.draggableId.lastIndexOf('.') + 1)); + +export const escapeDataProviderId = (path: string) => path.replace(/\./g, '_'); + +export const escapeContextId = (path: string) => path.replace(/\./g, '_'); + +export const escapeFieldId = (path: string) => path.replace(/\./g, '!!!DOT!!!'); + +export const unEscapeFieldId = (path: string) => path.replace(/!!!DOT!!!/g, '.'); + +export const providerWasDroppedOnTimeline = (result: DropResult): boolean => + reasonIsDrop(result) && + draggableIsContent(result) && + sourceIsContent(result) && + destinationIsTimelineProviders(result); + +export const userIsReArrangingProviders = (result: DropResult): boolean => + reasonIsDrop(result) && sourceAndDestinationAreSameTimelineProviders(result); + +export const fieldWasDroppedOnTimelineColumns = (result: DropResult): boolean => + reasonIsDrop(result) && draggableIsField(result) && destinationIsTimelineColumns(result); + +interface AddProviderToTimelineParams { + activeTimelineDataProviders: DataProvider[]; + dataProviders: IdToDataProvider; + dispatch: Dispatch; + noProviderFound?: ActionCreator<{ + id: string; + }>; + onAddedToTimeline: (fieldOrValue: string) => void; + result: DropResult; + timelineId: string; +} + +interface AddFieldToTimelineColumnsParams { + upsertColumn?: ActionCreator<{ + column: ColumnHeaderOptions; + id: string; + index: number; + }>; + browserFields: BrowserFields; + dispatch: Dispatch; + result: DropResult; + timelineId: string; +} + +export const addProviderToTimeline = ({ + activeTimelineDataProviders, + dataProviders, + dispatch, + result, + timelineId, + noProviderFound = dragAndDropActions.noProviderFound, + onAddedToTimeline, +}: AddProviderToTimelineParams): void => { + const providerId = getProviderIdFromDraggable(result); + const providerToAdd = dataProviders[providerId]; + + if (providerToAdd) { + addContentToTimeline({ + dataProviders: activeTimelineDataProviders, + destination: result.destination, + dispatch, + onAddedToTimeline, + providerToAdd, + timelineId, + }); + } else { + dispatch(noProviderFound({ id: providerId })); + } +}; + +export const addFieldToTimelineColumns = ({ + upsertColumn = timelineActions.upsertColumn, + browserFields, + dispatch, + result, + timelineId, +}: AddFieldToTimelineColumnsParams): void => { + const fieldId = getFieldIdFromDraggable(result); + const allColumns = getAllFieldsByName(browserFields); + const column = allColumns[fieldId]; + + if (column != null) { + dispatch( + upsertColumn({ + column: { + category: column.category, + columnHeaderType: 'not-filtered', + description: isString(column.description) ? column.description : undefined, + example: isString(column.example) ? column.example : undefined, + id: fieldId, + type: column.type, + aggregatable: column.aggregatable, + width: DEFAULT_COLUMN_MIN_WIDTH, + }, + id: timelineId, + index: result.destination != null ? result.destination.index : 0, + }) + ); + } else { + // create a column definition, because it doesn't exist in the browserFields: + dispatch( + upsertColumn({ + column: { + columnHeaderType: 'not-filtered', + id: fieldId, + width: DEFAULT_COLUMN_MIN_WIDTH, + }, + id: timelineId, + index: result.destination != null ? result.destination.index : 0, + }) + ); + } +}; + +/** + * Prevents fields from being dragged or dropped to any area other than column + * header drop zone in the timeline + */ +export const DRAG_TYPE_FIELD = 'drag-type-field'; + +/** This class is added to the document body while dragging */ +export const IS_DRAGGING_CLASS_NAME = 'is-dragging'; + +/** This class is added to the document body while timeline field dragging */ +export const IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME = 'is-timeline-field-dragging'; + +export const allowTopN = ({ + browserField, + fieldName, +}: { + browserField: Partial | undefined; + fieldName: string; +}): boolean => { + const isAggregatable = browserField?.aggregatable ?? false; + const fieldType = browserField?.type ?? ''; + const isAllowedType = [ + 'boolean', + 'geo-point', + 'geo-shape', + 'ip', + 'keyword', + 'number', + 'numeric', + 'string', + ].includes(fieldType); + + // TODO: remove this explicit whitelist when the ECS documentation includes alerts + const isWhitelistedNonBrowserField = [ + 'signal.ancestors.depth', + 'signal.ancestors.id', + 'signal.ancestors.rule', + 'signal.ancestors.type', + 'signal.original_event.action', + 'signal.original_event.category', + 'signal.original_event.code', + 'signal.original_event.created', + 'signal.original_event.dataset', + 'signal.original_event.duration', + 'signal.original_event.end', + 'signal.original_event.hash', + 'signal.original_event.id', + 'signal.original_event.kind', + 'signal.original_event.module', + 'signal.original_event.original', + 'signal.original_event.outcome', + 'signal.original_event.provider', + 'signal.original_event.risk_score', + 'signal.original_event.risk_score_norm', + 'signal.original_event.sequence', + 'signal.original_event.severity', + 'signal.original_event.start', + 'signal.original_event.timezone', + 'signal.original_event.type', + 'signal.original_time', + 'signal.parent.depth', + 'signal.parent.id', + 'signal.parent.index', + 'signal.parent.rule', + 'signal.parent.type', + 'signal.rule.created_by', + 'signal.rule.description', + 'signal.rule.enabled', + 'signal.rule.false_positives', + 'signal.rule.filters', + 'signal.rule.from', + 'signal.rule.id', + 'signal.rule.immutable', + 'signal.rule.index', + 'signal.rule.interval', + 'signal.rule.language', + 'signal.rule.max_signals', + 'signal.rule.name', + 'signal.rule.note', + 'signal.rule.output_index', + 'signal.rule.query', + 'signal.rule.references', + 'signal.rule.risk_score', + 'signal.rule.rule_id', + 'signal.rule.saved_id', + 'signal.rule.severity', + 'signal.rule.size', + 'signal.rule.tags', + 'signal.rule.threat', + 'signal.rule.threat.tactic.id', + 'signal.rule.threat.tactic.name', + 'signal.rule.threat.tactic.reference', + 'signal.rule.threat.technique.id', + 'signal.rule.threat.technique.name', + 'signal.rule.threat.technique.reference', + 'signal.rule.timeline_id', + 'signal.rule.timeline_title', + 'signal.rule.to', + 'signal.rule.type', + 'signal.rule.updated_by', + 'signal.rule.version', + 'signal.status', + ].includes(fieldName); + + return isWhitelistedNonBrowserField || (isAggregatable && isAllowedType); +}; + +export const getTimelineIdFromColumnDroppableId = (droppableId: string) => + droppableId.slice(droppableId.lastIndexOf('.') + 1); diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/provider_container.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/drag_and_drop/provider_container.tsx rename to x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/translations.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/translations.ts new file mode 100644 index 0000000000000..574b2c190e1db --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/translations.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ADD_TO_TIMELINE = i18n.translate('xpack.securitySolution.dragAndDrop.addToTimeline', { + defaultMessage: 'Add to timeline investigation', +}); + +export const COPY_TO_CLIPBOARD = i18n.translate( + 'xpack.securitySolution.dragAndDrop.copyToClipboardTooltip', + { + defaultMessage: 'Copy to Clipboard', + } +); + +export const FIELD = i18n.translate('xpack.securitySolution.dragAndDrop.fieldLabel', { + defaultMessage: 'Field', +}); + +export const FILTER_FOR_VALUE = i18n.translate( + 'xpack.securitySolution.dragAndDrop.filterForValueHoverAction', + { + defaultMessage: 'Filter for value', + } +); + +export const FILTER_OUT_VALUE = i18n.translate( + 'xpack.securitySolution.dragAndDrop.filterOutValueHoverAction', + { + defaultMessage: 'Filter out value', + } +); + +export const CLOSE = i18n.translate('xpack.securitySolution.dragAndDrop.closeButtonLabel', { + defaultMessage: 'Close', +}); + +export const SHOW_TOP = (fieldName: string) => + i18n.translate('xpack.securitySolution.overview.showTopTooltip', { + values: { fieldName }, + defaultMessage: `Show top {fieldName}`, + }); diff --git a/x-pack/plugins/siem/public/common/components/draggables/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/draggables/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/draggables/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/draggables/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/draggables/field_badge/index.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/field_badge/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/draggables/field_badge/index.tsx rename to x-pack/plugins/security_solution/public/common/components/draggables/field_badge/index.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/field_badge/translations.ts b/x-pack/plugins/security_solution/public/common/components/draggables/field_badge/translations.ts new file mode 100644 index 0000000000000..8d378c9a05a70 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/draggables/field_badge/translations.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const CATEGORY = i18n.translate('xpack.securitySolution.draggables.field.categoryLabel', { + defaultMessage: 'Category', +}); + +export const COPY_TO_CLIPBOARD = i18n.translate( + 'xpack.securitySolution.eventDetails.copyToClipboardTooltip', + { + defaultMessage: 'Copy to Clipboard', + } +); + +export const FIELD = i18n.translate('xpack.securitySolution.draggables.field.fieldLabel', { + defaultMessage: 'Field', +}); + +export const TYPE = i18n.translate('xpack.securitySolution.draggables.field.typeLabel', { + defaultMessage: 'Type', +}); + +export const VIEW_CATEGORY = i18n.translate( + 'xpack.securitySolution.draggables.field.viewCategoryTooltip', + { + defaultMessage: 'View Category', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx new file mode 100644 index 0000000000000..3d80a2605418e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx @@ -0,0 +1,317 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import { getEmptyString } from '../empty_value'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +import { + DefaultDraggable, + DraggableBadge, + getDefaultWhenTooltipIsUnspecified, + tooltipContentIsExplicitlyNull, +} from '.'; + +describe('draggables', () => { + const mount = useMountAppended(); + + describe('rendering', () => { + test('it renders the default DefaultDraggable', () => { + const wrapper = shallow( + + {'A child of this'} + + ); + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders the default Badge', () => { + const wrapper = shallow( + + {'A child of this'} + + ); + expect(wrapper).toMatchSnapshot(); + }); + }); + + describe('#tooltipContentIsExplicitlyNull', () => { + test('returns false if a string is provided for the tooltip', () => { + expect(tooltipContentIsExplicitlyNull('bob')).toBe(false); + }); + + test('returns false if the tooltip is undefined', () => { + expect(tooltipContentIsExplicitlyNull(undefined)).toBe(false); + }); + + test('returns false if the tooltip is a ReactNode', () => { + expect(tooltipContentIsExplicitlyNull({'be a good node'})).toBe(false); + }); + + test('returns true if the tooltip is null', () => { + expect(tooltipContentIsExplicitlyNull(null)).toBe(true); + }); + }); + + describe('#getDefaultWhenTooltipIsUnspecified', () => { + test('it returns the field (as as string) when the tooltipContent is undefined', () => { + expect(getDefaultWhenTooltipIsUnspecified({ field: 'source.bytes' })).toEqual('source.bytes'); + }); + + test('it returns the field (as as string) when the tooltipContent is null', () => { + expect( + getDefaultWhenTooltipIsUnspecified({ field: 'source.bytes', tooltipContent: null }) + ).toEqual('source.bytes'); + }); + + test('it returns the tooltipContent when a string is provided as content', () => { + expect( + getDefaultWhenTooltipIsUnspecified({ field: 'source.bytes', tooltipContent: 'a string' }) + ).toEqual('a string'); + }); + + test('it returns the tooltipContent when an element is provided as content', () => { + expect( + getDefaultWhenTooltipIsUnspecified({ + field: 'source.bytes', + tooltipContent: {'the universe'}, + }) + ).toEqual({'the universe'}); + }); + }); + + describe('DefaultDraggable', () => { + test('it works with just an id, field, and value and is some value', () => { + const wrapper = mount( + + + + ); + expect(wrapper.text()).toEqual('some value'); + }); + + test('it returns null if value is undefined', () => { + const wrapper = shallow( + + ); + expect(wrapper.isEmptyRender()).toBeTruthy(); + }); + + test('it returns null if value is null', () => { + const wrapper = shallow( + + ); + expect(wrapper.isEmptyRender()).toBeTruthy(); + }); + + test('it renders a tooltip with the field name if a tooltip is not explicitly provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="source.bytes-tooltip"]').first().props().content + ).toEqual('source.bytes'); + }); + + test('it renders the tooltipContent when a string is provided as content', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="source.bytes-tooltip"]').first().props().content + ).toEqual('default draggable string tooltip'); + }); + + test('it renders the tooltipContent when an element is provided as content', () => { + const wrapper = mount( + + {'default draggable tooltip'}} + value="a default draggable" + /> + + ); + + expect( + wrapper.find('[data-test-subj="source.bytes-tooltip"]').first().props().content + ).toEqual({'default draggable tooltip'}); + }); + + test('it does NOT render a tooltip when tooltipContent is null', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="source.bytes-tooltip"]').first().exists()).toBe(false); + }); + }); + + describe('DraggableBadge', () => { + test('it works with just an id, field, and value and is the default', () => { + const wrapper = mount( + + + + ); + expect(wrapper.text()).toEqual('some value'); + }); + + test('it returns null if value is undefined', () => { + const wrapper = shallow( + + ); + expect(wrapper.isEmptyRender()).toBeTruthy(); + }); + + test('it returns null if value is null', () => { + const wrapper = shallow( + + ); + expect(wrapper.isEmptyRender()).toBeTruthy(); + }); + + test('it returns Empty string text if value is an empty string', () => { + const wrapper = mount( + + + + ); + expect(wrapper.text()).toEqual(getEmptyString()); + }); + + test('it renders a tooltip with the field name if a tooltip is not explicitly provided', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="some-field-tooltip"]').first().props().content).toEqual( + 'some-field' + ); + }); + + test('it renders the tooltipContent when a string is provided as content', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="some-field-tooltip"]').first().props().content).toEqual( + 'draggable badge string tooltip' + ); + }); + + test('it renders the tooltipContent when an element is provided as content', () => { + const wrapper = mount( + + {'draggable badge tooltip'}} + /> + + ); + + expect(wrapper.find('[data-test-subj="some-field-tooltip"]').first().props().content).toEqual( + {'draggable badge tooltip'} + ); + }); + + test('it does NOT render a tooltip when tooltipContent is null', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="some-field-tooltip"]').first().exists()).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/siem/public/common/components/draggables/index.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/draggables/index.tsx rename to x-pack/plugins/security_solution/public/common/components/draggables/index.tsx diff --git a/x-pack/plugins/siem/public/common/components/empty_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/empty_page/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/empty_page/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/empty_page/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/empty_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/empty_page/index.test.tsx rename to x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx diff --git a/x-pack/plugins/siem/public/common/components/empty_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/empty_page/index.tsx rename to x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx diff --git a/x-pack/plugins/siem/public/common/components/empty_value/__snapshots__/empty_value.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/empty_value/__snapshots__/empty_value.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/empty_value/__snapshots__/empty_value.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/empty_value/__snapshots__/empty_value.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/empty_value/empty_value.test.tsx b/x-pack/plugins/security_solution/public/common/components/empty_value/empty_value.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/empty_value/empty_value.test.tsx rename to x-pack/plugins/security_solution/public/common/components/empty_value/empty_value.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/empty_value/index.tsx b/x-pack/plugins/security_solution/public/common/components/empty_value/index.tsx new file mode 100644 index 0000000000000..0136f5fff57e3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/empty_value/index.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get, isString } from 'lodash/fp'; +import React from 'react'; +import styled from 'styled-components'; + +import * as i18n from './translations'; + +const EmptyWrapper = styled.span` + color: ${(props) => props.theme.eui.euiColorMediumShade}; +`; + +EmptyWrapper.displayName = 'EmptyWrapper'; + +export const getEmptyValue = () => '—'; +export const getEmptyString = () => `(${i18n.EMPTY_STRING})`; + +export const getEmptyTagValue = () => {getEmptyValue()}; +export const getEmptyStringTag = () => {getEmptyString()}; + +export const defaultToEmptyTag = (item: T): JSX.Element => { + if (item == null) { + return getEmptyTagValue(); + } else if (isString(item) && item === '') { + return getEmptyStringTag(); + } else { + return <>{item}; + } +}; + +export const getOrEmptyTag = (path: string, item: unknown): JSX.Element => { + const text = get(path, item); + return getOrEmptyTagFromValue(text); +}; + +export const getOrEmptyTagFromValue = (value: string | number | null | undefined): JSX.Element => { + if (value == null) { + return getEmptyTagValue(); + } else if (value === '') { + return getEmptyStringTag(); + } else { + return <>{value}; + } +}; diff --git a/x-pack/plugins/security_solution/public/common/components/empty_value/translations.ts b/x-pack/plugins/security_solution/public/common/components/empty_value/translations.ts new file mode 100644 index 0000000000000..afcf21505103d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/empty_value/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const EMPTY_STRING = i18n.translate( + 'xpack.securitySolution.emptyString.emptyStringDescription', + { + defaultMessage: 'Empty String', + } +); diff --git a/x-pack/plugins/siem/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap similarity index 98% rename from x-pack/plugins/siem/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap index 35a42acf7e1fb..5d077dba447fa 100644 --- a/x-pack/plugins/siem/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap @@ -21,6 +21,10 @@ exports[`PageView component should display body header custom element 1`] = ` background: none; } +.c0 .endpoint-navTabs { + margin-left: 24px; +} + @@ -112,6 +116,10 @@ exports[`PageView component should display body header wrapped in EuiTitle 1`] = background: none; } +.c0 .endpoint-navTabs { + margin-left: 24px; +} + @@ -383,6 +399,10 @@ exports[`PageView component should display only header left 1`] = ` background: none; } +.c0 .endpoint-navTabs { + margin-left: 24px; +} + diff --git a/x-pack/plugins/siem/public/common/components/endpoint/formatted_date_time.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/formatted_date_time.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/endpoint/formatted_date_time.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/formatted_date_time.tsx diff --git a/x-pack/plugins/siem/public/common/components/endpoint/link_to_app.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.test.tsx similarity index 96% rename from x-pack/plugins/siem/public/common/components/endpoint/link_to_app.test.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.test.tsx index 0c3467d8f363c..79773630e0dc0 100644 --- a/x-pack/plugins/siem/public/common/components/endpoint/link_to_app.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.test.tsx @@ -44,7 +44,7 @@ describe('LinkToApp component', () => { }); it('should support onClick prop', () => { // Take `_event` (even though it is not used) so that `jest.fn` will have a type that expects to be called with an event - const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(_event => {}); + const spyOnClickHandler: LinkToAppOnClickMock = jest.fn((_event) => {}); const renderResult = render( {'link'} @@ -98,7 +98,7 @@ describe('LinkToApp component', () => { }); it('should still preventDefault if onClick callback throws', () => { // Take `_event` (even though it is not used) so that `jest.fn` will have a type that expects to be called with an event - const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(_event => { + const spyOnClickHandler: LinkToAppOnClickMock = jest.fn((_event) => { throw new Error('test'); }); const renderResult = render( @@ -111,7 +111,7 @@ describe('LinkToApp component', () => { expect(clickEventArg.isDefaultPrevented()).toBe(true); }); it('should not navigate if onClick callback prevents default', () => { - const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(ev => { + const spyOnClickHandler: LinkToAppOnClickMock = jest.fn((ev) => { ev.preventDefault(); }); const renderResult = render( @@ -143,7 +143,7 @@ describe('LinkToApp component', () => { ); const euiLink = renderResult.find('EuiLink'); - ['meta', 'alt', 'ctrl', 'shift'].forEach(key => { + ['meta', 'alt', 'ctrl', 'shift'].forEach((key) => { euiLink.simulate('click', { button: 0, [`${key}Key`]: true }); expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/siem/public/common/components/endpoint/link_to_app.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/endpoint/link_to_app.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx diff --git a/x-pack/plugins/siem/public/common/components/endpoint/page_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/endpoint/page_view.test.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/page_view.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx new file mode 100644 index 0000000000000..6fe15310fc88e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiPageHeaderSection, + EuiPageProps, + EuiTab, + EuiTabs, + EuiTitle, +} from '@elastic/eui'; +import React, { memo, MouseEventHandler, ReactNode, useMemo } from 'react'; +import styled from 'styled-components'; +import { EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; + +const StyledEuiPage = styled(EuiPage)` + &.endpoint--isListView { + padding: 0; + + .endpoint-header { + padding: ${(props) => props.theme.eui.euiSizeL}; + margin-bottom: 0; + } + .endpoint-page-content { + border-left: none; + border-right: none; + } + } + &.endpoint--isDetailsView { + .endpoint-page-content { + padding: 0; + border: none; + background: none; + } + } + .endpoint-navTabs { + margin-left: ${(props) => props.theme.eui.euiSizeL}; + } +`; + +const isStringOrNumber = /(string|number)/; + +/** + * The `PageView` component used to render `headerLeft` when it is set as a `string` + * Can be used when wanting to customize the `headerLeft` value but still use the standard + * title component + */ +export const PageViewHeaderTitle = memo<{ children: ReactNode }>(({ children }) => { + return ( + +

    {children}

    +
    + ); +}); + +PageViewHeaderTitle.displayName = 'PageViewHeaderTitle'; + +/** + * The `PageView` component used to render `bodyHeader` when it is set as a `string` + * Can be used when wanting to customize the `bodyHeader` value but still use the standard + * title component + */ +export const PageViewBodyHeaderTitle = memo<{ children: ReactNode }>( + ({ children, ...otherProps }) => { + return ( + +

    {children}

    +
    + ); + } +); +PageViewBodyHeaderTitle.displayName = 'PageViewBodyHeaderTitle'; + +export type PageViewProps = EuiPageProps & { + /** + * The type of view + */ + viewType: 'list' | 'details'; + /** + * content to be placed on the left side of the header. If a `string` is used, then it will + * be wrapped with `

    `, else it will just be used as is. + */ + headerLeft?: ReactNode; + /** Content for the right side of the header */ + headerRight?: ReactNode; + /** + * body (sub-)header section. If a `string` is used, then it will be wrapped with + * `

    ` + */ + bodyHeader?: ReactNode; + /** + * The list of tab navigation items + */ + tabs?: Array< + EuiTabProps & { + name: ReactNode; + id: string; + href?: string; + onClick?: MouseEventHandler; + } + >; + children?: ReactNode; +}; + +/** + * Page View layout for use in Endpoint + */ +export const PageView = memo( + ({ viewType, children, headerLeft, headerRight, bodyHeader, tabs, ...otherProps }) => { + const tabComponents = useMemo(() => { + if (!tabs) { + return []; + } + return tabs.map(({ name, id, ...otherEuiTabProps }) => ( + + {name} + + )); + }, [tabs]); + + return ( + + + {(headerLeft || headerRight) && ( + + + {isStringOrNumber.test(typeof headerLeft) ? ( + {headerLeft} + ) : ( + headerLeft + )} + + {headerRight && ( + + {headerRight} + + )} + + )} + {tabComponents.length > 0 && ( + {tabComponents} + )} + + {bodyHeader && ( + + + {isStringOrNumber.test(typeof bodyHeader) ? ( + {bodyHeader} + ) : ( + bodyHeader + )} + + + )} + {children} + + + + ); + } +); + +PageView.displayName = 'PageView'; diff --git a/x-pack/plugins/siem/public/common/components/endpoint/route_capture.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/endpoint/route_capture.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx diff --git a/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/__snapshots__/index.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/error_toast_dispatcher/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/__snapshots__/index.test.tsx.snap diff --git a/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.test.tsx new file mode 100644 index 0000000000000..39b17f7008e64 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallow } from 'enzyme'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../mock'; +import { createStore } from '../../store/store'; + +import { ErrorToastDispatcher } from '.'; +import { State } from '../../store/types'; + +describe('Error Toast Dispatcher', () => { + const state: State = mockGlobalState; + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + + beforeEach(() => { + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + }); + + describe('rendering', () => { + test('it renders', () => { + const wrapper = shallow( + + + + ); + expect(wrapper.find('Connect(ErrorToastDispatcherComponent)')).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.tsx b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.tsx new file mode 100644 index 0000000000000..d7e5a18dfb82e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; + +import { appSelectors, State } from '../../store'; +import { appActions } from '../../store/app'; +import { useStateToaster } from '../toasters'; + +interface OwnProps { + toastLifeTimeMs?: number; +} + +type Props = OwnProps & PropsFromRedux; + +const ErrorToastDispatcherComponent = ({ + toastLifeTimeMs = 5000, + errors = [], + removeError, +}: Props) => { + const [{ toasts }, dispatchToaster] = useStateToaster(); + useEffect(() => { + errors.forEach(({ id, title, message }) => { + if (!toasts.some((toast) => toast.id === id)) { + dispatchToaster({ + type: 'addToaster', + toast: { + color: 'danger', + id, + iconType: 'alert', + title, + errors: message, + toastLifeTimeMs, + }, + }); + } + removeError({ id }); + }); + }); + return null; +}; + +const makeMapStateToProps = () => { + const getErrorSelector = appSelectors.errorsSelector(); + return (state: State) => getErrorSelector(state); +}; + +const mapDispatchToProps = { + removeError: appActions.removeError, +}; + +const connector = connect(makeMapStateToProps, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +export const ErrorToastDispatcher = connector(ErrorToastDispatcherComponent); diff --git a/x-pack/plugins/siem/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap diff --git a/x-pack/plugins/siem/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap similarity index 100% rename from x-pack/plugins/siem/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap rename to x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx new file mode 100644 index 0000000000000..e01ccf1e544bb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx @@ -0,0 +1,211 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable react/display-name */ + +import { + EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import React from 'react'; +import { Draggable } from 'react-beautiful-dnd'; +import styled from 'styled-components'; + +import { BrowserFields } from '../../containers/source'; +import { ToStringArray } from '../../../graphql/types'; +import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; +import { DragEffects } from '../drag_and_drop/draggable_wrapper'; +import { DroppableWrapper } from '../drag_and_drop/droppable_wrapper'; +import { getDroppableId, getDraggableFieldId, DRAG_TYPE_FIELD } from '../drag_and_drop/helpers'; +import { DraggableFieldBadge } from '../draggables/field_badge'; +import { FieldName } from '../../../timelines/components/fields_browser/field_name'; +import { SelectableText } from '../selectable_text'; +import { OverflowField } from '../tables/helpers'; +import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers'; +import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; +import { MESSAGE_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants'; +import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; +import { OnUpdateColumns } from '../../../timelines/components/timeline/events'; +import { getIconFromType, getExampleText, getColumnsWithTimestamp } from './helpers'; +import * as i18n from './translations'; +import { EventFieldsData } from './types'; + +const HoverActionsContainer = styled(EuiPanel)` + align-items: center; + display: flex; + flex-direction: row; + height: 25px; + justify-content: center; + left: 5px; + position: absolute; + top: -10px; + width: 30px; +`; + +HoverActionsContainer.displayName = 'HoverActionsContainer'; + +export const getColumns = ({ + browserFields, + columnHeaders, + eventId, + onUpdateColumns, + contextId, + toggleColumn, +}: { + browserFields: BrowserFields; + columnHeaders: ColumnHeaderOptions[]; + eventId: string; + onUpdateColumns: OnUpdateColumns; + contextId: string; + toggleColumn: (column: ColumnHeaderOptions) => void; +}) => [ + { + field: 'field', + name: '', + sortable: false, + truncateText: false, + width: '30px', + render: (field: string) => ( + + c.id === field) !== -1} + data-test-subj={`toggle-field-${field}`} + id={field} + onChange={() => + toggleColumn({ + columnHeaderType: defaultColumnHeaderType, + id: field, + width: DEFAULT_COLUMN_MIN_WIDTH, + }) + } + /> + + ), + }, + { + field: 'field', + name: i18n.FIELD, + sortable: true, + truncateText: false, + render: (field: string, data: EventFieldsData) => ( + + + + + + + + + ( +
    + + + +
    + )} + > + + {(provided) => ( +
    + +
    + )} +
    +
    +
    +
    + ), + }, + { + field: 'values', + name: i18n.VALUE, + sortable: true, + truncateText: false, + render: (values: ToStringArray | null | undefined, data: EventFieldsData) => ( + + {values != null && + values.map((value, i) => ( + + {data.field === MESSAGE_FIELD_NAME ? ( + + ) : ( + + )} + + ))} + + ), + }, + { + field: 'description', + name: i18n.DESCRIPTION, + render: (description: string | null | undefined, data: EventFieldsData) => ( + + {`${description || ''} ${getExampleText(data.example)}`} + + ), + sortable: true, + truncateText: true, + width: '50%', + }, + { + field: 'valuesConcatenated', + name: i18n.BLANK, + render: () => null, + sortable: false, + truncateText: true, + width: '1px', + }, +]; diff --git a/x-pack/plugins/siem/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx similarity index 93% rename from x-pack/plugins/siem/public/common/components/event_details/event_details.test.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 162fc8fd8bb34..cb2cbcc62aba1 100644 --- a/x-pack/plugins/siem/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -38,7 +38,7 @@ describe('EventDetails', () => { }); describe('tabs', () => { - ['Table', 'JSON View'].forEach(tab => { + ['Table', 'JSON View'].forEach((tab) => { test(`it renders the ${tab} tab`, () => { const wrapper = mount( @@ -83,11 +83,7 @@ describe('EventDetails', () => { ); expect( - wrapper - .find('[data-test-subj="eventDetails"]') - .find('.euiTab-isSelected') - .first() - .text() + wrapper.find('[data-test-subj="eventDetails"]').find('.euiTab-isSelected').first().text() ).toEqual('Table'); }); }); diff --git a/x-pack/plugins/siem/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx similarity index 97% rename from x-pack/plugins/siem/public/common/components/event_details/event_details.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index c6a7a05bb2698..c28757a90c702 100644 --- a/x-pack/plugins/siem/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -76,7 +76,7 @@ export const EventDetails = React.memo( onViewSelected(e.id as View)} + onTabClick={(e) => onViewSelected(e.id as View)} /> ); diff --git a/x-pack/plugins/siem/public/common/components/event_details/event_fields_browser.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx similarity index 88% rename from x-pack/plugins/siem/public/common/components/event_details/event_fields_browser.test.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx index 9b23acbdc9502..9f60d16a6f671 100644 --- a/x-pack/plugins/siem/public/common/components/event_details/event_fields_browser.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx @@ -18,7 +18,7 @@ describe('EventFieldsBrowser', () => { const mount = useMountAppended(); describe('column headers', () => { - ['Field', 'Value', 'Description'].forEach(header => { + ['Field', 'Value', 'Description'].forEach((header) => { test(`it renders the ${header} column header`, () => { const wrapper = mount( @@ -81,12 +81,9 @@ describe('EventFieldsBrowser', () => { ); - expect( - wrapper - .find(`[data-test-subj="toggle-field-${field}"]`) - .first() - .props().checked - ).toBe(false); + expect(wrapper.find(`[data-test-subj="toggle-field-${field}"]`).first().props().checked).toBe( + false + ); }); test('it renders an checked checkbox for a field that is a member of columnHeaders', () => { @@ -106,12 +103,9 @@ describe('EventFieldsBrowser', () => { ); - expect( - wrapper - .find(`[data-test-subj="toggle-field-${field}"]`) - .first() - .props().checked - ).toBe(true); + expect(wrapper.find(`[data-test-subj="toggle-field-${field}"]`).first().props().checked).toBe( + true + ); }); test('it invokes toggleColumn when the checkbox is clicked', () => { @@ -191,12 +185,7 @@ describe('EventFieldsBrowser', () => { /> ); - expect( - wrapper - .find('[data-test-subj="field-name"]') - .at(0) - .text() - ).toEqual('@timestamp'); + expect(wrapper.find('[data-test-subj="field-name"]').at(0).text()).toEqual('@timestamp'); }); }); @@ -215,12 +204,9 @@ describe('EventFieldsBrowser', () => { /> ); - expect( - wrapper - .find('[data-test-subj="draggable-content-@timestamp"]') - .at(0) - .text() - ).toEqual('Feb 28, 2019 @ 16:50:54.621'); + expect(wrapper.find('[data-test-subj="draggable-content-@timestamp"]').at(0).text()).toEqual( + 'Feb 28, 2019 @ 16:50:54.621' + ); }); }); @@ -240,13 +226,7 @@ describe('EventFieldsBrowser', () => { ); - expect( - wrapper - .find('.euiTableRow') - .find('.euiTableRowCell') - .at(3) - .text() - ).toContain( + expect(wrapper.find('.euiTableRow').find('.euiTableRowCell').at(3).text()).toContain( 'DescriptionDate/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events. Example: 2016-05-23T08:05:34.853Z' ); }); diff --git a/x-pack/plugins/siem/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx similarity index 97% rename from x-pack/plugins/siem/public/common/components/event_details/event_fields_browser.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx index 0428f3ec8a197..af40d4ff18d5a 100644 --- a/x-pack/plugins/siem/public/common/components/event_details/event_fields_browser.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx @@ -32,7 +32,7 @@ export const EventFieldsBrowser = React.memo( const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); const items = useMemo( () => - sortBy(data, ['field']).map(item => ({ + sortBy(data, ['field']).map((item) => ({ ...item, ...fieldsByName[item.field], valuesConcatenated: item.values != null ? item.values.join() : '', diff --git a/x-pack/plugins/siem/public/common/components/event_details/event_id.ts b/x-pack/plugins/security_solution/public/common/components/event_details/event_id.ts similarity index 100% rename from x-pack/plugins/siem/public/common/components/event_details/event_id.ts rename to x-pack/plugins/security_solution/public/common/components/event_details/event_id.ts diff --git a/x-pack/plugins/siem/public/common/components/event_details/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/event_details/helpers.test.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/helpers.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx new file mode 100644 index 0000000000000..6aae2c9937ec0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get, getOr, isEmpty, uniqBy } from 'lodash/fp'; + +import { BrowserField, BrowserFields } from '../../containers/source'; +import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; +import { + DEFAULT_DATE_COLUMN_MIN_WIDTH, + DEFAULT_COLUMN_MIN_WIDTH, +} from '../../../timelines/components/timeline/body/constants'; +import { ToStringArray } from '../../../graphql/types'; + +import * as i18n from './translations'; + +/** + * Defines the behavior of the search input that appears above the table of data + */ +export const search = { + box: { + incremental: true, + placeholder: i18n.PLACEHOLDER, + schema: true, + }, +}; + +export interface ItemValues { + value: JSX.Element; + valueAsString: string; +} + +/** + * An item rendered in the table + */ +export interface Item { + description: string; + field: JSX.Element; + fieldId: string; + type: string; + values: ToStringArray; +} + +export const getColumnHeaderFromBrowserField = ({ + browserField, + width = DEFAULT_COLUMN_MIN_WIDTH, +}: { + browserField: Partial; + width?: number; +}): ColumnHeaderOptions => ({ + category: browserField.category, + columnHeaderType: 'not-filtered', + description: browserField.description != null ? browserField.description : undefined, + example: browserField.example != null ? `${browserField.example}` : undefined, + id: browserField.name || '', + type: browserField.type, + aggregatable: browserField.aggregatable, + width, +}); + +/** + * Returns a collection of columns, where the first column in the collection + * is a timestamp, and the remaining columns are all the columns in the + * specified category + */ +export const getColumnsWithTimestamp = ({ + browserFields, + category, +}: { + browserFields: BrowserFields; + category: string; +}): ColumnHeaderOptions[] => { + const emptyFields: Record> = {}; + const timestamp = get('base.fields.@timestamp', browserFields); + const categoryFields: Array> = [ + ...Object.values(getOr(emptyFields, `${category}.fields`, browserFields)), + ]; + + return timestamp != null && categoryFields.length + ? uniqBy('id', [ + getColumnHeaderFromBrowserField({ + browserField: timestamp, + width: DEFAULT_DATE_COLUMN_MIN_WIDTH, + }), + ...categoryFields.map((f) => getColumnHeaderFromBrowserField({ browserField: f })), + ]) + : []; +}; + +/** Returns example text, or an empty string if the field does not have an example */ +export const getExampleText = (example: string | number | null | undefined): string => + !isEmpty(example) ? `Example: ${example}` : ''; + +export const getIconFromType = (type: string | null) => { + switch (type) { + case 'string': // fall through + case 'keyword': + return 'string'; + case 'number': // fall through + case 'long': + return 'number'; + case 'date': + return 'clock'; + case 'ip': + return 'globe'; + case 'object': + return 'questionInCircle'; + case 'float': + return 'number'; + default: + return 'questionInCircle'; + } +}; diff --git a/x-pack/plugins/siem/public/common/components/event_details/json_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/event_details/json_view.test.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx diff --git a/x-pack/plugins/siem/public/common/components/event_details/json_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx similarity index 100% rename from x-pack/plugins/siem/public/common/components/event_details/json_view.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx diff --git a/x-pack/plugins/siem/public/common/components/event_details/stateful_event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/stateful_event_details.tsx similarity index 95% rename from x-pack/plugins/siem/public/common/components/event_details/stateful_event_details.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/stateful_event_details.tsx index ec0e82c218a07..6e04991e6a0bb 100644 --- a/x-pack/plugins/siem/public/common/components/event_details/stateful_event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/stateful_event_details.tsx @@ -27,7 +27,7 @@ export const StatefulEventDetails = React.memo( ({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => { const [view, setView] = useState('table-view'); - const handleSetView = useCallback(newView => setView(newView), []); + const handleSetView = useCallback((newView) => setView(newView), []); return ( { await wait(); wrapper.update(); - expect( - wrapper - .find(`[data-test-subj="header-section-subtitle"]`) - .first() - .text() - ).toEqual('Showing: 12 events'); + expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().text()).toEqual( + 'Showing: 12 events' + ); }); test('it renders the Fields Browser as a settings gear', async () => { @@ -80,12 +77,7 @@ describe('EventsViewer', () => { await wait(); wrapper.update(); - expect( - wrapper - .find(`[data-test-subj="show-field-browser-gear"]`) - .first() - .exists() - ).toBe(true); + expect(wrapper.find(`[data-test-subj="show-field-browser-gear"]`).first().exists()).toBe(true); }); test('it renders the footer containing the Load More button', async () => { @@ -105,15 +97,10 @@ describe('EventsViewer', () => { await wait(); wrapper.update(); - expect( - wrapper - .find(`[data-test-subj="TimelineMoreButton"]`) - .first() - .exists() - ).toBe(true); + expect(wrapper.find(`[data-test-subj="TimelineMoreButton"]`).first().exists()).toBe(true); }); - defaultHeaders.forEach(header => { + defaultHeaders.forEach((header) => { test(`it renders the ${header.id} default EventsViewer column header`, async () => { const wrapper = mount( @@ -131,13 +118,10 @@ describe('EventsViewer', () => { await wait(); wrapper.update(); - defaultHeaders.forEach(h => - expect( - wrapper - .find(`[data-test-subj="header-text-${header.id}"]`) - .first() - .exists() - ).toBe(true) + defaultHeaders.forEach((h) => + expect(wrapper.find(`[data-test-subj="header-text-${header.id}"]`).first().exists()).toBe( + true + ) ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx new file mode 100644 index 0000000000000..d0bd87188e541 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -0,0 +1,249 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiPanel } from '@elastic/eui'; +import { getOr, isEmpty, union } from 'lodash/fp'; +import React, { useEffect, useMemo, useState } from 'react'; +import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; + +import { BrowserFields } from '../../containers/source'; +import { TimelineQuery } from '../../../timelines/containers'; +import { Direction } from '../../../graphql/types'; +import { useKibana } from '../../lib/kibana'; +import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model'; +import { HeaderSection } from '../header_section'; +import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers'; +import { Sort } from '../../../timelines/components/timeline/body/sort'; +import { StatefulBody } from '../../../timelines/components/timeline/body/stateful_body'; +import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; +import { OnChangeItemsPerPage } from '../../../timelines/components/timeline/events'; +import { Footer, footerHeight } from '../../../timelines/components/timeline/footer'; +import { combineQueries } from '../../../timelines/components/timeline/helpers'; +import { TimelineRefetch } from '../../../timelines/components/timeline/refetch_timeline'; +import { EventDetailsWidthProvider } from './event_details_width_context'; +import * as i18n from './translations'; +import { + Filter, + esQuery, + IIndexPattern, + Query, +} from '../../../../../../../src/plugins/data/public'; +import { inputsModel } from '../../store'; +import { useManageTimeline } from '../../../timelines/components/manage_timeline'; + +const DEFAULT_EVENTS_VIEWER_HEIGHT = 500; + +const StyledEuiPanel = styled(EuiPanel)` + max-width: 100%; +`; + +const EventsContainerLoading = styled.div` + width: 100%; + overflow: auto; +`; + +interface Props { + browserFields: BrowserFields; + columns: ColumnHeaderOptions[]; + dataProviders: DataProvider[]; + deletedEventIds: Readonly; + end: number; + filters: Filter[]; + headerFilterGroup?: React.ReactNode; + height?: number; + id: string; + indexPattern: IIndexPattern; + isLive: boolean; + itemsPerPage: number; + itemsPerPageOptions: number[]; + kqlMode: KqlMode; + onChangeItemsPerPage: OnChangeItemsPerPage; + query: Query; + start: number; + sort: Sort; + toggleColumn: (column: ColumnHeaderOptions) => void; + utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; +} + +const EventsViewerComponent: React.FC = ({ + browserFields, + columns, + dataProviders, + deletedEventIds, + end, + filters, + headerFilterGroup, + height = DEFAULT_EVENTS_VIEWER_HEIGHT, + id, + indexPattern, + isLive, + itemsPerPage, + itemsPerPageOptions, + kqlMode, + onChangeItemsPerPage, + query, + start, + sort, + toggleColumn, + utilityBar, +}) => { + const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; + const kibana = useKibana(); + const { filterManager } = useKibana().services.data.query; + const [isQueryLoading, setIsQueryLoading] = useState(false); + + const { + getManageTimelineById, + setIsTimelineLoading, + setTimelineFilterManager, + } = useManageTimeline(); + useEffect(() => { + setIsTimelineLoading({ id, isLoading: isQueryLoading }); + }, [isQueryLoading]); + useEffect(() => { + setTimelineFilterManager({ id, filterManager }); + }, [filterManager]); + + const { queryFields, title, unit } = useMemo(() => getManageTimelineById(id), [ + getManageTimelineById, + id, + ]); + + const combinedQueries = combineQueries({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + dataProviders, + indexPattern, + browserFields, + filters, + kqlQuery: query, + kqlMode, + start, + end, + isEventViewer: true, + }); + const fields = useMemo( + () => + union( + columnsHeader.map((c) => c.id), + queryFields ?? [] + ), + [columnsHeader, queryFields] + ); + const sortField = useMemo( + () => ({ + sortFieldId: sort.columnId, + direction: sort.sortDirection as Direction, + }), + [sort.columnId, sort.sortDirection] + ); + + return ( + + {combinedQueries != null ? ( + + + {({ + events, + getUpdatedAt, + inspect, + loading, + loadMore, + pageInfo, + refetch, + totalCount = 0, + }) => { + setIsQueryLoading(loading); + const totalCountMinusDeleted = + totalCount > 0 ? totalCount - deletedEventIds.length : 0; + + const subtitle = `${i18n.SHOWING}: ${totalCountMinusDeleted.toLocaleString()} ${unit( + totalCountMinusDeleted + )}`; + + return ( + <> + + {headerFilterGroup} + + + {utilityBar?.(refetch, totalCountMinusDeleted)} + + + + + !deletedEventIds.includes(e._id))} + id={id} + isEventViewer={true} + height={height} + sort={sort} + toggleColumn={toggleColumn} + /> + +

    ]: KbnConfigSchemaInputTypeOf }; + +/** + * Takes the props of a schema.object type, and returns a version that excludes + * optional values. Used by `InputObjectTypeOf`. + * + * Instead of using this directly, use `InputTypeOf`. + */ +type KbnConfigSchemaNonOptionalProps> = Pick< + Props, + { + [Key in keyof Props]: undefined extends Props[Key] + ? never + : null extends Props[Key] + ? never + : Key; + }[keyof Props] +>; + +/** + * Endpoint Policy configuration + */ +export interface PolicyConfig { + windows: { + events: { + dll_and_driver_load: boolean; + dns: boolean; + file: boolean; + network: boolean; + process: boolean; + registry: boolean; + security: boolean; + }; + malware: MalwareFields; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + mac: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + malware: MalwareFields; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + linux: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; +} + +/** + * The set of Policy configuration settings that are show/edited via the UI + */ +export interface UIPolicyConfig { + /** + * Windows-specific policy configuration that is supported via the UI + */ + windows: Pick; + /** + * Mac-specific policy configuration that is supported via the UI + */ + mac: Pick; + /** + * Linux-specific policy configuration that is supported via the UI + */ + linux: Pick; +} + +interface PolicyConfigAdvancedOptions { + elasticsearch: { + indices: { + control: string; + event: string; + logging: string; + }; + kernel: { + connect: boolean; + process: boolean; + }; + }; +} + +/** Policy: Malware protection fields */ +export interface MalwareFields { + mode: ProtectionModes; +} + +/** Policy protection mode options */ +export enum ProtectionModes { + detect = 'detect', + prevent = 'prevent', + preventNotify = 'preventNotify', + off = 'off', +} + +/** + * Endpoint Policy data, which extends Ingest's `Datasource` type + */ +export type PolicyData = Datasource & NewPolicyData; + +/** + * New policy data. Used when updating the policy record via ingest APIs + */ +export type NewPolicyData = NewDatasource & { + inputs: [ + { + type: 'endpoint'; + enabled: boolean; + streams: []; + config: { + policy: { + value: PolicyConfig; + }; + }; + } + ]; +}; + +/** + * the possible status for actions, configurations and overall Policy Response + */ +export enum HostPolicyResponseActionStatus { + success = 'success', + failure = 'failure', + warning = 'warning', +} + +/** + * The name of actions that can be applied during the processing of a policy + */ +type HostPolicyActionName = + | 'download_model' + | 'ingest_events_config' + | 'workflow' + | 'configure_elasticsearch_connection' + | 'configure_kernel' + | 'configure_logging' + | 'configure_malware' + | 'connect_kernel' + | 'detect_file_open_events' + | 'detect_file_write_events' + | 'detect_image_load_events' + | 'detect_process_events' + | 'download_global_artifacts' + | 'load_config' + | 'load_malware_model' + | 'read_elasticsearch_config' + | 'read_events_config' + | 'read_kernel_config' + | 'read_logging_config' + | 'read_malware_config' + | string; + +/** + * Host Policy Response Applied Action + */ +export interface HostPolicyResponseAppliedAction { + name: HostPolicyActionName; + status: HostPolicyResponseActionStatus; + message: string; +} + +export type HostPolicyResponseConfiguration = HostPolicyResponse['endpoint']['policy']['applied']['response']['configurations']; + +interface HostPolicyResponseConfigurationStatus { + status: HostPolicyResponseActionStatus; + concerned_actions: HostPolicyActionName[]; +} + +/** + * Host Policy Response Applied Artifact + */ +interface HostPolicyResponseAppliedArtifact { + name: string; + sha256: string; +} + +/** + * Information about the applying of a policy to a given host + */ +export interface HostPolicyResponse { + '@timestamp': number; + elastic: { + agent: { + id: string; + }; + }; + ecs: { + version: string; + }; + host: { + id: string; + }; + event: { + created: number; + kind: string; + id: string; + category: string; + type: string; + module: string; + action: string; + dataset: string; + }; + agent: { + version: string; + id: string; + }; + endpoint: { + policy: { + applied: { + version: string; + id: string; + status: HostPolicyResponseActionStatus; + actions: HostPolicyResponseAppliedAction[]; + response: { + configurations: { + malware: HostPolicyResponseConfigurationStatus; + events: HostPolicyResponseConfigurationStatus; + logging: HostPolicyResponseConfigurationStatus; + streaming: HostPolicyResponseConfigurationStatus; + }; + }; + artifacts: { + global: { + version: string; + identifiers: HostPolicyResponseAppliedArtifact[]; + }; + user: { + version: string; + identifiers: HostPolicyResponseAppliedArtifact[]; + }; + }; + }; + }; + }; +} + +/** + * REST API response for retrieving a host's Policy Response status + */ +export interface GetHostPolicyResponse { + policy_response: HostPolicyResponse; +} diff --git a/x-pack/plugins/siem/common/endpoint_alerts/alert_constants.ts b/x-pack/plugins/security_solution/common/endpoint_alerts/alert_constants.ts similarity index 100% rename from x-pack/plugins/siem/common/endpoint_alerts/alert_constants.ts rename to x-pack/plugins/security_solution/common/endpoint_alerts/alert_constants.ts diff --git a/x-pack/plugins/siem/common/endpoint_alerts/schema/alert_index.ts b/x-pack/plugins/security_solution/common/endpoint_alerts/schema/alert_index.ts similarity index 84% rename from x-pack/plugins/siem/common/endpoint_alerts/schema/alert_index.ts rename to x-pack/plugins/security_solution/common/endpoint_alerts/schema/alert_index.ts index 6794eea20066b..bf57a366f341d 100644 --- a/x-pack/plugins/siem/common/endpoint_alerts/schema/alert_index.ts +++ b/x-pack/plugins/security_solution/common/endpoint_alerts/schema/alert_index.ts @@ -47,7 +47,7 @@ export const alertingIndexGetQuerySchema = schema.object( try { decode(value); } catch (err) { - return i18n.translate('xpack.siem.endpoint.alerts.errors.bad_rison', { + return i18n.translate('xpack.securitySolution.endpoint.alerts.errors.bad_rison', { defaultMessage: 'must be a valid rison-encoded string', }); } @@ -62,7 +62,7 @@ export const alertingIndexGetQuerySchema = schema.object( try { decode(value); } catch (err) { - return i18n.translate('xpack.siem.endpoint.alerts.errors.bad_rison', { + return i18n.translate('xpack.securitySolution.endpoint.alerts.errors.bad_rison', { defaultMessage: 'must be a valid rison-encoded string', }); } @@ -77,7 +77,7 @@ export const alertingIndexGetQuerySchema = schema.object( try { decode(value); } catch (err) { - return i18n.translate('xpack.siem.endpoint.alerts.errors.bad_rison', { + return i18n.translate('xpack.securitySolution.endpoint.alerts.errors.bad_rison', { defaultMessage: 'must be a valid rison-encoded string', }); } @@ -89,7 +89,7 @@ export const alertingIndexGetQuerySchema = schema.object( validate(value) { if (value.after !== undefined && value.page_index !== undefined) { return i18n.translate( - 'xpack.siem.endpoint.alerts.errors.page_index_cannot_be_used_with_after', + 'xpack.securitySolution.endpoint.alerts.errors.page_index_cannot_be_used_with_after', { defaultMessage: '[page_index] cannot be used with [after]', } @@ -97,7 +97,7 @@ export const alertingIndexGetQuerySchema = schema.object( } if (value.before !== undefined && value.page_index !== undefined) { return i18n.translate( - 'xpack.siem.endpoint.alerts.errors.page_index_cannot_be_used_with_before', + 'xpack.securitySolution.endpoint.alerts.errors.page_index_cannot_be_used_with_before', { defaultMessage: '[page_index] cannot be used with [before]', } @@ -105,7 +105,7 @@ export const alertingIndexGetQuerySchema = schema.object( } if (value.before !== undefined && value.after !== undefined) { return i18n.translate( - 'xpack.siem.endpoint.alerts.errors.before_cannot_be_used_with_after', + 'xpack.securitySolution.endpoint.alerts.errors.before_cannot_be_used_with_after', { defaultMessage: '[before] cannot be used with [after]', } diff --git a/x-pack/plugins/siem/common/endpoint_alerts/schema/index_pattern.ts b/x-pack/plugins/security_solution/common/endpoint_alerts/schema/index_pattern.ts similarity index 100% rename from x-pack/plugins/siem/common/endpoint_alerts/schema/index_pattern.ts rename to x-pack/plugins/security_solution/common/endpoint_alerts/schema/index_pattern.ts diff --git a/x-pack/plugins/security_solution/common/endpoint_alerts/types.ts b/x-pack/plugins/security_solution/common/endpoint_alerts/types.ts new file mode 100644 index 0000000000000..3fbde79414aa0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint_alerts/types.ts @@ -0,0 +1,257 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchResponse } from 'elasticsearch'; +import { TypeOf } from '@kbn/config-schema'; +import { IIndexPattern, TimeRange, Filter, Query } from 'src/plugins/data/public'; +import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { alertingIndexGetQuerySchema } from './schema/alert_index'; +import { indexPatternGetParamsSchema } from './schema/index_pattern'; +import { + HostMetadata, + AlertEvent, + KbnConfigSchemaInputTypeOf, + AppLocation, + Immutable, +} from '../endpoint/types'; + +/** + * Values for the Alert APIs 'order' and 'direction' parameters. + */ +export type AlertAPIOrdering = 'asc' | 'desc'; + +/** + * Returned by 'api/endpoint/alerts' + */ +export interface AlertResultList { + /** + * The alerts restricted by page size. + */ + alerts: AlertData[]; + + /** + * The total number of alerts on the page. + */ + total: number; + + /** + * The size of the requested page. + */ + request_page_size: number; + + /** + * The index of the requested page, starting at 0. + */ + request_page_index?: number; + + /** + * The offset of the requested page, starting at 0. + */ + result_from_index?: number; + + /** + * A cursor-based URL for the next page. + */ + next: string | null; + + /** + * A cursor-based URL for the previous page. + */ + prev: string | null; +} + +interface AlertMetadata { + id: string; + + // Alert Details Pagination + next: string | null; + prev: string | null; +} + +interface AlertState { + state: { + host_metadata: HostMetadata; + }; +} + +export type AlertData = AlertEvent & AlertMetadata; + +export type AlertDetails = AlertData & AlertState; + +/** + * Represents `total` response from Elasticsearch after ES 7.0. + */ +export interface ESTotal { + value: number; + relation: string; +} + +/** + * `Hits` array in responses from ES search API. + */ +export type AlertHits = SearchResponse['hits']['hits']; + +/** + * Query params to pass to the alert API when fetching new data. + */ +export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf< + TypeOf +>; + +/** + * Result of the validated query params when handling alert index requests. + */ +export type AlertingIndexGetQueryResult = TypeOf; + +/** + * Result of the validated params when handling an index pattern request. + */ +export type IndexPatternGetParamsResult = TypeOf; + +interface AlertsSearchBarState { + patterns: IIndexPattern[]; +} + +export type AlertListData = AlertResultList; + +export interface AlertListState { + /** Array of alert items. */ + readonly alerts: Immutable; + + /** The total number of alerts on the page. */ + readonly total: number; + + /** Number of alerts per page. */ + readonly pageSize: number; + + /** Page number, starting at 0. */ + readonly pageIndex: number; + + /** Current location object from React Router history. */ + readonly location?: Immutable; + + /** Specific Alert data to be shown in the details view */ + readonly alertDetails?: Immutable; + + /** Search bar state including indexPatterns */ + readonly searchBar: AlertsSearchBarState; +} + +/** + * Gotten by parsing the URL from the browser. Used to calculate the new URL when changing views. + */ +export interface AlertingIndexUIQueryParams { + /** + * How many items to show in list. + */ + page_size?: string; + /** + * Which page to show. If `page_index` is 1, show page 2. + */ + page_index?: string; + /** + * If any value is present, show the alert detail view for the selected alert. Should be an ID for an alert event. + */ + selected_alert?: string; + /** + * Retain the selected tab through any refreshes. Should be an ID for an alert event. + */ + active_details_tab?: string; + query?: string; + date_range?: string; + filters?: string; +} + +/** + * Sort parameters for alerts in ES. + */ +export interface AlertSortParam { + [key: string]: { + order: AlertAPIOrdering; + missing?: UndefinedResultPosition; + }; +} + +/** + * Sort array for alerts. + */ +export type AlertSort = [AlertSortParam, AlertSortParam]; + +/** + * Cursor-based pagination params. + */ +export type SearchCursor = [string, string]; + +/** + * Request metadata used in searching alerts. + */ +export interface AlertSearchQuery { + pageSize: number; + pageIndex?: number; + fromIndex?: number; + query: Query; + filters: Filter[]; + dateRange?: TimeRange; + sort: string; + order: AlertAPIOrdering; + searchAfter?: SearchCursor; + searchBefore?: SearchCursor; + emptyStringIsUndefined?: boolean; +} + +/** + * ES request body for alerts. + */ +export interface AlertSearchRequest { + track_total_hits: number; + query: JsonObject; + sort: AlertSort; + search_after?: SearchCursor; +} + +/** + * Request for alerts. + */ +export interface AlertSearchRequestWrapper { + index: string; + size: number; + from?: number; + body: AlertSearchRequest; +} + +/** + * Request params for alert details. + */ +export interface AlertDetailsRequestParams { + id: string; +} + +/** + * Request params for alert queries. + * + * Must match exactly the values that the API receives. + */ +export interface AlertListRequestQuery { + page_index?: number; + page_size: number; + query?: string; + filters?: string; + date_range: string; + sort: string; + order: AlertAPIOrdering; + after?: SearchCursor; + before?: SearchCursor; + empty_string_is_undefined?: boolean; +} + +/** + * Indicates whether undefined results are sorted to the beginning (_first) or end (_last) + * of a result set. + */ +export enum UndefinedResultPosition { + first = '_first', + last = '_last', +} diff --git a/x-pack/plugins/siem/common/exact_check.test.ts b/x-pack/plugins/security_solution/common/exact_check.test.ts similarity index 100% rename from x-pack/plugins/siem/common/exact_check.test.ts rename to x-pack/plugins/security_solution/common/exact_check.test.ts diff --git a/x-pack/plugins/siem/common/exact_check.ts b/x-pack/plugins/security_solution/common/exact_check.ts similarity index 95% rename from x-pack/plugins/siem/common/exact_check.ts rename to x-pack/plugins/security_solution/common/exact_check.ts index 9484765f9973d..30c5b585a3480 100644 --- a/x-pack/plugins/siem/common/exact_check.ts +++ b/x-pack/plugins/security_solution/common/exact_check.ts @@ -58,8 +58,8 @@ export const findDifferencesRecursive = (original: T, decodedValue: T): strin return []; } else { const decodedKeys = Object.keys(decodedValue); - const differences = Object.keys(original).flatMap(originalKey => { - const foundKey = decodedKeys.some(key => key === originalKey); + const differences = Object.keys(original).flatMap((originalKey) => { + const foundKey = decodedKeys.some((key) => key === originalKey); const topLevelKey = foundKey ? [] : [originalKey]; // I use lodash to cheat and get an any (not going to lie ;-)) const valueObjectOrArrayOriginal = get(originalKey, original); diff --git a/x-pack/plugins/siem/common/format_errors.test.ts b/x-pack/plugins/security_solution/common/format_errors.test.ts similarity index 100% rename from x-pack/plugins/siem/common/format_errors.test.ts rename to x-pack/plugins/security_solution/common/format_errors.test.ts diff --git a/x-pack/plugins/siem/common/format_errors.ts b/x-pack/plugins/security_solution/common/format_errors.ts similarity index 77% rename from x-pack/plugins/siem/common/format_errors.ts rename to x-pack/plugins/security_solution/common/format_errors.ts index a9c222050ee38..d712979f9eff3 100644 --- a/x-pack/plugins/siem/common/format_errors.ts +++ b/x-pack/plugins/security_solution/common/format_errors.ts @@ -7,15 +7,15 @@ import * as t from 'io-ts'; export const formatErrors = (errors: t.Errors): string[] => { - return errors.map(error => { + return errors.map((error) => { if (error.message != null) { return error.message; } else { const mappedContext = error.context .filter( - entry => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== '' + (entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== '' ) - .map(entry => entry.key) + .map((entry) => entry.key) .join(','); return `Invalid value "${error.value}" supplied to "${mappedContext}"`; } diff --git a/x-pack/plugins/siem/common/graphql/root/index.ts b/x-pack/plugins/security_solution/common/graphql/root/index.ts similarity index 100% rename from x-pack/plugins/siem/common/graphql/root/index.ts rename to x-pack/plugins/security_solution/common/graphql/root/index.ts diff --git a/x-pack/plugins/siem/common/graphql/root/schema.gql.ts b/x-pack/plugins/security_solution/common/graphql/root/schema.gql.ts similarity index 100% rename from x-pack/plugins/siem/common/graphql/root/schema.gql.ts rename to x-pack/plugins/security_solution/common/graphql/root/schema.gql.ts diff --git a/x-pack/plugins/siem/common/graphql/shared/index.ts b/x-pack/plugins/security_solution/common/graphql/shared/index.ts similarity index 100% rename from x-pack/plugins/siem/common/graphql/shared/index.ts rename to x-pack/plugins/security_solution/common/graphql/shared/index.ts diff --git a/x-pack/plugins/siem/common/graphql/shared/schema.gql.ts b/x-pack/plugins/security_solution/common/graphql/shared/schema.gql.ts similarity index 100% rename from x-pack/plugins/siem/common/graphql/shared/schema.gql.ts rename to x-pack/plugins/security_solution/common/graphql/shared/schema.gql.ts diff --git a/x-pack/plugins/siem/common/machine_learning/empty_ml_capabilities.ts b/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts similarity index 100% rename from x-pack/plugins/siem/common/machine_learning/empty_ml_capabilities.ts rename to x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts diff --git a/x-pack/plugins/siem/common/machine_learning/has_ml_admin_permissions.test.ts b/x-pack/plugins/security_solution/common/machine_learning/has_ml_admin_permissions.test.ts similarity index 100% rename from x-pack/plugins/siem/common/machine_learning/has_ml_admin_permissions.test.ts rename to x-pack/plugins/security_solution/common/machine_learning/has_ml_admin_permissions.test.ts diff --git a/x-pack/plugins/siem/common/machine_learning/has_ml_admin_permissions.ts b/x-pack/plugins/security_solution/common/machine_learning/has_ml_admin_permissions.ts similarity index 100% rename from x-pack/plugins/siem/common/machine_learning/has_ml_admin_permissions.ts rename to x-pack/plugins/security_solution/common/machine_learning/has_ml_admin_permissions.ts diff --git a/x-pack/plugins/siem/common/machine_learning/has_ml_user_permissions.test.ts b/x-pack/plugins/security_solution/common/machine_learning/has_ml_user_permissions.test.ts similarity index 100% rename from x-pack/plugins/siem/common/machine_learning/has_ml_user_permissions.test.ts rename to x-pack/plugins/security_solution/common/machine_learning/has_ml_user_permissions.test.ts diff --git a/x-pack/plugins/siem/common/machine_learning/has_ml_user_permissions.ts b/x-pack/plugins/security_solution/common/machine_learning/has_ml_user_permissions.ts similarity index 100% rename from x-pack/plugins/siem/common/machine_learning/has_ml_user_permissions.ts rename to x-pack/plugins/security_solution/common/machine_learning/has_ml_user_permissions.ts diff --git a/x-pack/plugins/siem/common/machine_learning/helpers.test.ts b/x-pack/plugins/security_solution/common/machine_learning/helpers.test.ts similarity index 100% rename from x-pack/plugins/siem/common/machine_learning/helpers.test.ts rename to x-pack/plugins/security_solution/common/machine_learning/helpers.test.ts diff --git a/x-pack/plugins/siem/common/machine_learning/helpers.ts b/x-pack/plugins/security_solution/common/machine_learning/helpers.ts similarity index 100% rename from x-pack/plugins/siem/common/machine_learning/helpers.ts rename to x-pack/plugins/security_solution/common/machine_learning/helpers.ts diff --git a/x-pack/plugins/security_solution/common/test_utils.ts b/x-pack/plugins/security_solution/common/test_utils.ts new file mode 100644 index 0000000000000..b96639ad7b034 --- /dev/null +++ b/x-pack/plugins/security_solution/common/test_utils.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { formatErrors } from './format_errors'; + +interface Message { + errors: t.Errors; + schema: T | {}; +} + +const onLeft = (errors: t.Errors): Message => { + return { schema: {}, errors }; +}; + +const onRight = (schema: T): Message => { + return { + schema, + errors: [], + }; +}; + +export const foldLeftRight = fold(onLeft, onRight); + +/** + * Convenience utility to keep the error message handling within tests to be + * very concise. + * @param validation The validation to get the errors from + */ +export const getPaths = (validation: t.Validation): string[] => { + return pipe( + validation, + fold( + (errors) => formatErrors(errors), + () => ['no errors'] + ) + ); +}; diff --git a/x-pack/plugins/siem/common/typed_json.ts b/x-pack/plugins/security_solution/common/typed_json.ts similarity index 98% rename from x-pack/plugins/siem/common/typed_json.ts rename to x-pack/plugins/security_solution/common/typed_json.ts index 62e7319e091cb..61c1093002192 100644 --- a/x-pack/plugins/siem/common/typed_json.ts +++ b/x-pack/plugins/security_solution/common/typed_json.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { JsonObject } from '../../../../src/plugins/kibana_utils/public'; +import { JsonObject } from '../../../../src/plugins/kibana_utils/common'; export type ESQuery = ESRangeQuery | ESQueryStringQuery | ESMatchQuery | ESTermQuery | JsonObject; diff --git a/x-pack/plugins/siem/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts similarity index 100% rename from x-pack/plugins/siem/common/types/timeline/index.ts rename to x-pack/plugins/security_solution/common/types/timeline/index.ts diff --git a/x-pack/plugins/siem/common/types/timeline/note/index.ts b/x-pack/plugins/security_solution/common/types/timeline/note/index.ts similarity index 100% rename from x-pack/plugins/siem/common/types/timeline/note/index.ts rename to x-pack/plugins/security_solution/common/types/timeline/note/index.ts diff --git a/x-pack/plugins/siem/common/types/timeline/pinned_event/index.ts b/x-pack/plugins/security_solution/common/types/timeline/pinned_event/index.ts similarity index 100% rename from x-pack/plugins/siem/common/types/timeline/pinned_event/index.ts rename to x-pack/plugins/security_solution/common/types/timeline/pinned_event/index.ts diff --git a/x-pack/plugins/siem/common/utility_types.ts b/x-pack/plugins/security_solution/common/utility_types.ts similarity index 100% rename from x-pack/plugins/siem/common/utility_types.ts rename to x-pack/plugins/security_solution/common/utility_types.ts diff --git a/x-pack/plugins/siem/cypress/.eslintrc.json b/x-pack/plugins/security_solution/cypress/.eslintrc.json similarity index 100% rename from x-pack/plugins/siem/cypress/.eslintrc.json rename to x-pack/plugins/security_solution/cypress/.eslintrc.json diff --git a/x-pack/plugins/siem/cypress/.gitignore b/x-pack/plugins/security_solution/cypress/.gitignore similarity index 100% rename from x-pack/plugins/siem/cypress/.gitignore rename to x-pack/plugins/security_solution/cypress/.gitignore diff --git a/x-pack/plugins/security_solution/cypress/README.md b/x-pack/plugins/security_solution/cypress/README.md new file mode 100644 index 0000000000000..ad3af2aaa4e8a --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/README.md @@ -0,0 +1,355 @@ +# Cypress Tests + +The `security_solution/cypress` directory contains end to end tests, (plus a few tests +that rely on mocked API calls), that execute via [Cypress](https://www.cypress.io/). + +Cypress tests may be run against: + +- A local Kibana instance, interactively or via the command line. Credentials +are specified via `kibana.dev.yml` or environment variables. +- A remote Elastic Cloud instance (override `baseUrl`), interactively or via +the command line. Again, credentials are specified via `kibana.dev.yml` or +environment variables. +- As part of CI (override `baseUrl` and pass credentials via the +`CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD` +environment variables), via command line. + +At present, Cypress tests are only executed manually. They are **not** yet +integrated in the Kibana CI infrastructure, and therefore do **not** run +automatically when you submit a PR. + +## Smoke Tests + +Smoke Tests are located in `security_solution/cypress/integration/smoke_tests` + +## Structure + +### Tasks + +_Tasks_ are functions that my be re-used across tests. Inside the _tasks_ folder there are some other folders that represents +the page to which we will perform the actions. For each folder we are going to create a file for each one of the sections that + has the page. + +i.e. +- tasks + - hosts + - events.ts + +### Screens + +In _screens_ folder we are going to find all the elements we want to interact in our tests. Inside _screens_ fonder there +are some other folders that represents the page that contains the elements the tests are going to interact with. For each +folder we are going to create a file for each one of the sections that the page has. + +i.e. +- tasks + - hosts + - events.ts + +## Mock Data + +We prefer not to mock API responses in most of our smoke tests, but sometimes +it's necessary because a test must assert that a specific value is rendered, +and it's not possible to derive that value based on the data in the +environment where tests are running. + +Mocked responses API from the server are located in `security_solution/cypress/fixtures`. + +## Speeding up test execution time + +Loading the web page takes a big amount of time, in order to minimize that impact, the following points should be +taken into consideration until another solution is implemented: + +- Don't refresh the page for every test to clean the state of it. +- Instead, group the tests that are similar in different contexts. +- For every context login only once, clean the state between tests if needed without re-loading the page. +- All tests in a spec file must be order-independent. + - If you need to reload the page to make the tests order-independent, consider to create a new context. + +Remember that minimizing the number of times the web page is loaded, we minimize as well the execution time. + +## Authentication + +When running tests, there are two ways to specify the credentials used to +authenticate with Kibana: + +- Via `kibana.dev.yml` (recommended for developers) +- Via the `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD` +environment variables (recommended for CI), or when testing a remote Kibana +instance, e.g. in Elastic Cloud. + +Note: Tests that use the `login()` test helper function for authentication will +automatically use the `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD` +environment variables when they are defined, and fall back to the values in +`config/kibana.dev.yml` when they are unset. + +### Content Security Policy (CSP) Settings + +Your local or cloud Kibana server must have the `csp.strict: false` setting +configured in `kibana.dev.yml`, or `kibana.yml`, as shown in the example below: + +```yaml +csp.strict: false +``` + +The above setting is required to prevent the _Please upgrade +your browser_ / _This Kibana installation has strict security requirements +enabled that your current browser does not meet._ warning that's displayed for +unsupported user agents, like the one reported by Cypress when running tests. + +### Example `kibana.dev.yml` + +If you're a developer running tests interactively or on the command line, the +easiset way to specify the credentials used for authentication is to update + `kibana.dev.yml` per the following example: + +```yaml +csp.strict: false +elasticsearch: + username: 'elastic' + password: '' + hosts: ['https://:9200'] +``` + +## Running (Headless) Tests on the Command Line as a Jenkins execution (The preferred way) + +To run (headless) tests as a Jenkins execution. + +1. First bootstrap kibana changes from the Kibana root directory: + +```sh +yarn kbn bootstrap +``` + +2. Launch Cypress command line test runner: + +```sh +cd x-pack/plugins/security_solution +yarn cypress:run-as-ci +``` + +Note that with this type of execution you don't need to have running a kibana and elasticsearch instance. This is because + the command, as it would happen in the CI, will launch the instances. The elasticsearch instance will be fed data + found in: `x-pack/test/security_solution_cypress/es_archives` + +As in this case we want to mimic a CI execution we want to execute the tests with the same set of data, this is why +in this case does not make sense to override Cypress environment variables. + +### Test data + +As mentioned above, when running the tests as Jenkins the tests are populated with data ("archives") found in: `x-pack/test/security_solution_cypress/es_archives`. + +By default, each test is populated with some base data: an empty kibana index and a set of auditbeat data (the `empty_kibana` and `auditbeat` archives, respectively). This is usually enough to cover most of the scenarios that we are testing. + +#### Running tests with additional archives + +When the base data is insufficient, one can specify additional archives. Use `esArchiverLoad` to load the necessary archive, and `esArchiverUnload` to remove the archive from elasticsearch: + +```typescript +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; + +describe('This are going to be a set of tests', () => { + before(() => { + esArchiverLoad('name_of_the_data_set_you_want_to_load'); + }); + + after(() => { + esArchiverUnload('name_of_the_data_set_you_want_to_unload'); + }); + + it('Going to test something awesome', () => { + hereGoesYourAwesomeTestcode + }); +}); + +``` + +Note that loading and unloading data take a significant amount of time, so try to minimize their use. + +### Current archives + +The current archives can be found in `x-pack/test/security_solution_cypress/es_archives/`. + +- auditbeat + - Auditbeat data generated in Sep, 2019 with the following hosts present: + - suricata-iowa + - siem-kibana + - siem-es + - jessie +- closed_alerts + - Set of data with 108 closed alerts linked to "Alerts test" custom rule. +- custome_rules + - Set if data with just 4 custom activated rules. +- empty_kibana + - Empty kibana board. +- prebuilt_rules_loaded + - Elastic prebuilt loaded rules and deactivated. +- alerts + - Set of data with 108 opened alerts linked to "Alerts test" custom rule. + +### How to generate a new archive + +We are using es_archiver in order to manage the data that our Cypress tests needs. + +1. Setup if possible a clean instance of kibana and elasticsearch (if not, possible please try to clean the data that you are going to generate). +2. With the kibana and elasticsearch instance up and running, create the data that you need for your test. +3. When you are sure that you have all the data you need run the following command from: `x-pack/plugins/security_solution` + +```sh +node ../../../scripts/es_archiver save --dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url http://:@: +``` + +Example: +```sh +node ../../../scripts/es_archiver save custom_rules ".kibana",".siem-signal*" --dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url http://elastic:changeme@localhost:9220 +``` + +Note that the command is going to create the folder if does not exist in the directory with the imported data. + + +## Running Tests Interactively + +Use the Cypress interactive test runner to develop and debug specific tests +by adding a `.only` to the test you're developing, or click on a specific +spec in the interactive test runner to run just the tests in that spec. + +To run and debug tests in interactively via the Cypress test runner: + +1. Disable CSP on the local or remote Kibana instance, as described in the +_Content Security Policy (CSP) Settings_ section above. + +2. To specify the credentials required for authentication, configure +`config/kibana.dev.yml`, as described in the _Server and Authentication +Requirements_ section above, or specify them via environment variables +as described later in this section. + +3. Start a local instance of the Kibana development server (only if testing against a +local host): + +```sh +yarn start --no-base-path +``` + +4. Launch the Cypress interactive test runner via one of the following options: + +- To run tests interactively against the default (local) host specified by +`baseUrl`, as configured in `plugins/security_solution/cypress.json`: + +```sh +cd x-pack/plugins/security_solution +yarn cypress:open +``` + +- To (optionally) run tests interactively against a different host, pass the +`CYPRESS_baseUrl` environment variable on the command line when launching the +test runner, as shown in the following example: + +```sh +cd x-pack/plugins/security_solution +CYPRESS_baseUrl=http://localhost:5601 yarn cypress:open +``` + +- To (optionally) override username and password via environment variables when +running tests interactively: + +```sh +cd x-pack/plugins/security_solution +CYPRESS_baseUrl=http://localhost:5601 CYPRESS_ELASTICSEARCH_USERNAME=elastic CYPRESS_ELASTICSEARCH_PASSWORD= yarn cypress:open +``` + +5. Click the `Run all specs` button in the Cypress test runner (after adding +a `.only` to an `it` or `describe` block). + +## Running (Headless) Tests on the Command Line + +To run (headless) tests on the command line: + +1. Disable CSP on the local or remote Kibana instance, as described in the +_Content Security Policy (CSP) Settings_ section above. + +2. To specify the credentials required for authentication, configure +`config/kibana.dev.yml`, as described in the _Server and Authentication +Requirements_ section above, or specify them via environment variables +as described later in this section. + +3. Start a local instance of the Kibana development server (only if testing against a +local host): + +```sh +yarn start --no-base-path +``` + +4. Launch the Cypress command line test runner via one of the following options: + +- To run tests on the command line against the default (local) host specified by +`baseUrl`, as configured in `plugins/security_solution/cypress.json`: + +```sh +cd x-pack/plugins/security_solution +yarn cypress:run +``` + +- To (optionally) run tests on the command line against a different host, pass +`CYPRESS_baseUrl` as an environment variable on the command line, as shown in +the following example: + +```sh +cd x-pack/plugins/security_solution +CYPRESS_baseUrl=http://localhost:5601 yarn cypress:run +``` + +- To (optionally) override username and password via environment variables when +running via the command line: + +```sh +cd x-pack/plugins/security_solution +CYPRESS_baseUrl=http://localhost:5601 CYPRESS_ELASTICSEARCH_USERNAME=elastic CYPRESS_ELASTICSEARCH_PASSWORD= yarn cypress:run +``` + +## Reporting + +When Cypress tests are run on the command line via `yarn cypress:run`, +reporting artifacts are generated under the `target` directory in the root +of the Kibana, as detailed for each artifact type in the sections below. + +### HTML Reports + +An HTML report (e.g. for email notifications) is output to: + +``` +target/kibana-security-solution/cypress/results/output.html +``` + +### Screenshots + +Screenshots of failed tests are output to: + +``` +target/kibana-security-solution/cypress/screenshots +``` + +### `junit` Reports + +The Kibana CI process reports `junit` test results from the `target/junit` directory. + +Cypress `junit` reports are generated in `target/kibana-security-solution/cypress/results` +and copied to the `target/junit` directory. + +### Videos (optional) + +Videos are disabled by default, but can optionally be enabled by setting the +`CYPRESS_video=true` environment variable: + +``` +CYPRESS_video=true yarn cypress:run +``` + +Videos are (optionally) output to: + +``` +target/kibana-security-solution/cypress/videos +``` + +## Linting + +Optional linting rules for Cypress and linting setup can be found [here](https://github.com/cypress-io/eslint-plugin-cypress#usage) diff --git a/x-pack/plugins/security_solution/cypress/cypress.json b/x-pack/plugins/security_solution/cypress/cypress.json new file mode 100644 index 0000000000000..b097b0432e75d --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/cypress.json @@ -0,0 +1,8 @@ +{ + "baseUrl": "http://localhost:5601", + "defaultCommandTimeout": 120000, + "screenshotsFolder": "../../../target/kibana-security-solution/cypress/screenshots", + "trashAssetsBeforeRuns": false, + "video": false, + "videosFolder": "../../../target/kibana-security-solution/cypress/videos" +} diff --git a/x-pack/plugins/siem/cypress/fixtures/overview.json b/x-pack/plugins/security_solution/cypress/fixtures/overview.json similarity index 100% rename from x-pack/plugins/siem/cypress/fixtures/overview.json rename to x-pack/plugins/security_solution/cypress/fixtures/overview.json diff --git a/x-pack/plugins/siem/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts similarity index 90% rename from x-pack/plugins/siem/cypress/integration/cases.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/cases.spec.ts index 8f35a3209c69d..bb2dffe8ddd7d 100644 --- a/x-pack/plugins/siem/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -84,22 +84,14 @@ describe('Cases', () => { const expectedTags = case1.tags.join(''); cy.get(CASE_DETAILS_PAGE_TITLE).should('have.text', case1.name); cy.get(CASE_DETAILS_STATUS).should('have.text', 'open'); - cy.get(CASE_DETAILS_USER_ACTION) - .eq(USER) - .should('have.text', case1.reporter); - cy.get(CASE_DETAILS_USER_ACTION) - .eq(ACTION) - .should('have.text', 'added description'); + cy.get(CASE_DETAILS_USER_ACTION).eq(USER).should('have.text', case1.reporter); + cy.get(CASE_DETAILS_USER_ACTION).eq(ACTION).should('have.text', 'added description'); cy.get(CASE_DETAILS_DESCRIPTION).should( 'have.text', `${case1.description} ${case1.timeline.title}` ); - cy.get(CASE_DETAILS_USERNAMES) - .eq(REPORTER) - .should('have.text', case1.reporter); - cy.get(CASE_DETAILS_USERNAMES) - .eq(PARTICIPANTS) - .should('have.text', case1.reporter); + cy.get(CASE_DETAILS_USERNAMES).eq(REPORTER).should('have.text', case1.reporter); + cy.get(CASE_DETAILS_USERNAMES).eq(PARTICIPANTS).should('have.text', case1.reporter); cy.get(CASE_DETAILS_TAGS).should('have.text', expectedTags); cy.get(CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN).should('have.attr', 'disabled'); diff --git a/x-pack/plugins/siem/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts similarity index 84% rename from x-pack/plugins/siem/cypress/integration/cases_connectors.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts index 9be9067a38ee8..ae4bb82ebfee6 100644 --- a/x-pack/plugins/siem/cypress/integration/cases_connectors.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts @@ -21,7 +21,7 @@ import { CASES } from '../urls/navigation'; describe.skip('Cases connectors', () => { before(() => { cy.server(); - cy.route('POST', '**/api/action').as('createConnector'); + cy.route('POST', '**/api/actions/action').as('createConnector'); cy.route('POST', '**/api/cases/configure').as('saveConnector'); }); @@ -31,16 +31,12 @@ describe.skip('Cases connectors', () => { openAddNewConnectorOption(); addServiceNowConnector(serviceNowConnector); - cy.wait('@createConnector') - .its('status') - .should('eql', 200); + cy.wait('@createConnector').its('status').should('eql', 200); cy.get(TOASTER).should('have.text', "Created 'New connector'"); selectLastConnectorCreated(); - cy.wait('@saveConnector', { timeout: 10000 }) - .its('status') - .should('eql', 200); + cy.wait('@saveConnector', { timeout: 10000 }).its('status').should('eql', 200); cy.get(TOASTER).should('have.text', 'Saved external connection settings'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/detections.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detections.spec.ts new file mode 100644 index 0000000000000..23e84070e93ae --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/detections.spec.ts @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + NUMBER_OF_ALERTS, + OPEN_CLOSE_ALERTS_BTN, + SELECTED_ALERTS, + SHOWING_ALERTS, + ALERTS, +} from '../screens/detections'; + +import { + closeFirstAlert, + closeAlerts, + goToClosedAlerts, + goToOpenedAlerts, + openFirstAlert, + openAlerts, + selectNumberOfAlerts, + waitForAlertsPanelToBeLoaded, + waitForAlerts, + waitForAlertsToBeLoaded, +} from '../tasks/detections'; +import { esArchiverLoad } from '../tasks/es_archiver'; +import { loginAndWaitForPage } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detections', () => { + context('Closing alerts', () => { + beforeEach(() => { + esArchiverLoad('alerts'); + loginAndWaitForPage(DETECTIONS); + }); + + it('Closes and opens alerts', () => { + waitForAlertsPanelToBeLoaded(); + waitForAlertsToBeLoaded(); + + cy.get(NUMBER_OF_ALERTS) + .invoke('text') + .then((numberOfAlerts) => { + cy.get(SHOWING_ALERTS).should('have.text', `Showing ${numberOfAlerts} alerts`); + + const numberOfAlertsToBeClosed = 3; + selectNumberOfAlerts(numberOfAlertsToBeClosed); + + cy.get(SELECTED_ALERTS).should( + 'have.text', + `Selected ${numberOfAlertsToBeClosed} alerts` + ); + + closeAlerts(); + waitForAlerts(); + cy.reload(); + waitForAlerts(); + + const expectedNumberOfAlertsAfterClosing = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).should( + 'have.text', + expectedNumberOfAlertsAfterClosing.toString() + ); + + cy.get(SHOWING_ALERTS).should( + 'have.text', + `Showing ${expectedNumberOfAlertsAfterClosing.toString()} alerts` + ); + + goToClosedAlerts(); + waitForAlerts(); + + cy.get(NUMBER_OF_ALERTS).should('have.text', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS).should( + 'have.text', + `Showing ${numberOfAlertsToBeClosed.toString()} alerts` + ); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); + + const numberOfAlertsToBeOpened = 1; + selectNumberOfAlerts(numberOfAlertsToBeOpened); + + cy.get(SELECTED_ALERTS).should('have.text', `Selected ${numberOfAlertsToBeOpened} alert`); + + openAlerts(); + waitForAlerts(); + cy.reload(); + waitForAlertsToBeLoaded(); + waitForAlerts(); + goToClosedAlerts(); + waitForAlerts(); + + const expectedNumberOfClosedAlertsAfterOpened = 2; + cy.get(NUMBER_OF_ALERTS).should( + 'have.text', + expectedNumberOfClosedAlertsAfterOpened.toString() + ); + cy.get(SHOWING_ALERTS).should( + 'have.text', + `Showing ${expectedNumberOfClosedAlertsAfterOpened.toString()} alerts` + ); + cy.get(ALERTS).should('have.length', expectedNumberOfClosedAlertsAfterOpened); + + goToOpenedAlerts(); + waitForAlerts(); + + const expectedNumberOfOpenedAlerts = + +numberOfAlerts - expectedNumberOfClosedAlertsAfterOpened; + cy.get(SHOWING_ALERTS).should( + 'have.text', + `Showing ${expectedNumberOfOpenedAlerts.toString()} alerts` + ); + + cy.get('[data-test-subj="server-side-event-count"]').should( + 'have.text', + expectedNumberOfOpenedAlerts.toString() + ); + }); + }); + + it('Closes one alert when more than one opened alerts are selected', () => { + waitForAlertsToBeLoaded(); + + cy.get(NUMBER_OF_ALERTS) + .invoke('text') + .then((numberOfAlerts) => { + const numberOfAlertsToBeClosed = 1; + const numberOfAlertsToBeSelected = 3; + + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); + + closeFirstAlert(); + cy.reload(); + waitForAlertsToBeLoaded(); + waitForAlerts(); + + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); + + goToClosedAlerts(); + waitForAlerts(); + + cy.get(NUMBER_OF_ALERTS) + .invoke('text') + .should('eql', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS) + .invoke('text') + .should('eql', `Showing ${numberOfAlertsToBeClosed.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); + }); + }); + }); + context('Opening alerts', () => { + beforeEach(() => { + esArchiverLoad('closed_alerts'); + loginAndWaitForPage(DETECTIONS); + }); + + it('Open one alert when more than one closed alerts are selected', () => { + waitForAlerts(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); + + cy.get(NUMBER_OF_ALERTS) + .invoke('text') + .then((numberOfAlerts) => { + const numberOfAlertsToBeOpened = 1; + const numberOfAlertsToBeSelected = 3; + + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); + + openFirstAlert(); + cy.reload(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); + waitForAlerts(); + + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeOpened; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); + + goToOpenedAlerts(); + waitForAlerts(); + + cy.get(NUMBER_OF_ALERTS) + .invoke('text') + .should('eql', numberOfAlertsToBeOpened.toString()); + cy.get(SHOWING_ALERTS) + .invoke('text') + .should('eql', `Showing ${numberOfAlertsToBeOpened.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeOpened); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/detections_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detections_timeline.spec.ts new file mode 100644 index 0000000000000..d3ddb2ad71e30 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/detections_timeline.spec.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ALERT_ID } from '../screens/detections'; +import { PROVIDER_BADGE } from '../screens/timeline'; + +import { + expandFirstAlert, + investigateFirstAlertInTimeline, + waitForAlertsPanelToBeLoaded, +} from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPage } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detections timeline', () => { + beforeEach(() => { + esArchiverLoad('timeline_alerts'); + loginAndWaitForPage(DETECTIONS); + }); + + afterEach(() => { + esArchiverUnload('timeline_alerts'); + }); + + it('Investigate alert in default timeline', () => { + waitForAlertsPanelToBeLoaded(); + expandFirstAlert(); + cy.get(ALERT_ID) + .first() + .invoke('text') + .then((eventId) => { + investigateFirstAlertInTimeline(); + cy.get(PROVIDER_BADGE).invoke('text').should('eql', `_id: "${eventId}"`); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts new file mode 100644 index 0000000000000..82b4f4f0fbe34 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + FIELDS_BROWSER_CHECKBOX, + FIELDS_BROWSER_CONTAINER, + FIELDS_BROWSER_SELECTED_CATEGORY_TITLE, +} from '../screens/fields_browser'; +import { + HEADER_SUBTITLE, + HOST_GEO_CITY_NAME_HEADER, + HOST_GEO_COUNTRY_NAME_HEADER, + INSPECT_MODAL, + LOAD_MORE, + LOCAL_EVENTS_COUNT, +} from '../screens/hosts/events'; +import { HEADERS_GROUP } from '../screens/timeline'; + +import { closeFieldsBrowser, filterFieldsBrowser } from '../tasks/fields_browser'; +import { loginAndWaitForPage } from '../tasks/login'; +import { openEvents } from '../tasks/hosts/main'; +import { + addsHostGeoCityNameToHeader, + addsHostGeoCountryNameToHeader, + closeModal, + dragAndDropColumn, + openEventsViewerFieldsBrowser, + opensInspectQueryModal, + resetFields, + waitsForEventsToBeLoaded, +} from '../tasks/hosts/events'; +import { clearSearchBar, kqlSearch } from '../tasks/siem_header'; + +import { HOSTS_PAGE } from '../urls/navigation'; + +const defaultHeadersInDefaultEcsCategory = [ + { id: '@timestamp' }, + { id: 'message' }, + { id: 'host.name' }, + { id: 'event.action' }, + { id: 'user.name' }, + { id: 'source.ip' }, + { id: 'destination.ip' }, +]; + +describe('Events Viewer', () => { + context('Fields rendering', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + }); + + beforeEach(() => { + openEventsViewerFieldsBrowser(); + }); + + afterEach(() => { + closeFieldsBrowser(); + cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); + }); + + it('displays the `default ECS` category (by default)', () => { + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).invoke('text').should('eq', 'default ECS'); + }); + + it('displays a checked checkbox for all of the default events viewer columns that are also in the default ECS category', () => { + defaultHeadersInDefaultEcsCategory.forEach((header) => + cy.get(FIELDS_BROWSER_CHECKBOX(header.id)).should('be.checked') + ); + }); + }); + + context('Events viewer query modal', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + }); + + after(() => { + closeModal(); + cy.get(INSPECT_MODAL).should('not.exist'); + }); + + it('launches the inspect query modal when the inspect button is clicked', () => { + waitsForEventsToBeLoaded(); + opensInspectQueryModal(); + cy.get(INSPECT_MODAL).should('exist'); + }); + }); + + context('Events viewer fields behaviour', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + }); + + beforeEach(() => { + openEventsViewerFieldsBrowser(); + }); + + it('adds a field to the events viewer when the user clicks the checkbox', () => { + const filterInput = 'host.geo.c'; + + filterFieldsBrowser(filterInput); + cy.get(HOST_GEO_CITY_NAME_HEADER).should('not.exist'); + addsHostGeoCityNameToHeader(); + closeFieldsBrowser(); + cy.get(HOST_GEO_CITY_NAME_HEADER).should('exist'); + }); + + it('resets all fields in the events viewer when `Reset Fields` is clicked', () => { + const filterInput = 'host.geo.c'; + + filterFieldsBrowser(filterInput); + cy.get(HOST_GEO_COUNTRY_NAME_HEADER).should('not.exist'); + addsHostGeoCountryNameToHeader(); + resetFields(); + cy.get(HOST_GEO_COUNTRY_NAME_HEADER).should('not.exist'); + }); + }); + + context('Events behaviour', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + waitsForEventsToBeLoaded(); + }); + + afterEach(() => { + clearSearchBar(); + }); + + it('filters the events by applying filter criteria from the search bar at the top of the page', () => { + const filterInput = 'aa7ca589f1b8220002f2fc61c64cfbf1'; // this will never match real data + cy.get(HEADER_SUBTITLE) + .invoke('text') + .then((initialNumberOfEvents) => { + kqlSearch(`${filterInput}{enter}`); + cy.get(HEADER_SUBTITLE).invoke('text').should('not.equal', initialNumberOfEvents); + }); + }); + + it('loads more events when the load more button is clicked', () => { + const defaultNumberOfLoadedEvents = '25'; + cy.get(LOCAL_EVENTS_COUNT).invoke('text').should('equal', defaultNumberOfLoadedEvents); + + cy.get(LOAD_MORE).click({ force: true }); + + cy.get(LOCAL_EVENTS_COUNT).invoke('text').should('not.equal', defaultNumberOfLoadedEvents); + }); + }); + + context.skip('Events columns', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + waitsForEventsToBeLoaded(); + }); + + afterEach(() => { + openEventsViewerFieldsBrowser(); + resetFields(); + }); + + it('re-orders columns via drag and drop', () => { + const originalColumnOrder = + '@timestampmessagehost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; + const expectedOrderAfterDragAndDrop = + 'message@timestamphost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; + + cy.get(HEADERS_GROUP).invoke('text').should('equal', originalColumnOrder); + dragAndDropColumn({ column: 0, newPosition: 1 }); + cy.get(HEADERS_GROUP).invoke('text').should('equal', expectedOrderAfterDragAndDrop); + }); + }); +}); diff --git a/x-pack/plugins/siem/cypress/integration/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts similarity index 90% rename from x-pack/plugins/siem/cypress/integration/fields_browser.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts index 8dddd97f2d830..b058c9fb51fd1 100644 --- a/x-pack/plugins/siem/cypress/integration/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts @@ -58,9 +58,7 @@ describe('Fields Browser', () => { }); it('displays the `default ECS` category (by default)', () => { - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) - .invoke('text') - .should('eq', 'default ECS'); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).invoke('text').should('eq', 'default ECS'); }); it('the `defaultECS` (selected) category count matches the default timeline header count', () => { @@ -70,7 +68,7 @@ describe('Fields Browser', () => { }); it('displays a checked checkbox for all of the default timeline columns', () => { - defaultHeaders.forEach(header => + defaultHeaders.forEach((header) => cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked') ); }); @@ -80,9 +78,7 @@ describe('Fields Browser', () => { filterFieldsBrowser(filterInput); - cy.get(FIELDS_BROWSER_CATEGORIES_COUNT) - .invoke('text') - .should('eq', '2 categories'); + cy.get(FIELDS_BROWSER_CATEGORIES_COUNT).invoke('text').should('eq', '2 categories'); }); it('displays a search results label with the expected count of fields matching the filter input', () => { @@ -92,10 +88,10 @@ describe('Fields Browser', () => { cy.get(FIELDS_BROWSER_HOST_CATEGORIES_COUNT) .invoke('text') - .then(hostCategoriesCount => { + .then((hostCategoriesCount) => { cy.get(FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT) .invoke('text') - .then(systemCategoriesCount => { + .then((systemCategoriesCount) => { cy.get(FIELDS_BROWSER_FIELDS_COUNT) .invoke('text') .should('eq', `${+hostCategoriesCount + +systemCategoriesCount} fields`); @@ -108,9 +104,7 @@ describe('Fields Browser', () => { filterFieldsBrowser(filterInput); - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT) - .invoke('text') - .should('eq', '4'); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT).invoke('text').should('eq', '4'); }); }); @@ -140,9 +134,7 @@ describe('Fields Browser', () => { const category = 'host'; filterFieldsBrowser(category); - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) - .invoke('text') - .should('eq', category); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).invoke('text').should('eq', category); }); it('adds a field to the timeline when the user clicks the checkbox', () => { diff --git a/x-pack/plugins/siem/cypress/integration/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts similarity index 94% rename from x-pack/plugins/siem/cypress/integration/inspect.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts index b6b4e7a72b8f6..d770eb6c761cf 100644 --- a/x-pack/plugins/siem/cypress/integration/inspect.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts @@ -30,7 +30,7 @@ describe('Inspect', () => { closesModal(); }); - INSPECT_HOSTS_BUTTONS_IN_SIEM.forEach(table => + INSPECT_HOSTS_BUTTONS_IN_SIEM.forEach((table) => it(`inspects the ${table.title}`, () => { openStatsAndTables(table); cy.get(INSPECT_MODAL).should('be.visible'); @@ -46,7 +46,7 @@ describe('Inspect', () => { closesModal(); }); - INSPECT_NETWORK_BUTTONS_IN_SIEM.forEach(table => + INSPECT_NETWORK_BUTTONS_IN_SIEM.forEach((table) => it(`inspects the ${table.title}`, () => { openStatsAndTables(table); cy.get(INSPECT_MODAL).should('be.visible'); diff --git a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts new file mode 100644 index 0000000000000..e96c1fe691966 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KQL_INPUT } from '../screens/siem_header'; + +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; + +import { + mlHostMultiHostKqlQuery, + mlHostMultiHostNullKqlQuery, + mlHostSingleHostKqlQuery, + mlHostSingleHostKqlQueryVariable, + mlHostSingleHostNullKqlQuery, + mlHostVariableHostKqlQuery, + mlHostVariableHostNullKqlQuery, + mlNetworkKqlQuery, + mlNetworkMultipleIpKqlQuery, + mlNetworkMultipleIpNullKqlQuery, + mlNetworkNullKqlQuery, + mlNetworkSingleIpKqlQuery, + mlNetworkSingleIpNullKqlQuery, +} from '../urls/ml_conditional_links'; + +describe('ml conditional links', () => { + it('sets the KQL from a single IP with a value for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('sets the KQL from a multiple IPs with a null for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2"))' + ); + }); + + it('sets the KQL from a multiple IPs with a value for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2")) and ((process.name: "conhost.exe" or process.name: "sc.exe"))' + ); + }); + + it('sets the KQL from a $ip$ with a value for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('sets the KQL from a single host name with a value for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('sets the KQL from a multiple host names with null for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '(host.name: "siem-windows" or host.name: "siem-suricata")' + ); + }); + + it('sets the KQL from a multiple host names with a value for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '(host.name: "siem-windows" or host.name: "siem-suricata") and ((process.name: "conhost.exe" or process.name: "sc.exe"))' + ); + }); + + it('sets the KQL from a undefined/null host name but with a value for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery); + cy.get(KQL_INPUT).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('redirects from a single IP with a null for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpNullKqlQuery); + cy.url().should( + 'include', + '/app/security#/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' + ); + }); + + it('redirects from a single IP with a value for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery); + cy.url().should( + 'include', + "/app/security#/network/ip/127.0.0.1/source?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + ); + }); + + it('redirects from a multiple IPs with a null for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery); + cy.url().should( + 'include', + "app/security#/network/flows?query=(language:kuery,query:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999))" + ); + }); + + it('redirects from a multiple IPs with a value for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery); + cy.url().should( + 'include', + "/app/security#/network/flows?query=(language:kuery,query:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + ); + }); + + it('redirects from a $ip$ with a null query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkNullKqlQuery); + cy.url().should( + 'include', + '/app/security#/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' + ); + }); + + it('redirects from a $ip$ with a value for the query', () => { + loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery); + cy.url().should( + 'include', + "/app/security#/network/flows?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + ); + }); + + it('redirects from a single host name with a null for the query', () => { + loginAndWaitForPageWithoutDateRange(mlHostSingleHostNullKqlQuery); + cy.url().should( + 'include', + '/app/security#/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))' + ); + }); + + it('redirects from a host name with a variable in the query', () => { + loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQueryVariable); + cy.url().should( + 'include', + '/app/security#/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))' + ); + }); + + it('redirects from a single host name with a value for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery); + cy.url().should( + 'include', + "/app/security#/hosts/siem-windows/anomalies?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); + + it('redirects from a multiple host names with null for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery); + cy.url().should( + 'include', + "/app/security#/hosts/anomalies?query=(language:kuery,query:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); + + it('redirects from a multiple host names with a value for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery); + cy.url().should( + 'include', + "/app/security#/hosts/anomalies?query=(language:kuery,query:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); + + it('redirects from a undefined/null host name with a null for the KQL', () => { + loginAndWaitForPageWithoutDateRange(mlHostVariableHostNullKqlQuery); + cy.url().should( + 'include', + '/app/security#/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))' + ); + }); + + it('redirects from a undefined/null host name but with a value for query', () => { + loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery); + cy.url().should( + 'include', + "/app/security#/hosts/anomalies?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); +}); diff --git a/x-pack/plugins/siem/cypress/integration/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts similarity index 79% rename from x-pack/plugins/siem/cypress/integration/navigation.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts index bebd5f7d679cf..a9e5a848d54a8 100644 --- a/x-pack/plugins/siem/cypress/integration/navigation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts @@ -16,26 +16,26 @@ describe('top-level navigation common to all pages in the SIEM app', () => { }); it('navigates to the Overview page', () => { navigateFromHeaderTo(OVERVIEW); - cy.url().should('include', '/siem#/overview'); + cy.url().should('include', '/security#/overview'); }); it('navigates to the Hosts page', () => { navigateFromHeaderTo(HOSTS); - cy.url().should('include', '/siem#/hosts'); + cy.url().should('include', '/security#/hosts'); }); it('navigates to the Network page', () => { navigateFromHeaderTo(NETWORK); - cy.url().should('include', '/siem#/network'); + cy.url().should('include', '/security#/network'); }); it('navigates to the Detections page', () => { navigateFromHeaderTo(DETECTIONS); - cy.url().should('include', '/siem#/detections'); + cy.url().should('include', '/security#/detections'); }); it('navigates to the Timelines page', () => { navigateFromHeaderTo(TIMELINES); - cy.url().should('include', '/siem#/timelines'); + cy.url().should('include', '/security#/timelines'); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts similarity index 77% rename from x-pack/plugins/siem/cypress/integration/overview.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index cadb4beca0f9e..55bcbfafaa092 100644 --- a/x-pack/plugins/siem/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -20,20 +20,16 @@ describe('Overview Page', () => { it('Host stats render with correct values', () => { expandHostStats(); - HOST_STATS.forEach(stat => { - cy.get(stat.domId) - .invoke('text') - .should('eq', stat.value); + HOST_STATS.forEach((stat) => { + cy.get(stat.domId).invoke('text').should('eq', stat.value); }); }); it('Network stats render with correct values', () => { expandNetworkStats(); - NETWORK_STATS.forEach(stat => { - cy.get(stat.domId) - .invoke('text') - .should('eq', stat.value); + NETWORK_STATS.forEach((stat) => { + cy.get(stat.domId).invoke('text').should('eq', stat.value); }); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts similarity index 94% rename from x-pack/plugins/siem/cypress/integration/pagination.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts index 482c97fe29c3b..e430520fe1dc4 100644 --- a/x-pack/plugins/siem/cypress/integration/pagination.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts @@ -32,13 +32,13 @@ describe('Pagination', () => { cy.get(PROCESS_NAME_FIELD) .first() .invoke('text') - .then(processNameFirstPage => { + .then((processNameFirstPage) => { goToThirdPage(); waitForUncommonProcessesToBeLoaded(); cy.get(PROCESS_NAME_FIELD) .first() .invoke('text') - .should(processNameSecondPage => { + .should((processNameSecondPage) => { expect(processNameFirstPage).not.to.eq(processNameSecondPage); }); }); @@ -54,7 +54,7 @@ describe('Pagination', () => { cy.get(PROCESS_NAME_FIELD) .first() .invoke('text') - .then(expectedThirdPageResult => { + .then((expectedThirdPageResult) => { openAuthentications(); waitForAuthenticationsToBeLoaded(); cy.get(FIRST_PAGE_SELECTOR).should('have.class', 'euiPaginationButton-isActive'); @@ -64,7 +64,7 @@ describe('Pagination', () => { cy.get(PROCESS_NAME_FIELD) .first() .invoke('text') - .should(actualThirdPageResult => { + .should((actualThirdPageResult) => { expect(expectedThirdPageResult).to.eq(actualThirdPageResult); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules.spec.ts new file mode 100644 index 0000000000000..e8f9411c149d4 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules.spec.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + FIFTH_RULE, + FIRST_RULE, + RULE_NAME, + RULE_SWITCH, + SECOND_RULE, + SEVENTH_RULE, +} from '../screens/alert_detection_rules'; + +import { + goToManageAlertDetectionRules, + waitForAlertsPanelToBeLoaded, + waitForAlertsIndexToBeCreated, +} from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { + activateRule, + sortByActivatedRules, + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, + waitForRuleToBeActivated, +} from '../tasks/alert_detection_rules'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detection rules', () => { + before(() => { + esArchiverLoad('prebuilt_rules_loaded'); + }); + + after(() => { + esArchiverUnload('prebuilt_rules_loaded'); + }); + + it('Sorts by activated rules', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + cy.get(RULE_NAME) + .eq(FIFTH_RULE) + .invoke('text') + .then((fifthRuleName) => { + activateRule(FIFTH_RULE); + waitForRuleToBeActivated(); + cy.get(RULE_NAME) + .eq(SEVENTH_RULE) + .invoke('text') + .then((seventhRuleName) => { + activateRule(SEVENTH_RULE); + waitForRuleToBeActivated(); + sortByActivatedRules(); + + cy.get(RULE_NAME) + .eq(FIRST_RULE) + .invoke('text') + .then((firstRuleName) => { + cy.get(RULE_NAME) + .eq(SECOND_RULE) + .invoke('text') + .then((secondRuleName) => { + const expectedRulesNames = `${firstRuleName} ${secondRuleName}`; + cy.wrap(expectedRulesNames).should('include', fifthRuleName); + cy.wrap(expectedRulesNames).should('include', seventhRuleName); + }); + }); + + cy.get(RULE_SWITCH).eq(FIRST_RULE).should('have.attr', 'role', 'switch'); + cy.get(RULE_SWITCH).eq(SECOND_RULE).should('have.attr', 'role', 'switch'); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_custom.spec.ts new file mode 100644 index 0000000000000..e5cec16c48a37 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_custom.spec.ts @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { newRule, totalNumberOfPrebuiltRulesInEsArchive } from '../objects/rule'; + +import { + ABOUT_FALSE_POSITIVES, + ABOUT_INVESTIGATION_NOTES, + ABOUT_MITRE, + ABOUT_RISK, + ABOUT_RULE_DESCRIPTION, + ABOUT_SEVERITY, + ABOUT_STEP, + ABOUT_TAGS, + ABOUT_URLS, + DEFINITION_CUSTOM_QUERY, + DEFINITION_INDEX_PATTERNS, + DEFINITION_TIMELINE, + DEFINITION_STEP, + INVESTIGATION_NOTES_MARKDOWN, + INVESTIGATION_NOTES_TOGGLE, + RULE_ABOUT_DETAILS_HEADER_TOGGLE, + RULE_NAME_HEADER, + SCHEDULE_LOOPBACK, + SCHEDULE_RUNS, + SCHEDULE_STEP, +} from '../screens/rule_details'; +import { + CUSTOM_RULES_BTN, + RISK_SCORE, + RULE_NAME, + RULES_ROW, + RULES_TABLE, + SEVERITY, + SHOWING_RULES_TEXT, +} from '../screens/alert_detection_rules'; + +import { + createAndActivateRule, + fillAboutRuleAndContinue, + fillDefineCustomRuleWithImportedQueryAndContinue, +} from '../tasks/create_new_rule'; +import { + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, +} from '../tasks/detections'; +import { + changeToThreeHundredRowsPerPage, + deleteFirstRule, + deleteSelectedRules, + filterByCustomRules, + goToCreateNewRule, + goToRuleDetails, + selectNumberOfRules, + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, + waitForRulesToBeLoaded, +} from '../tasks/alert_detection_rules'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detection rules, custom', () => { + before(() => { + esArchiverLoad('custom_rule_with_timeline'); + }); + + after(() => { + esArchiverUnload('custom_rule_with_timeline'); + }); + + it('Creates and activates a new custom rule', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + goToCreateNewRule(); + fillDefineCustomRuleWithImportedQueryAndContinue(newRule); + fillAboutRuleAndContinue(newRule); + createAndActivateRule(); + + cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)'); + + changeToThreeHundredRowsPerPage(); + waitForRulesToBeLoaded(); + + const expectedNumberOfRules = totalNumberOfPrebuiltRulesInEsArchive + 1; + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); + }); + + filterByCustomRules(); + + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', 1); + }); + cy.get(RULE_NAME).invoke('text').should('eql', newRule.name); + cy.get(RISK_SCORE).invoke('text').should('eql', newRule.riskScore); + cy.get(SEVERITY).invoke('text').should('eql', newRule.severity); + cy.get('[data-test-subj="rule-switch"]').should('have.attr', 'aria-checked', 'true'); + + goToRuleDetails(); + + let expectedUrls = ''; + newRule.referenceUrls.forEach((url) => { + expectedUrls = expectedUrls + url; + }); + let expectedFalsePositives = ''; + newRule.falsePositivesExamples.forEach((falsePositive) => { + expectedFalsePositives = expectedFalsePositives + falsePositive; + }); + let expectedTags = ''; + newRule.tags.forEach((tag) => { + expectedTags = expectedTags + tag; + }); + let expectedMitre = ''; + newRule.mitre.forEach((mitre) => { + expectedMitre = expectedMitre + mitre.tactic; + mitre.techniques.forEach((technique) => { + expectedMitre = expectedMitre + technique; + }); + }); + const expectedIndexPatterns = [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ]; + + cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${newRule.name} Beta`); + + cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', newRule.description); + cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newRule.severity); + cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', newRule.riskScore); + cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls); + cy.get(ABOUT_STEP) + .eq(ABOUT_FALSE_POSITIVES) + .invoke('text') + .should('eql', expectedFalsePositives); + cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre); + cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags); + + cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN); + + cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => { + cy.wrap(patterns).each((pattern, index) => { + cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]); + }); + }); + cy.get(DEFINITION_STEP) + .eq(DEFINITION_CUSTOM_QUERY) + .invoke('text') + .should('eql', `${newRule.customQuery} `); + cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None'); + + cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m'); + cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m'); + }); +}); + +describe('Deletes custom rules', () => { + beforeEach(() => { + esArchiverLoad('custom_rules'); + loginAndWaitForPageWithoutDateRange(DETECTIONS); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); + }); + + after(() => { + esArchiverUnload('custom_rules'); + }); + + it('Deletes one rule', () => { + cy.get(RULES_TABLE) + .find(RULES_ROW) + .then((rules) => { + const initialNumberOfRules = rules.length; + const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - 1; + + cy.get(SHOWING_RULES_TEXT) + .invoke('text') + .should('eql', `Showing ${initialNumberOfRules} rules`); + + deleteFirstRule(); + waitForRulesToBeLoaded(); + + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterDeletion); + }); + cy.get(SHOWING_RULES_TEXT) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfRulesAfterDeletion} rules`); + cy.get(CUSTOM_RULES_BTN) + .invoke('text') + .should('eql', `Custom rules (${expectedNumberOfRulesAfterDeletion})`); + }); + }); + + it('Deletes more than one rule', () => { + cy.get(RULES_TABLE) + .find(RULES_ROW) + .then((rules) => { + const initialNumberOfRules = rules.length; + const numberOfRulesToBeDeleted = 3; + const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - numberOfRulesToBeDeleted; + + selectNumberOfRules(numberOfRulesToBeDeleted); + deleteSelectedRules(); + waitForRulesToBeLoaded(); + + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterDeletion); + }); + cy.get(SHOWING_RULES_TEXT) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfRulesAfterDeletion} rule`); + cy.get(CUSTOM_RULES_BTN) + .invoke('text') + .should('eql', `Custom rules (${expectedNumberOfRulesAfterDeletion})`); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_export.spec.ts new file mode 100644 index 0000000000000..4a12990438999 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_export.spec.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, +} from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { exportFirstRule } from '../tasks/alert_detection_rules'; + +import { DETECTIONS } from '../urls/navigation'; + +const EXPECTED_EXPORTED_RULE_FILE_PATH = 'cypress/test_files/expected_rules_export.ndjson'; + +describe('Export rules', () => { + before(() => { + esArchiverLoad('custom_rules'); + cy.server(); + cy.route( + 'POST', + '**api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson*' + ).as('export'); + }); + + after(() => { + esArchiverUnload('custom_rules'); + }); + + it('Exports a custom rule', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); + exportFirstRule(); + cy.wait('@export').then((xhr) => { + cy.readFile(EXPECTED_EXPORTED_RULE_FILE_PATH).then(($expectedExportedJson) => { + cy.wrap(xhr.responseBody).should('eql', $expectedExportedJson); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_ml.spec.ts new file mode 100644 index 0000000000000..fd2dff27ad359 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_ml.spec.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { machineLearningRule, totalNumberOfPrebuiltRulesInEsArchive } from '../objects/rule'; + +import { + ABOUT_FALSE_POSITIVES, + ABOUT_MITRE, + ABOUT_RISK, + ABOUT_RULE_DESCRIPTION, + ABOUT_SEVERITY, + ABOUT_STEP, + ABOUT_TAGS, + ABOUT_URLS, + ANOMALY_SCORE, + DEFINITION_TIMELINE, + DEFINITION_STEP, + MACHINE_LEARNING_JOB_ID, + MACHINE_LEARNING_JOB_STATUS, + RULE_NAME_HEADER, + SCHEDULE_LOOPBACK, + SCHEDULE_RUNS, + SCHEDULE_STEP, + RULE_TYPE, +} from '../screens/rule_details'; +import { + CUSTOM_RULES_BTN, + RISK_SCORE, + RULE_NAME, + RULE_SWITCH, + RULES_ROW, + RULES_TABLE, + SEVERITY, +} from '../screens/alert_detection_rules'; + +import { + createAndActivateRule, + fillAboutRuleAndContinue, + fillDefineMachineLearningRuleAndContinue, + selectMachineLearningRuleType, +} from '../tasks/create_new_rule'; +import { + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, +} from '../tasks/detections'; +import { + changeToThreeHundredRowsPerPage, + filterByCustomRules, + goToCreateNewRule, + goToRuleDetails, + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, + waitForRulesToBeLoaded, +} from '../tasks/alert_detection_rules'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detection rules, machine learning', () => { + before(() => { + esArchiverLoad('prebuilt_rules_loaded'); + }); + + after(() => { + esArchiverUnload('prebuilt_rules_loaded'); + }); + + it('Creates and activates a new ml rule', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + goToCreateNewRule(); + selectMachineLearningRuleType(); + fillDefineMachineLearningRuleAndContinue(machineLearningRule); + fillAboutRuleAndContinue(machineLearningRule); + createAndActivateRule(); + + cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)'); + + changeToThreeHundredRowsPerPage(); + waitForRulesToBeLoaded(); + + const expectedNumberOfRules = totalNumberOfPrebuiltRulesInEsArchive + 1; + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); + }); + + filterByCustomRules(); + + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', 1); + }); + cy.get(RULE_NAME).invoke('text').should('eql', machineLearningRule.name); + cy.get(RISK_SCORE).invoke('text').should('eql', machineLearningRule.riskScore); + cy.get(SEVERITY).invoke('text').should('eql', machineLearningRule.severity); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetails(); + + let expectedUrls = ''; + machineLearningRule.referenceUrls.forEach((url) => { + expectedUrls = expectedUrls + url; + }); + let expectedFalsePositives = ''; + machineLearningRule.falsePositivesExamples.forEach((falsePositive) => { + expectedFalsePositives = expectedFalsePositives + falsePositive; + }); + let expectedTags = ''; + machineLearningRule.tags.forEach((tag) => { + expectedTags = expectedTags + tag; + }); + let expectedMitre = ''; + machineLearningRule.mitre.forEach((mitre) => { + expectedMitre = expectedMitre + mitre.tactic; + mitre.techniques.forEach((technique) => { + expectedMitre = expectedMitre + technique; + }); + }); + + cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${machineLearningRule.name} Beta`); + + cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', machineLearningRule.description); + cy.get(ABOUT_STEP) + .eq(ABOUT_SEVERITY) + .invoke('text') + .should('eql', machineLearningRule.severity); + cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', machineLearningRule.riskScore); + cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls); + cy.get(ABOUT_STEP) + .eq(ABOUT_FALSE_POSITIVES) + .invoke('text') + .should('eql', expectedFalsePositives); + cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre); + cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags); + + cy.get(DEFINITION_STEP).eq(RULE_TYPE).invoke('text').should('eql', 'Machine Learning'); + cy.get(DEFINITION_STEP) + .eq(ANOMALY_SCORE) + .invoke('text') + .should('eql', machineLearningRule.anomalyScoreThreshold); + cy.get(DEFINITION_STEP) + .get(MACHINE_LEARNING_JOB_STATUS) + .invoke('text') + .should('eql', 'Stopped'); + cy.get(DEFINITION_STEP) + .get(MACHINE_LEARNING_JOB_ID) + .invoke('text') + .should('eql', machineLearningRule.machineLearningJob); + + cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None'); + + cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m'); + cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m'); + }); +}); diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_prebuilt.spec.ts similarity index 83% rename from x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_prebuilt.spec.ts index 866302e81e1a0..2cd087b2ca5e1 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_prebuilt.spec.ts @@ -10,7 +10,7 @@ import { RELOAD_PREBUILT_RULES_BTN, RULES_ROW, RULES_TABLE, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { changeToThreeHundredRowsPerPage, @@ -22,11 +22,11 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForPrebuiltDetectionRulesToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoadEmptyKibana, esArchiverUnloadEmptyKibana } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -35,7 +35,7 @@ import { DETECTIONS } from '../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../objects/rule'; -describe('Signal detection rules, prebuilt rules', () => { +describe('Detection rules, prebuilt rules', () => { before(() => { esArchiverLoadEmptyKibana(); }); @@ -49,21 +49,19 @@ describe('Signal detection rules, prebuilt rules', () => { const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); - cy.get(ELASTIC_RULES_BTN) - .invoke('text') - .should('eql', expectedElasticRulesBtnText); + cy.get(ELASTIC_RULES_BTN).invoke('text').should('eql', expectedElasticRulesBtnText); changeToThreeHundredRowsPerPage(); waitForRulesToBeLoaded(); - cy.get(RULES_TABLE).then($table => { + cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); }); }); @@ -76,21 +74,19 @@ describe('Deleting prebuilt rules', () => { esArchiverLoadEmptyKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); - cy.get(ELASTIC_RULES_BTN) - .invoke('text') - .should('eql', expectedElasticRulesBtnText); + cy.get(ELASTIC_RULES_BTN).invoke('text').should('eql', expectedElasticRulesBtnText); changeToThreeHundredRowsPerPage(); waitForRulesToBeLoaded(); - cy.get(RULES_TABLE).then($table => { + cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); }); }); @@ -103,7 +99,7 @@ describe('Deleting prebuilt rules', () => { const numberOfRulesToBeSelected = 2; selectNumberOfRules(numberOfRulesToBeSelected); - cy.get(COLLAPSED_ACTION_BTN).each(collapsedItemActionBtn => { + cy.get(COLLAPSED_ACTION_BTN).each((collapsedItemActionBtn) => { cy.wrap(collapsedItemActionBtn).should('have.attr', 'disabled'); }); }); @@ -120,7 +116,7 @@ describe('Deleting prebuilt rules', () => { cy.get(ELASTIC_RULES_BTN) .invoke('text') .should('eql', `Elastic rules (${expectedNumberOfRulesAfterDeletion})`); - cy.get(RULES_TABLE).then($table => { + cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterDeletion); }); cy.get(RELOAD_PREBUILT_RULES_BTN).should('exist'); @@ -136,7 +132,7 @@ describe('Deleting prebuilt rules', () => { changeToThreeHundredRowsPerPage(); waitForRulesToBeLoaded(); - cy.get(RULES_TABLE).then($table => { + cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterRecovering); }); cy.get(ELASTIC_RULES_BTN) @@ -162,7 +158,7 @@ describe('Deleting prebuilt rules', () => { cy.get(ELASTIC_RULES_BTN) .invoke('text') .should('eql', `Elastic rules (${expectedNumberOfRulesAfterDeletion})`); - cy.get(RULES_TABLE).then($table => { + cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterDeletion); }); @@ -174,7 +170,7 @@ describe('Deleting prebuilt rules', () => { changeToThreeHundredRowsPerPage(); waitForRulesToBeLoaded(); - cy.get(RULES_TABLE).then($table => { + cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterRecovering); }); cy.get(ELASTIC_RULES_BTN) diff --git a/x-pack/plugins/siem/cypress/integration/timeline_data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts similarity index 97% rename from x-pack/plugins/siem/cypress/integration/timeline_data_providers.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts index 08eb3df57c7a0..243886752706d 100644 --- a/x-pack/plugins/siem/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts @@ -44,11 +44,11 @@ describe('timeline data providers', () => { cy.get(TIMELINE_DROPPED_DATA_PROVIDERS) .first() .invoke('text') - .then(dataProviderText => { + .then((dataProviderText) => { cy.get(HOSTS_NAMES_DRAGGABLE) .first() .invoke('text') - .should(hostname => { + .should((hostname) => { expect(dataProviderText).to.eq(`host.name: "${hostname}"AND`); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/timeline_flyout_button.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts similarity index 100% rename from x-pack/plugins/siem/cypress/integration/timeline_flyout_button.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts diff --git a/x-pack/plugins/siem/cypress/integration/timeline_search_or_filter.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts similarity index 97% rename from x-pack/plugins/siem/cypress/integration/timeline_search_or_filter.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts index f738ff792049a..0668b91ca4fc1 100644 --- a/x-pack/plugins/siem/cypress/integration/timeline_search_or_filter.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts @@ -24,7 +24,7 @@ describe('timeline search or filter KQL bar', () => { cy.get(SERVER_SIDE_EVENT_COUNT) .invoke('text') - .then(strCount => { + .then((strCount) => { const intCount = +strCount; cy.wrap(intCount).should('be.above', 0); }); diff --git a/x-pack/plugins/siem/cypress/integration/timeline_toggle_column.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts similarity index 100% rename from x-pack/plugins/siem/cypress/integration/timeline_toggle_column.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts diff --git a/x-pack/plugins/siem/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts similarity index 96% rename from x-pack/plugins/siem/cypress/integration/url_state.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index cd60745b19040..cf69a83dbf951 100644 --- a/x-pack/plugins/siem/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -197,10 +197,7 @@ describe('url state', () => { 'href', "#/link-to/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))" ); - cy.get(HOSTS_NAMES) - .first() - .invoke('text') - .should('eq', 'siem-kibana'); + cy.get(HOSTS_NAMES).first().invoke('text').should('eq', 'siem-kibana'); openFirstHostDetails(); clearSearchBar(); @@ -241,7 +238,7 @@ describe('url state', () => { cy.get(SERVER_SIDE_EVENT_COUNT) .invoke('text') - .then(strCount => { + .then((strCount) => { const intCount = +strCount; cy.wrap(intCount).should('be.above', 0); }); @@ -252,17 +249,15 @@ describe('url state', () => { cy.wait(5000); cy.url({ timeout: 30000 }).should('match', /\w*-\w*-\w*-\w*-\w*/); - cy.url().then(url => { + cy.url().then((url) => { const matched = url.match(/\w*-\w*-\w*-\w*-\w*/); const newTimelineId = matched && matched.length > 0 ? matched[0] : 'null'; expect(matched).to.have.lengthOf(1); closeTimeline(); cy.visit('/app/kibana'); - cy.visit(`/app/siem#/overview?timeline\=(id:'${newTimelineId}',isOpen:!t)`); - cy.contains('a', 'SIEM'); - cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE) - .invoke('text') - .should('not.equal', 'Updating'); + cy.visit(`/app/security#/overview?timeline\=(id:'${newTimelineId}',isOpen:!t)`); + cy.contains('a', 'Security'); + cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).invoke('text').should('not.equal', 'Updating'); cy.get(TIMELINE_TITLE).should('have.attr', 'value', timelineName); }); }); diff --git a/x-pack/plugins/siem/cypress/objects/case.ts b/x-pack/plugins/security_solution/cypress/objects/case.ts similarity index 100% rename from x-pack/plugins/siem/cypress/objects/case.ts rename to x-pack/plugins/security_solution/cypress/objects/case.ts diff --git a/x-pack/plugins/siem/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts similarity index 95% rename from x-pack/plugins/siem/cypress/objects/rule.ts rename to x-pack/plugins/security_solution/cypress/objects/rule.ts index 7ce8aa69f3339..d750fe212002d 100644 --- a/x-pack/plugins/siem/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -28,6 +28,7 @@ export interface CustomRule { falsePositivesExamples: string[]; mitre: Mitre[]; note: string; + timelineId: string; } export interface MachineLearningRule { @@ -56,7 +57,7 @@ const mitre2: Mitre = { }; export const newRule: CustomRule = { - customQuery: 'hosts.name: *', + customQuery: 'host.name: *', name: 'New Rule Test', description: 'The new rule description.', severity: 'High', @@ -66,6 +67,7 @@ export const newRule: CustomRule = { falsePositivesExamples: ['False1', 'False2'], mitre: [mitre1, mitre2], note: '# test markdown', + timelineId: '352c6110-9ffb-11ea-b3d8-857d6042d9bd', }; export const machineLearningRule: MachineLearningRule = { diff --git a/x-pack/plugins/siem/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts similarity index 100% rename from x-pack/plugins/siem/cypress/objects/timeline.ts rename to x-pack/plugins/security_solution/cypress/objects/timeline.ts diff --git a/x-pack/plugins/security_solution/cypress/plugins/index.js b/x-pack/plugins/security_solution/cypress/plugins/index.js new file mode 100644 index 0000000000000..35e2669f1611a --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/plugins/index.js @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +// eslint-disable-next-line import/no-extraneous-dependencies +const wp = require('@cypress/webpack-preprocessor'); + +module.exports = (on) => { + const options = { + webpackOptions: { + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: 'ts-loader', + options: { transpileOnly: true }, + }, + ], + }, + }, + }; + on('file:preprocessor', wp(options)); +}; diff --git a/x-pack/plugins/security_solution/cypress/reporter_config.json b/x-pack/plugins/security_solution/cypress/reporter_config.json new file mode 100644 index 0000000000000..b72a5cf077c6a --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/reporter_config.json @@ -0,0 +1,10 @@ +{ + "reporterEnabled": "mochawesome, mocha-junit-reporter", + "reporterOptions": { + "html": false, + "json": true, + "mochaFile": "../../../target/kibana-security-solution/cypress/results/TEST-security-solution-cypress-[hash].xml", + "overwrite": false, + "reportDir": "../../../target/kibana-security-solution/cypress/results" + } +} diff --git a/x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alert_detection_rules.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts rename to x-pack/plugins/security_solution/cypress/screens/alert_detection_rules.ts diff --git a/x-pack/plugins/siem/cypress/screens/all_cases.ts b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/all_cases.ts rename to x-pack/plugins/security_solution/cypress/screens/all_cases.ts diff --git a/x-pack/plugins/siem/cypress/screens/case_details.ts b/x-pack/plugins/security_solution/cypress/screens/case_details.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/case_details.ts rename to x-pack/plugins/security_solution/cypress/screens/case_details.ts diff --git a/x-pack/plugins/siem/cypress/screens/configure_cases.ts b/x-pack/plugins/security_solution/cypress/screens/configure_cases.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/configure_cases.ts rename to x-pack/plugins/security_solution/cypress/screens/configure_cases.ts diff --git a/x-pack/plugins/siem/cypress/screens/create_new_case.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/create_new_case.ts rename to x-pack/plugins/security_solution/cypress/screens/create_new_case.ts diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts new file mode 100644 index 0000000000000..bc0740554bc52 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ABOUT_CONTINUE_BTN = '[data-test-subj="about-continue"]'; + +export const ADD_FALSE_POSITIVE_BTN = + '[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text'; + +export const ADD_REFERENCE_URL_BTN = + '[data-test-subj="detectionEngineStepAboutRuleReferenceUrls"] .euiButtonEmpty__text'; + +export const ANOMALY_THRESHOLD_INPUT = '[data-test-subj="anomalyThresholdSlider"] .euiFieldNumber'; + +export const MITRE_BTN = '[data-test-subj="addMitre"]'; + +export const ADVANCED_SETTINGS_BTN = '[data-test-subj="advancedSettings"] .euiAccordion__button'; + +export const CREATE_AND_ACTIVATE_BTN = '[data-test-subj="create-activate"]'; + +export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]'; + +export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]'; + +export const IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK = + '[data-test-subj="importQueryFromSavedTimeline"]'; + +export const INVESTIGATION_NOTES_TEXTAREA = + '[data-test-subj="detectionEngineStepAboutRuleNote"] textarea'; + +export const FALSE_POSITIVES_INPUT = + '[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] input'; + +export const MACHINE_LEARNING_DROPDOWN = '[data-test-subj="mlJobSelect"] button'; + +export const MACHINE_LEARNING_LIST = '.euiContextMenuItem__text'; + +export const MACHINE_LEARNING_TYPE = '[data-test-subj="machineLearningRuleType"]'; + +export const MITRE_TACTIC = '.euiContextMenuItem__text'; + +export const MITRE_TACTIC_DROPDOWN = '[data-test-subj="mitreTactic"]'; + +export const MITRE_TECHNIQUES_INPUT = + '[data-test-subj="mitreTechniques"] [data-test-subj="comboBoxSearchInput"]'; + +export const REFERENCE_URLS_INPUT = + '[data-test-subj="detectionEngineStepAboutRuleReferenceUrls"] input'; + +export const RISK_INPUT = '.euiRangeInput'; + +export const RULE_DESCRIPTION_INPUT = + '[data-test-subj="detectionEngineStepAboutRuleDescription"] [data-test-subj="input"]'; + +export const RULE_NAME_INPUT = + '[data-test-subj="detectionEngineStepAboutRuleName"] [data-test-subj="input"]'; + +export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]'; + +export const SEVERITY_DROPDOWN = + '[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]'; + +export const TAGS_INPUT = + '[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxSearchInput"]'; diff --git a/x-pack/plugins/siem/cypress/screens/date_picker.ts b/x-pack/plugins/security_solution/cypress/screens/date_picker.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/date_picker.ts rename to x-pack/plugins/security_solution/cypress/screens/date_picker.ts diff --git a/x-pack/plugins/security_solution/cypress/screens/detections.ts b/x-pack/plugins/security_solution/cypress/screens/detections.ts new file mode 100644 index 0000000000000..b915bcba2f880 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/detections.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const CLOSED_ALERTS_BTN = '[data-test-subj="closedAlerts"]'; + +export const EXPAND_ALERT_BTN = '[data-test-subj="expand-event"]'; + +export const LOADING_ALERTS_PANEL = '[data-test-subj="loading-alerts-panel"]'; + +export const MANAGE_ALERT_DETECTION_RULES_BTN = '[data-test-subj="manage-alert-detection-rules"]'; + +export const NUMBER_OF_ALERTS = '[data-test-subj="server-side-event-count"] .euiBadge__text'; + +export const OPEN_CLOSE_ALERT_BTN = '[data-test-subj="update-alert-status-button"]'; + +export const OPEN_CLOSE_ALERTS_BTN = '[data-test-subj="openCloseAlert"] button'; + +export const OPENED_ALERTS_BTN = '[data-test-subj="openAlerts"]'; + +export const SELECTED_ALERTS = '[data-test-subj="selectedAlerts"]'; + +export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]'; + +export const SHOWING_ALERTS = '[data-test-subj="showingAlerts"]'; + +export const ALERTS = '[data-test-subj="event"]'; + +export const ALERT_ID = '[data-test-subj="draggable-content-_id"]'; + +export const ALERT_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/plugins/siem/cypress/screens/fields_browser.ts b/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/fields_browser.ts rename to x-pack/plugins/security_solution/cypress/screens/fields_browser.ts diff --git a/x-pack/plugins/siem/cypress/screens/hosts/all_hosts.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/all_hosts.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/hosts/all_hosts.ts rename to x-pack/plugins/security_solution/cypress/screens/hosts/all_hosts.ts diff --git a/x-pack/plugins/siem/cypress/screens/hosts/authentications.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/authentications.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/hosts/authentications.ts rename to x-pack/plugins/security_solution/cypress/screens/hosts/authentications.ts diff --git a/x-pack/plugins/siem/cypress/screens/hosts/events.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/hosts/events.ts rename to x-pack/plugins/security_solution/cypress/screens/hosts/events.ts diff --git a/x-pack/plugins/siem/cypress/screens/hosts/main.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/hosts/main.ts rename to x-pack/plugins/security_solution/cypress/screens/hosts/main.ts diff --git a/x-pack/plugins/siem/cypress/screens/hosts/uncommon_processes.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/uncommon_processes.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/hosts/uncommon_processes.ts rename to x-pack/plugins/security_solution/cypress/screens/hosts/uncommon_processes.ts diff --git a/x-pack/plugins/siem/cypress/screens/inspect.ts b/x-pack/plugins/security_solution/cypress/screens/inspect.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/inspect.ts rename to x-pack/plugins/security_solution/cypress/screens/inspect.ts diff --git a/x-pack/plugins/siem/cypress/screens/network/flows.ts b/x-pack/plugins/security_solution/cypress/screens/network/flows.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/network/flows.ts rename to x-pack/plugins/security_solution/cypress/screens/network/flows.ts diff --git a/x-pack/plugins/siem/cypress/screens/overview.ts b/x-pack/plugins/security_solution/cypress/screens/overview.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/overview.ts rename to x-pack/plugins/security_solution/cypress/screens/overview.ts diff --git a/x-pack/plugins/siem/cypress/screens/pagination.ts b/x-pack/plugins/security_solution/cypress/screens/pagination.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/pagination.ts rename to x-pack/plugins/security_solution/cypress/screens/pagination.ts diff --git a/x-pack/plugins/siem/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/rule_details.ts rename to x-pack/plugins/security_solution/cypress/screens/rule_details.ts diff --git a/x-pack/plugins/siem/cypress/screens/siem_header.ts b/x-pack/plugins/security_solution/cypress/screens/siem_header.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/siem_header.ts rename to x-pack/plugins/security_solution/cypress/screens/siem_header.ts diff --git a/x-pack/plugins/siem/cypress/screens/siem_main.ts b/x-pack/plugins/security_solution/cypress/screens/siem_main.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/siem_main.ts rename to x-pack/plugins/security_solution/cypress/screens/siem_main.ts diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts new file mode 100644 index 0000000000000..bb232b752994a --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; + +export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; + +export const DRAGGABLE_HEADER = + '[data-test-subj="headers-group"] [data-test-subj="draggable-header"]'; + +export const HEADERS_GROUP = '[data-test-subj="headers-group"]'; + +export const ID_HEADER_FIELD = '[data-test-subj="timeline"] [data-test-subj="header-text-_id"]'; + +export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name-_id"]'; + +export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; + +export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; + +export const SEARCH_OR_FILTER_CONTAINER = + '[data-test-subj="timeline-search-or-filter-search-container"]'; + +export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; + +export const TIMELINE = (id: string) => { + return `[data-test-subj="title-${id}"]`; +}; + +export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]'; + +export const TIMELINE_DATA_PROVIDERS_EMPTY = + '[data-test-subj="dataProviders"] [data-test-subj="empty"]'; + +export const TIMELINE_DESCRIPTION = '[data-test-subj="timeline-description"]'; + +export const TIMELINE_DROPPED_DATA_PROVIDERS = '[data-test-subj="providerContainer"]'; + +export const TIMELINE_FIELDS_BUTTON = + '[data-test-subj="timeline"] [data-test-subj="show-field-browser"]'; + +export const TIMELINE_FLYOUT_HEADER = '[data-test-subj="eui-flyout-header"]'; + +export const TIMELINE_FLYOUT_BODY = '[data-test-subj="eui-flyout-body"]'; + +export const TIMELINE_INSPECT_BUTTON = '[data-test-subj="inspect-empty-button"]'; + +export const TIMELINE_NOT_READY_TO_DROP_BUTTON = + '[data-test-subj="flyout-button-not-ready-to-drop"]'; + +export const TIMELINE_QUERY = '[data-test-subj="timelineQueryInput"]'; + +export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-gear"]'; + +export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; + +export const TIMESTAMP_HEADER_FIELD = '[data-test-subj="header-text-@timestamp"]'; + +export const TIMESTAMP_TOGGLE_FIELD = '[data-test-subj="toggle-field-@timestamp"]'; + +export const TOGGLE_TIMELINE_EXPAND_EVENT = '[data-test-subj="expand-event"]'; diff --git a/x-pack/plugins/siem/cypress/support/commands.js b/x-pack/plugins/security_solution/cypress/support/commands.js similarity index 87% rename from x-pack/plugins/siem/cypress/support/commands.js rename to x-pack/plugins/security_solution/cypress/support/commands.js index e697dbce0f249..d086981cb98f7 100644 --- a/x-pack/plugins/siem/cypress/support/commands.js +++ b/x-pack/plugins/security_solution/cypress/support/commands.js @@ -30,12 +30,12 @@ // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -Cypress.Commands.add('stubSIEMapi', function(dataFileName) { - cy.on('window:before:load', win => { +Cypress.Commands.add('stubSIEMapi', function (dataFileName) { + cy.on('window:before:load', (win) => { // @ts-ignore no null, this is a temp hack see issue above win.fetch = null; }); cy.server(); cy.fixture(dataFileName).as(`${dataFileName}JSON`); - cy.route('POST', 'api/siem/graphql', `@${dataFileName}JSON`); + cy.route('POST', 'api/solutions/security/graphql', `@${dataFileName}JSON`); }); diff --git a/x-pack/plugins/siem/cypress/support/index.d.ts b/x-pack/plugins/security_solution/cypress/support/index.d.ts similarity index 100% rename from x-pack/plugins/siem/cypress/support/index.d.ts rename to x-pack/plugins/security_solution/cypress/support/index.d.ts diff --git a/x-pack/plugins/security_solution/cypress/support/index.js b/x-pack/plugins/security_solution/cypress/support/index.js new file mode 100644 index 0000000000000..42abecd4b0bad --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/support/index.js @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; + +Cypress.Cookies.defaults({ + whitelist: 'sid', +}); + +Cypress.on('uncaught:exception', (err) => { + if (err.message.includes('ResizeObserver loop limit exceeded')) { + return false; + } +}); + +Cypress.on('window:before:load', (win) => { + win.fetch = null; + win.Blob = null; +}); + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/x-pack/plugins/security_solution/cypress/tasks/alert_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alert_detection_rules.ts new file mode 100644 index 0000000000000..9710e0e808ac5 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/alert_detection_rules.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + BULK_ACTIONS_BTN, + COLLAPSED_ACTION_BTN, + CREATE_NEW_RULE_BTN, + CUSTOM_RULES_BTN, + DELETE_RULE_ACTION_BTN, + DELETE_RULE_BULK_BTN, + LOAD_PREBUILT_RULES_BTN, + LOADING_INITIAL_PREBUILT_RULES_TABLE, + LOADING_SPINNER, + PAGINATION_POPOVER_BTN, + RELOAD_PREBUILT_RULES_BTN, + RULE_CHECKBOX, + RULE_NAME, + RULE_SWITCH, + RULE_SWITCH_LOADER, + RULES_TABLE, + SORT_RULES_BTN, + THREE_HUNDRED_ROWS, + EXPORT_ACTION_BTN, +} from '../screens/alert_detection_rules'; + +export const activateRule = (rulePosition: number) => { + cy.get(RULE_SWITCH).eq(rulePosition).click({ force: true }); +}; + +export const changeToThreeHundredRowsPerPage = () => { + cy.get(PAGINATION_POPOVER_BTN).click({ force: true }); + cy.get(THREE_HUNDRED_ROWS).click(); +}; + +export const deleteFirstRule = () => { + cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true }); + cy.get(DELETE_RULE_ACTION_BTN).click(); +}; + +export const deleteSelectedRules = () => { + cy.get(BULK_ACTIONS_BTN).click({ force: true }); + cy.get(DELETE_RULE_BULK_BTN).click(); +}; + +export const exportFirstRule = () => { + cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true }); + cy.get(EXPORT_ACTION_BTN).click(); + cy.get(EXPORT_ACTION_BTN).should('not.exist'); +}; + +export const filterByCustomRules = () => { + cy.get(CUSTOM_RULES_BTN).click({ force: true }); + cy.get(LOADING_SPINNER).should('exist'); + cy.get(LOADING_SPINNER).should('not.exist'); +}; + +export const goToCreateNewRule = () => { + cy.get(CREATE_NEW_RULE_BTN).click({ force: true }); +}; + +export const goToRuleDetails = () => { + cy.get(RULE_NAME).click({ force: true }); +}; + +export const loadPrebuiltDetectionRules = () => { + cy.get(LOAD_PREBUILT_RULES_BTN).should('exist').click({ force: true }); +}; + +export const reloadDeletedRules = () => { + cy.get(RELOAD_PREBUILT_RULES_BTN).click({ force: true }); +}; + +export const selectNumberOfRules = (numberOfRules: number) => { + for (let i = 0; i < numberOfRules; i++) { + cy.get(RULE_CHECKBOX).eq(i).click({ force: true }); + } +}; + +export const sortByActivatedRules = () => { + cy.get(SORT_RULES_BTN).click({ force: true }); + waitForRulesToBeLoaded(); + cy.get(SORT_RULES_BTN).click({ force: true }); + waitForRulesToBeLoaded(); +}; + +export const waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded = () => { + cy.get(LOADING_INITIAL_PREBUILT_RULES_TABLE).should('exist'); + cy.get(LOADING_INITIAL_PREBUILT_RULES_TABLE).should('not.exist'); +}; + +export const waitForPrebuiltDetectionRulesToBeLoaded = () => { + cy.get(LOAD_PREBUILT_RULES_BTN).should('not.exist'); + cy.get(RULES_TABLE).should('exist'); +}; + +export const waitForRuleToBeActivated = () => { + cy.get(RULE_SWITCH_LOADER).should('exist'); + cy.get(RULE_SWITCH_LOADER).should('not.exist'); +}; + +export const waitForRulesToBeLoaded = () => { + cy.get(LOADING_SPINNER).should('exist'); + cy.get(LOADING_SPINNER).should('not.exist'); +}; diff --git a/x-pack/plugins/siem/cypress/tasks/all_cases.ts b/x-pack/plugins/security_solution/cypress/tasks/all_cases.ts similarity index 100% rename from x-pack/plugins/siem/cypress/tasks/all_cases.ts rename to x-pack/plugins/security_solution/cypress/tasks/all_cases.ts diff --git a/x-pack/plugins/siem/cypress/tasks/case_details.ts b/x-pack/plugins/security_solution/cypress/tasks/case_details.ts similarity index 100% rename from x-pack/plugins/siem/cypress/tasks/case_details.ts rename to x-pack/plugins/security_solution/cypress/tasks/case_details.ts diff --git a/x-pack/plugins/siem/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts similarity index 100% rename from x-pack/plugins/siem/cypress/tasks/common.ts rename to x-pack/plugins/security_solution/cypress/tasks/common.ts diff --git a/x-pack/plugins/siem/cypress/tasks/configure_cases.ts b/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts similarity index 95% rename from x-pack/plugins/siem/cypress/tasks/configure_cases.ts rename to x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts index 6ba9e875c7cb0..8ff8fbf4b0cb7 100644 --- a/x-pack/plugins/siem/cypress/tasks/configure_cases.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts @@ -29,7 +29,7 @@ export const addServiceNowConnector = (connector: Connector) => { }; export const openAddNewConnectorOption = () => { - cy.get(MAIN_PAGE).then($page => { + cy.get(MAIN_PAGE).then(($page) => { if ($page.find(SERVICE_NOW_CONNECTOR_CARD).length !== 1) { cy.wait(1000); cy.get(CONNECTORS_DROPDOWN).click({ force: true }); @@ -42,7 +42,7 @@ export const selectLastConnectorCreated = () => { cy.get(CONNECTORS_DROPDOWN).click({ force: true }); cy.get('@createConnector') .its('response') - .then(response => { + .then((response) => { cy.get(CONNECTOR(response.body.id)).click(); }); }; diff --git a/x-pack/plugins/siem/cypress/tasks/create_new_case.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts similarity index 92% rename from x-pack/plugins/siem/cypress/tasks/create_new_case.ts rename to x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts index 491fdd84e9b38..2dac1065e23eb 100644 --- a/x-pack/plugins/siem/cypress/tasks/create_new_case.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts @@ -24,7 +24,7 @@ export const backToCases = () => { export const createNewCase = (newCase: TestCase) => { cy.get(TITLE_INPUT).type(newCase.name, { force: true }); - newCase.tags.forEach(tag => { + newCase.tags.forEach((tag) => { cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true }); }); cy.get(DESCRIPTION_INPUT).type(`${newCase.description} `, { force: true }); @@ -33,9 +33,7 @@ export const createNewCase = (newCase: TestCase) => { cy.get(TIMELINE_SEARCHBOX).type(`${newCase.timeline.title}{enter}`); cy.get(TIMELINE).should('be.visible'); cy.wait(300); - cy.get(TIMELINE) - .eq(1) - .click({ force: true }); + cy.get(TIMELINE).eq(1).click({ force: true }); cy.get(SUBMIT_BTN).click({ force: true }); cy.get(LOADING_SPINNER).should('exist'); diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts new file mode 100644 index 0000000000000..eca5885e7b3d9 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { CustomRule, MachineLearningRule, machineLearningRule } from '../objects/rule'; +import { + ABOUT_CONTINUE_BTN, + ANOMALY_THRESHOLD_INPUT, + ADD_FALSE_POSITIVE_BTN, + ADD_REFERENCE_URL_BTN, + ADVANCED_SETTINGS_BTN, + CREATE_AND_ACTIVATE_BTN, + CUSTOM_QUERY_INPUT, + DEFINE_CONTINUE_BUTTON, + FALSE_POSITIVES_INPUT, + IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK, + INVESTIGATION_NOTES_TEXTAREA, + MACHINE_LEARNING_DROPDOWN, + MACHINE_LEARNING_LIST, + MACHINE_LEARNING_TYPE, + MITRE_BTN, + MITRE_TACTIC, + MITRE_TACTIC_DROPDOWN, + MITRE_TECHNIQUES_INPUT, + RISK_INPUT, + REFERENCE_URLS_INPUT, + RULE_DESCRIPTION_INPUT, + RULE_NAME_INPUT, + SCHEDULE_CONTINUE_BUTTON, + SEVERITY_DROPDOWN, + TAGS_INPUT, +} from '../screens/create_new_rule'; +import { TIMELINE } from '../screens/timeline'; + +export const createAndActivateRule = () => { + cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true }); + cy.get(CREATE_AND_ACTIVATE_BTN).click({ force: true }); + cy.get(CREATE_AND_ACTIVATE_BTN).should('not.exist'); +}; + +export const fillAboutRuleAndContinue = (rule: CustomRule | MachineLearningRule) => { + cy.get(RULE_NAME_INPUT).type(rule.name, { force: true }); + cy.get(RULE_DESCRIPTION_INPUT).type(rule.description, { force: true }); + + cy.get(SEVERITY_DROPDOWN).click({ force: true }); + cy.get(`#${rule.severity.toLowerCase()}`).click(); + + cy.get(RISK_INPUT).clear({ force: true }).type(`${rule.riskScore}`, { force: true }); + + rule.tags.forEach((tag) => { + cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true }); + }); + + cy.get(ADVANCED_SETTINGS_BTN).click({ force: true }); + + rule.referenceUrls.forEach((url, index) => { + cy.get(REFERENCE_URLS_INPUT).eq(index).type(url, { force: true }); + cy.get(ADD_REFERENCE_URL_BTN).click({ force: true }); + }); + + rule.falsePositivesExamples.forEach((falsePositive, index) => { + cy.get(FALSE_POSITIVES_INPUT).eq(index).type(falsePositive, { force: true }); + cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true }); + }); + + rule.mitre.forEach((mitre, index) => { + cy.get(MITRE_TACTIC_DROPDOWN).eq(index).click({ force: true }); + cy.contains(MITRE_TACTIC, mitre.tactic).click(); + + mitre.techniques.forEach((technique) => { + cy.get(MITRE_TECHNIQUES_INPUT).eq(index).type(`${technique}{enter}`, { force: true }); + }); + + cy.get(MITRE_BTN).click({ force: true }); + }); + + cy.get(INVESTIGATION_NOTES_TEXTAREA).type(rule.note, { force: true }); + + cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true }); +}; + +export const fillDefineCustomRuleAndContinue = (rule: CustomRule) => { + cy.get(CUSTOM_QUERY_INPUT).type(rule.customQuery); + cy.get(CUSTOM_QUERY_INPUT).should('have.attr', 'value', rule.customQuery); + cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); + + cy.get(CUSTOM_QUERY_INPUT).should('not.exist'); +}; + +export const fillDefineCustomRuleWithImportedQueryAndContinue = (rule: CustomRule) => { + cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click(); + cy.get(TIMELINE(rule.timelineId)).click(); + cy.get(CUSTOM_QUERY_INPUT).should('have.attr', 'value', rule.customQuery); + cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); + + cy.get(CUSTOM_QUERY_INPUT).should('not.exist'); +}; + +export const fillDefineMachineLearningRuleAndContinue = (rule: MachineLearningRule) => { + cy.get(MACHINE_LEARNING_DROPDOWN).click({ force: true }); + cy.contains(MACHINE_LEARNING_LIST, rule.machineLearningJob).click(); + cy.get(ANOMALY_THRESHOLD_INPUT).type(`{selectall}${machineLearningRule.anomalyScoreThreshold}`, { + force: true, + }); + cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); + + cy.get(MACHINE_LEARNING_DROPDOWN).should('not.exist'); +}; + +export const selectMachineLearningRuleType = () => { + cy.get(MACHINE_LEARNING_TYPE).click({ force: true }); +}; diff --git a/x-pack/plugins/siem/cypress/tasks/date_picker.ts b/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts similarity index 78% rename from x-pack/plugins/siem/cypress/tasks/date_picker.ts rename to x-pack/plugins/security_solution/cypress/tasks/date_picker.ts index 0d778b737380b..809498d25c5d8 100644 --- a/x-pack/plugins/siem/cypress/tasks/date_picker.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts @@ -18,36 +18,26 @@ import { export const setEndDate = (date: string) => { cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON).click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_TAB) - .first() - .click({ force: true }); + cy.get(DATE_PICKER_ABSOLUTE_TAB).first().click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_INPUT) - .clear() - .type(date); + cy.get(DATE_PICKER_ABSOLUTE_INPUT).clear().type(date); }; export const setStartDate = (date: string) => { cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_TAB) - .first() - .click({ force: true }); + cy.get(DATE_PICKER_ABSOLUTE_TAB).first().click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_INPUT) - .clear() - .type(date); + cy.get(DATE_PICKER_ABSOLUTE_INPUT).clear().type(date); }; export const setTimelineEndDate = (date: string) => { cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE).click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_TAB) - .first() - .click({ force: true }); + cy.get(DATE_PICKER_ABSOLUTE_TAB).first().click({ force: true }); cy.get(DATE_PICKER_ABSOLUTE_INPUT).click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_INPUT).then($el => { + cy.get(DATE_PICKER_ABSOLUTE_INPUT).then(($el) => { // @ts-ignore if (Cypress.dom.isAttached($el)) { cy.wrap($el).click({ force: true }); @@ -61,12 +51,10 @@ export const setTimelineStartDate = (date: string) => { force: true, }); - cy.get(DATE_PICKER_ABSOLUTE_TAB) - .first() - .click({ force: true }); + cy.get(DATE_PICKER_ABSOLUTE_TAB).first().click({ force: true }); cy.get(DATE_PICKER_ABSOLUTE_INPUT).click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_INPUT).then($el => { + cy.get(DATE_PICKER_ABSOLUTE_INPUT).then(($el) => { // @ts-ignore if (Cypress.dom.isAttached($el)) { cy.wrap($el).click({ force: true }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/detections.ts b/x-pack/plugins/security_solution/cypress/tasks/detections.ts new file mode 100644 index 0000000000000..f53dd83635d85 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/detections.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CLOSED_ALERTS_BTN, + EXPAND_ALERT_BTN, + LOADING_ALERTS_PANEL, + MANAGE_ALERT_DETECTION_RULES_BTN, + OPEN_CLOSE_ALERT_BTN, + OPEN_CLOSE_ALERTS_BTN, + OPENED_ALERTS_BTN, + SEND_ALERT_TO_TIMELINE_BTN, + ALERTS, + ALERT_CHECKBOX, +} from '../screens/detections'; +import { REFRESH_BUTTON } from '../screens/siem_header'; + +export const closeFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); +}; + +export const closeAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); +}; + +export const expandFirstAlert = () => { + cy.get(EXPAND_ALERT_BTN).first().click({ force: true }); +}; + +export const goToClosedAlerts = () => { + cy.get(CLOSED_ALERTS_BTN).click({ force: true }); +}; + +export const goToManageAlertDetectionRules = () => { + cy.get(MANAGE_ALERT_DETECTION_RULES_BTN).should('exist').click({ force: true }); +}; + +export const goToOpenedAlerts = () => { + cy.get(OPENED_ALERTS_BTN).click({ force: true }); +}; + +export const openFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); +}; + +export const openAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); +}; + +export const selectNumberOfAlerts = (numberOfAlerts: number) => { + for (let i = 0; i < numberOfAlerts; i++) { + cy.get(ALERT_CHECKBOX).eq(i).click({ force: true }); + } +}; + +export const investigateFirstAlertInTimeline = () => { + cy.get(SEND_ALERT_TO_TIMELINE_BTN).first().click({ force: true }); +}; + +export const waitForAlerts = () => { + cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); +}; + +export const waitForAlertsIndexToBeCreated = () => { + cy.request({ url: '/api/detection_engine/index', retryOnStatusCodeFailure: true }).then( + (response) => { + if (response.status !== 200) { + cy.wait(7500); + } + } + ); +}; + +export const waitForAlertsPanelToBeLoaded = () => { + cy.get(LOADING_ALERTS_PANEL).should('exist'); + cy.get(LOADING_ALERTS_PANEL).should('not.exist'); +}; + +export const waitForAlertsToBeLoaded = () => { + const expectedNumberOfDisplayedAlerts = 25; + cy.get(ALERTS).should('have.length', expectedNumberOfDisplayedAlerts); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts b/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts new file mode 100644 index 0000000000000..5a09a2f753dc4 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const esArchiverLoadEmptyKibana = () => { + cy.exec( + `node ../../../scripts/es_archiver empty_kibana load empty--dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url ${Cypress.env( + 'ELASTICSEARCH_URL' + )} --kibana-url ${Cypress.config().baseUrl}` + ); +}; + +export const esArchiverLoad = (folder: string) => { + cy.exec( + `node ../../../scripts/es_archiver load ${folder} --dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url ${Cypress.env( + 'ELASTICSEARCH_URL' + )} --kibana-url ${Cypress.config().baseUrl}` + ); +}; + +export const esArchiverUnload = (folder: string) => { + cy.exec( + `node ../../../scripts/es_archiver unload ${folder} --dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url ${Cypress.env( + 'ELASTICSEARCH_URL' + )} --kibana-url ${Cypress.config().baseUrl}` + ); +}; + +export const esArchiverUnloadEmptyKibana = () => { + cy.exec( + `node ../../../scripts/es_archiver unload empty_kibana empty--dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url ${Cypress.env( + 'ELASTICSEARCH_URL' + )} --kibana-url ${Cypress.config().baseUrl}` + ); +}; + +export const esArchiverResetKibana = () => { + cy.exec( + `node ../../../scripts/es_archiver empty-kibana-index --config ../../../test/functional/config.js --es-url ${Cypress.env( + 'ELASTICSEARCH_URL' + )} --kibana-url ${Cypress.config().baseUrl}` + ); +}; diff --git a/x-pack/plugins/siem/cypress/tasks/fields_browser.ts b/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts similarity index 93% rename from x-pack/plugins/siem/cypress/tasks/fields_browser.ts rename to x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts index e1d2f24da424c..eb709d2dd5778 100644 --- a/x-pack/plugins/siem/cypress/tasks/fields_browser.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts @@ -30,9 +30,9 @@ export const addsHostGeoContinentNameToTimeline = () => { export const addsHostGeoCountryNameToTimelineDraggingIt = () => { cy.get(FIELDS_BROWSER_DRAGGABLE_HOST_GEO_COUNTRY_NAME_HEADER).should('exist'); - cy.get(FIELDS_BROWSER_DRAGGABLE_HOST_GEO_COUNTRY_NAME_HEADER).then(field => drag(field)); + cy.get(FIELDS_BROWSER_DRAGGABLE_HOST_GEO_COUNTRY_NAME_HEADER).then((field) => drag(field)); - cy.get(FIELDS_BROWSER_HEADER_DROP_AREA).then(headersDropArea => drop(headersDropArea)); + cy.get(FIELDS_BROWSER_HEADER_DROP_AREA).then((headersDropArea) => drop(headersDropArea)); }; export const clearFieldsBrowser = () => { diff --git a/x-pack/plugins/siem/cypress/tasks/hosts/all_hosts.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/all_hosts.ts similarity index 75% rename from x-pack/plugins/siem/cypress/tasks/hosts/all_hosts.ts rename to x-pack/plugins/security_solution/cypress/tasks/hosts/all_hosts.ts index 312df96df1ddf..f9f902c3de8c7 100644 --- a/x-pack/plugins/siem/cypress/tasks/hosts/all_hosts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/all_hosts.ts @@ -12,16 +12,16 @@ import { drag, dragWithoutDrop, drop } from '../../tasks/common'; export const dragAndDropFirstHostToTimeline = () => { cy.get(HOSTS_NAMES_DRAGGABLE) .first() - .then(firstHost => drag(firstHost)); - cy.get(TIMELINE_DATA_PROVIDERS).then(dataProvidersDropArea => drop(dataProvidersDropArea)); + .then((firstHost) => drag(firstHost)); + cy.get(TIMELINE_DATA_PROVIDERS).then((dataProvidersDropArea) => drop(dataProvidersDropArea)); }; export const dragFirstHostToEmptyTimelineDataProviders = () => { cy.get(HOSTS_NAMES_DRAGGABLE) .first() - .then(host => drag(host)); + .then((host) => drag(host)); - cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).then(dataProvidersDropArea => + cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).then((dataProvidersDropArea) => dragWithoutDrop(dataProvidersDropArea) ); }; @@ -29,12 +29,10 @@ export const dragFirstHostToEmptyTimelineDataProviders = () => { export const dragFirstHostToTimeline = () => { cy.get(HOSTS_NAMES_DRAGGABLE) .first() - .then(host => drag(host)); + .then((host) => drag(host)); }; export const openFirstHostDetails = () => { - cy.get(HOSTS_NAMES) - .first() - .click({ force: true }); + cy.get(HOSTS_NAMES).first().click({ force: true }); }; export const waitForAllHostsToBeLoaded = () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/authentications.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/authentications.ts new file mode 100644 index 0000000000000..7ba7f227964ab --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/authentications.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AUTHENTICATIONS_TABLE } from '../../screens/hosts/authentications'; +import { REFRESH_BUTTON } from '../../screens/siem_header'; + +export const waitForAuthenticationsToBeLoaded = () => { + cy.get(AUTHENTICATIONS_TABLE).should('exist'); + cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts new file mode 100644 index 0000000000000..a593650989259 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { drag, drop } from '../common'; +import { + CLOSE_MODAL, + EVENTS_VIEWER_FIELDS_BUTTON, + FIELDS_BROWSER_CONTAINER, + HOST_GEO_CITY_NAME_CHECKBOX, + HOST_GEO_COUNTRY_NAME_CHECKBOX, + INSPECT_QUERY, + LOAD_MORE, + RESET_FIELDS, + SERVER_SIDE_EVENT_COUNT, +} from '../../screens/hosts/events'; +import { DRAGGABLE_HEADER } from '../../screens/timeline'; + +export const addsHostGeoCityNameToHeader = () => { + cy.get(HOST_GEO_CITY_NAME_CHECKBOX).check({ + force: true, + }); +}; + +export const addsHostGeoCountryNameToHeader = () => { + cy.get(HOST_GEO_COUNTRY_NAME_CHECKBOX).check({ + force: true, + }); +}; + +export const closeModal = () => { + cy.get(CLOSE_MODAL).click(); +}; + +export const loadMoreEvents = () => { + cy.get(LOAD_MORE).click({ force: true }); +}; + +export const openEventsViewerFieldsBrowser = () => { + cy.get(EVENTS_VIEWER_FIELDS_BUTTON).click({ force: true }); + + cy.get(SERVER_SIDE_EVENT_COUNT).invoke('text').should('not.equal', '0'); + + cy.get(FIELDS_BROWSER_CONTAINER).should('exist'); +}; + +export const opensInspectQueryModal = () => { + cy.get(INSPECT_QUERY) + .should('exist') + .trigger('mousemove', { force: true }) + .click({ force: true }); +}; + +export const resetFields = () => { + cy.get(RESET_FIELDS).click({ force: true }); +}; + +export const waitsForEventsToBeLoaded = () => { + cy.get(SERVER_SIDE_EVENT_COUNT).should('exist').invoke('text').should('not.equal', '0'); +}; + +export const dragAndDropColumn = ({ + column, + newPosition, +}: { + column: number; + newPosition: number; +}) => { + cy.get(DRAGGABLE_HEADER).first().should('exist'); + cy.get(DRAGGABLE_HEADER) + .eq(column) + .then((header) => drag(header)); + + cy.wait(3000); // wait for DOM updates before moving + + cy.get(DRAGGABLE_HEADER) + .eq(newPosition) + .then((targetPosition) => { + drop(targetPosition); + }); +}; diff --git a/x-pack/plugins/siem/cypress/tasks/hosts/main.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/main.ts similarity index 100% rename from x-pack/plugins/siem/cypress/tasks/hosts/main.ts rename to x-pack/plugins/security_solution/cypress/tasks/hosts/main.ts diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/uncommon_processes.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/uncommon_processes.ts new file mode 100644 index 0000000000000..c86b169d60a13 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/uncommon_processes.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UNCOMMON_PROCESSES_TABLE } from '../../screens/hosts/uncommon_processes'; +import { REFRESH_BUTTON } from '../../screens/siem_header'; + +export const waitForUncommonProcessesToBeLoaded = () => { + cy.get(UNCOMMON_PROCESSES_TABLE).should('exist'); + cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); +}; diff --git a/x-pack/plugins/siem/cypress/tasks/inspect.ts b/x-pack/plugins/security_solution/cypress/tasks/inspect.ts similarity index 100% rename from x-pack/plugins/siem/cypress/tasks/inspect.ts rename to x-pack/plugins/security_solution/cypress/tasks/inspect.ts diff --git a/x-pack/plugins/siem/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts similarity index 97% rename from x-pack/plugins/siem/cypress/tasks/login.ts rename to x-pack/plugins/security_solution/cypress/tasks/login.ts index 1bbf41d05db00..4479c8d9d1fbe 100644 --- a/x-pack/plugins/siem/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts @@ -98,7 +98,7 @@ const loginViaConfig = () => { ); // read the login details from `kibana.dev.yaml` - cy.readFile(KIBANA_DEV_YML_PATH).then(kibanaDevYml => { + cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => { const config = yaml.safeLoad(kibanaDevYml); // programmatically authenticate without interacting with the Kibana login page @@ -124,12 +124,12 @@ export const loginAndWaitForPage = (url: string) => { cy.visit( `${url}?timerange=(global:(linkTo:!(timeline),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)))` ); - cy.contains('a', 'SIEM'); + cy.contains('a', 'Security'); }; export const loginAndWaitForPageWithoutDateRange = (url: string) => { login(); cy.viewport('macbook-15'); cy.visit(url); - cy.contains('a', 'SIEM', { timeout: 120000 }); + cy.contains('a', 'Security', { timeout: 120000 }); }; diff --git a/x-pack/plugins/siem/cypress/tasks/network/flows.ts b/x-pack/plugins/security_solution/cypress/tasks/network/flows.ts similarity index 100% rename from x-pack/plugins/siem/cypress/tasks/network/flows.ts rename to x-pack/plugins/security_solution/cypress/tasks/network/flows.ts diff --git a/x-pack/plugins/siem/cypress/tasks/overview.ts b/x-pack/plugins/security_solution/cypress/tasks/overview.ts similarity index 89% rename from x-pack/plugins/siem/cypress/tasks/overview.ts rename to x-pack/plugins/security_solution/cypress/tasks/overview.ts index 0ca4059a90097..38c3c00fbc365 100644 --- a/x-pack/plugins/siem/cypress/tasks/overview.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/overview.ts @@ -7,9 +7,7 @@ import { OVERVIEW_HOST_STATS, OVERVIEW_NETWORK_STATS } from '../screens/overview'; export const expand = (statType: string) => { - cy.get(statType) - .find('button') - .invoke('click'); + cy.get(statType).find('button').invoke('click'); }; export const expandHostStats = () => { diff --git a/x-pack/plugins/siem/cypress/tasks/pagination.ts b/x-pack/plugins/security_solution/cypress/tasks/pagination.ts similarity index 100% rename from x-pack/plugins/siem/cypress/tasks/pagination.ts rename to x-pack/plugins/security_solution/cypress/tasks/pagination.ts diff --git a/x-pack/plugins/siem/cypress/tasks/siem_header.ts b/x-pack/plugins/security_solution/cypress/tasks/siem_header.ts similarity index 77% rename from x-pack/plugins/siem/cypress/tasks/siem_header.ts rename to x-pack/plugins/security_solution/cypress/tasks/siem_header.ts index 4c43948445c58..2cc9199a42bbb 100644 --- a/x-pack/plugins/siem/cypress/tasks/siem_header.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/siem_header.ts @@ -7,9 +7,7 @@ import { KQL_INPUT, REFRESH_BUTTON } from '../screens/siem_header'; export const clearSearchBar = () => { - cy.get(KQL_INPUT) - .clear() - .type('{enter}'); + cy.get(KQL_INPUT).clear().type('{enter}'); }; export const kqlSearch = (search: string) => { @@ -21,8 +19,5 @@ export const navigateFromHeaderTo = (page: string) => { }; export const refreshPage = () => { - cy.get(REFRESH_BUTTON) - .click({ force: true }) - .invoke('text') - .should('not.equal', 'Updating'); + cy.get(REFRESH_BUTTON).click({ force: true }).invoke('text').should('not.equal', 'Updating'); }; diff --git a/x-pack/plugins/siem/cypress/tasks/siem_main.ts b/x-pack/plugins/security_solution/cypress/tasks/siem_main.ts similarity index 93% rename from x-pack/plugins/siem/cypress/tasks/siem_main.ts rename to x-pack/plugins/security_solution/cypress/tasks/siem_main.ts index 2bdc62ecbdc03..eece7edcb2b44 100644 --- a/x-pack/plugins/siem/cypress/tasks/siem_main.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/siem_main.ts @@ -11,7 +11,7 @@ export const openTimeline = () => { }; export const openTimelineIfClosed = () => { - cy.get(MAIN_PAGE).then($page => { + cy.get(MAIN_PAGE).then(($page) => { if ($page.find(TIMELINE_TOGGLE_BUTTON).length === 1) { openTimeline(); } diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts new file mode 100644 index 0000000000000..38da611428b2e --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DATE_PICKER_APPLY_BUTTON_TIMELINE } from '../screens/date_picker'; + +import { + CLOSE_TIMELINE_BTN, + CREATE_NEW_TIMELINE, + ID_FIELD, + ID_HEADER_FIELD, + ID_TOGGLE_FIELD, + SEARCH_OR_FILTER_CONTAINER, + SERVER_SIDE_EVENT_COUNT, + TIMELINE_DESCRIPTION, + TIMELINE_FIELDS_BUTTON, + TIMELINE_INSPECT_BUTTON, + TIMELINE_SETTINGS_ICON, + TIMELINE_TITLE, + TIMESTAMP_TOGGLE_FIELD, + TOGGLE_TIMELINE_EXPAND_EVENT, +} from '../screens/timeline'; + +import { drag, drop } from '../tasks/common'; + +export const hostExistsQuery = 'host.name: *'; + +export const addDescriptionToTimeline = (description: string) => { + cy.get(TIMELINE_DESCRIPTION).type(`${description}{enter}`); + cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).click().invoke('text').should('not.equal', 'Updating'); +}; + +export const addNameToTimeline = (name: string) => { + cy.get(TIMELINE_TITLE).type(`${name}{enter}`); + cy.get(TIMELINE_TITLE).should('have.attr', 'value', name); +}; + +export const checkIdToggleField = () => { + cy.get(ID_HEADER_FIELD).should('not.exist'); + + cy.get(ID_TOGGLE_FIELD).check({ + force: true, + }); +}; + +export const closeTimeline = () => { + cy.get(CLOSE_TIMELINE_BTN).click({ force: true }); +}; + +export const createNewTimeline = () => { + cy.get(TIMELINE_SETTINGS_ICON).click({ force: true }); + cy.get(CREATE_NEW_TIMELINE).click(); + cy.get(CLOSE_TIMELINE_BTN).click({ force: true }); +}; + +export const executeTimelineKQL = (query: string) => { + cy.get(`${SEARCH_OR_FILTER_CONTAINER} input`).type(`${query} {enter}`); +}; + +export const expandFirstTimelineEventDetails = () => { + cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click({ force: true }); +}; + +export const openTimelineFieldsBrowser = () => { + cy.get(TIMELINE_FIELDS_BUTTON).click({ force: true }); +}; + +export const openTimelineInspectButton = () => { + cy.get(TIMELINE_INSPECT_BUTTON).should('not.be.disabled'); + cy.get(TIMELINE_INSPECT_BUTTON).trigger('click', { force: true }); +}; + +export const openTimelineSettings = () => { + cy.get(TIMELINE_SETTINGS_ICON).trigger('click', { force: true }); +}; + +export const populateTimeline = () => { + executeTimelineKQL(hostExistsQuery); + cy.get(SERVER_SIDE_EVENT_COUNT) + .invoke('text') + .then((strCount) => { + const intCount = +strCount; + cy.wrap(intCount).should('be.above', 0); + }); +}; + +export const uncheckTimestampToggleField = () => { + cy.get(TIMESTAMP_TOGGLE_FIELD).should('exist'); + + cy.get(TIMESTAMP_TOGGLE_FIELD).uncheck({ force: true }); +}; + +export const dragAndDropIdToggleFieldToTimeline = () => { + cy.get(ID_HEADER_FIELD).should('not.exist'); + + cy.get(ID_FIELD).then((field) => drag(field)); + + cy.get(`[data-test-subj="timeline"] [data-test-subj="headers-group"]`).then((headersDropArea) => + drop(headersDropArea) + ); +}; diff --git a/x-pack/plugins/siem/cypress/test_files/expected_rules_export.ndjson b/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson similarity index 100% rename from x-pack/plugins/siem/cypress/test_files/expected_rules_export.ndjson rename to x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson diff --git a/x-pack/plugins/siem/cypress/tsconfig.json b/x-pack/plugins/security_solution/cypress/tsconfig.json similarity index 100% rename from x-pack/plugins/siem/cypress/tsconfig.json rename to x-pack/plugins/security_solution/cypress/tsconfig.json diff --git a/x-pack/plugins/security_solution/cypress/urls/ml_conditional_links.ts b/x-pack/plugins/security_solution/cypress/urls/ml_conditional_links.ts new file mode 100644 index 0000000000000..cfa18099e5888 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/urls/ml_conditional_links.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * These links are for different test scenarios that try and capture different drill downs into + * ml-network and ml-hosts and are of the flavor of testing: + * A filter being null: (query:!n) + * A filter being set with single values: query=(query:%27process.name%20:%20%22conhost.exe%22%27,language:kuery) + * A filter being set with multiple values: query=(query:%27process.name%20:%20%22conhost.exe,sc.exe%22%27,language:kuery) + * A filter containing variables not replaced: query=(query:%27process.name%20:%20%$process.name$%22%27,language:kuery) + * + * In different combination with: + * network not being set: $ip$ + * host not being set: $host.name$ + * ...or... + * network being set normally: 127.0.0.1 + * host being set normally: suricata-iowa + * ...or... + * network having multiple values: 127.0.0.1,127.0.0.2 + * host having multiple values: suricata-iowa,siem-windows + */ + +// Single IP with a null for the Query: +export const mlNetworkSingleIpNullKqlQuery = + "/app/security#/ml-network/ip/127.0.0.1?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Single IP with a value for the Query: +export const mlNetworkSingleIpKqlQuery = + "/app/security#/ml-network/ip/127.0.0.1?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Multiple IPs with a null for the Query: +export const mlNetworkMultipleIpNullKqlQuery = + "/app/security#/ml-network/ip/127.0.0.1,127.0.0.2?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Multiple IPs with a value for the Query: +export const mlNetworkMultipleIpKqlQuery = + "/app/security#/ml-network/ip/127.0.0.1,127.0.0.2?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// $ip$ with a null Query: +export const mlNetworkNullKqlQuery = + "/app/security#/ml-network/ip/$ip$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// $ip$ with a value for the Query: +export const mlNetworkKqlQuery = + "/app/security#/ml-network/ip/$ip$?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Single host name with a null for the Query: +export const mlHostSingleHostNullKqlQuery = + "/app/security#/ml-hosts/siem-windows?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Single host name with a variable in the Query: +export const mlHostSingleHostKqlQueryVariable = + "/app/security#/ml-hosts/siem-windows?query=(language:kuery,query:'process.name%20:%20%22$process.name$%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Single host name with a value for Query: +export const mlHostSingleHostKqlQuery = + "/app/security#/ml-hosts/siem-windows?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Multiple host names with null for Query: +export const mlHostMultiHostNullKqlQuery = + "/app/security#/ml-hosts/siem-windows,siem-suricata?query=!n&&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Multiple host names with a value for Query: +export const mlHostMultiHostKqlQuery = + "/app/security#/ml-hosts/siem-windows,siem-suricata?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Undefined/null host name with a null for the KQL: +export const mlHostVariableHostNullKqlQuery = + "/app/security#/ml-hosts/$host.name$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Undefined/null host name but with a value for Query: +export const mlHostVariableHostKqlQuery = + "/app/security#/ml-hosts/$host.name$?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts new file mode 100644 index 0000000000000..9bfe2e9e5102e --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const CASES = '/app/security#/case'; +export const DETECTIONS = 'app/security#/detections'; +export const HOSTS_PAGE = '/app/security#/hosts/allHosts'; +export const HOSTS_PAGE_TAB_URLS = { + allHosts: '/app/security#/hosts/allHosts', + anomalies: '/app/security#/hosts/anomalies', + authentications: '/app/security#/hosts/authentications', + events: '/app/security#/hosts/events', + uncommonProcesses: '/app/security#/hosts/uncommonProcesses', +}; +export const NETWORK_PAGE = '/app/security#/network'; +export const OVERVIEW_PAGE = '/app/security#/overview'; +export const TIMELINES_PAGE = '/app/security#/timelines'; diff --git a/x-pack/plugins/security_solution/cypress/urls/state.ts b/x-pack/plugins/security_solution/cypress/urls/state.ts new file mode 100644 index 0000000000000..6de30fdafdaf8 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/urls/state.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ABSOLUTE_DATE_RANGE = { + url: + '/app/security#/network/?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))', + + urlUnlinked: + '/app/security#/network/?timerange=(global:(linkTo:!(),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(),timerange:(from:1564776209186,kind:absolute,to:1564779809186)))', + urlKqlNetworkNetwork: `/app/security#/network/?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, + urlKqlNetworkHosts: `/app/security#/network/?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, + urlKqlHostsNetwork: `/app/security#/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, + urlKqlHostsHosts: `/app/security#/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, + urlHost: + '/app/security#/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))', + urlHostNew: + '/app/security#/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))', +}; diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json new file mode 100644 index 0000000000000..8ce8820a8e57d --- /dev/null +++ b/x-pack/plugins/security_solution/kibana.json @@ -0,0 +1,32 @@ +{ + "id": "securitySolution", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "securitySolution"], + "requiredPlugins": [ + "actions", + "alerts", + "data", + "dataEnhanced", + "embeddable", + "features", + "home", + "ingestManager", + "inspector", + "licensing", + "maps", + "triggers_actions_ui", + "uiActions" + ], + "optionalPlugins": [ + "encryptedSavedObjects", + "ml", + "newsfeed", + "security", + "spaces", + "usageCollection", + "lists" + ], + "server": true, + "ui": true +} diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json new file mode 100644 index 0000000000000..73347e00e6b34 --- /dev/null +++ b/x-pack/plugins/security_solution/package.json @@ -0,0 +1,24 @@ +{ + "author": "Elastic", + "name": "security_solution", + "version": "8.0.0", + "private": true, + "license": "Elastic-License", + "scripts": { + "extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ./public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix", + "build-graphql-types": "node scripts/generate_types_from_graphql.js", + "cypress:open": "cypress open --config-file ./cypress/cypress.json", + "cypress:run": "cypress run --browser chrome --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../node_modules/.bin/mochawesome-merge --reportDir ../../../target/kibana-security-solution/cypress/results > ../../../target/kibana-security-solution/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/ && exit $status;", + "cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/security_solution_cypress/config.ts", + "test:generate": "ts-node --project scripts/endpoint/cli_tsconfig.json scripts/endpoint/resolver_generator.ts" + }, + "devDependencies": { + "@types/lodash": "^4.14.110" + }, + "dependencies": { + "lodash": "^4.17.15", + "querystring": "^0.2.0", + "redux-devtools-extension": "^2.13.8", + "@types/seedrandom": ">=2.0.0 <4.0.0" + } +} diff --git a/x-pack/plugins/siem/public/alerts/components/activity_monitor/columns.tsx b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/columns.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/activity_monitor/columns.tsx rename to x-pack/plugins/security_solution/public/alerts/components/activity_monitor/columns.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/activity_monitor/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/activity_monitor/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/activity_monitor/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/activity_monitor/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/activity_monitor/types.ts b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/activity_monitor/types.ts rename to x-pack/plugins/security_solution/public/alerts/components/activity_monitor/types.ts diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx new file mode 100644 index 0000000000000..7f340b0bea37b --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AlertsHistogram } from './alerts_histogram'; + +jest.mock('../../../common/lib/kibana'); + +describe('AlertsHistogram', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('Chart')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/alerts_histogram.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/alerts_histogram.tsx new file mode 100644 index 0000000000000..11dcbfa39d574 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/alerts_histogram.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Axis, + Chart, + HistogramBarSeries, + Position, + Settings, + ChartSizeArray, +} from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { useTheme, UpdateDateRange } from '../../../common/components/charts/common'; +import { histogramDateTimeFormatter } from '../../../common/components/utils'; +import { DraggableLegend } from '../../../common/components/charts/draggable_legend'; +import { LegendItem } from '../../../common/components/charts/draggable_legend_item'; + +import { HistogramData } from './types'; + +const DEFAULT_CHART_HEIGHT = 174; + +interface AlertsHistogramProps { + chartHeight?: number; + from: number; + legendItems: LegendItem[]; + legendPosition?: Position; + loading: boolean; + to: number; + data: HistogramData[]; + updateDateRange: UpdateDateRange; +} +export const AlertsHistogram = React.memo( + ({ + chartHeight = DEFAULT_CHART_HEIGHT, + data, + from, + legendItems, + legendPosition = 'right', + loading, + to, + updateDateRange, + }) => { + const theme = useTheme(); + + const chartSize: ChartSizeArray = useMemo(() => ['100%', chartHeight], [chartHeight]); + const xAxisId = 'alertsHistogramAxisX'; + const yAxisId = 'alertsHistogramAxisY'; + const id = 'alertsHistogram'; + const yAccessors = useMemo(() => ['y'], []); + const splitSeriesAccessors = useMemo(() => ['g'], []); + const tickFormat = useMemo(() => histogramDateTimeFormatter([from, to]), [from, to]); + + return ( + <> + {loading && ( + + )} + + + + + + + + + + + + + + + {legendItems.length > 0 && ( + + )} + + + + ); + } +); + +AlertsHistogram.displayName = 'AlertsHistogram'; diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/config.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/config.ts new file mode 100644 index 0000000000000..5138835873812 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/config.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertsHistogramOption } from './types'; + +export const alertsHistogramOptions: AlertsHistogramOption[] = [ + { text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' }, + { text: 'signal.rule.severity', value: 'signal.rule.severity' }, + { text: 'signal.rule.threat.tactic.name', value: 'signal.rule.threat.tactic.name' }, + { text: 'destination.ip', value: 'destination.ip' }, + { text: 'event.action', value: 'event.action' }, + { text: 'event.category', value: 'event.category' }, + { text: 'host.name', value: 'host.name' }, + { text: 'signal.rule.type', value: 'signal.rule.type' }, + { text: 'signal.rule.name', value: 'signal.rule.name' }, + { text: 'source.ip', value: 'source.ip' }, + { text: 'user.name', value: 'user.name' }, +]; diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/helpers.test.tsx new file mode 100644 index 0000000000000..bfe4cee088a02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/helpers.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { showInitialLoadingSpinner } from './helpers'; + +describe('helpers', () => { + describe('showInitialLoadingSpinner', () => { + test('it should (only) show the spinner during initial loading, while we are fetching data', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: true })).toBe( + true + ); + }); + + test('it should STOP showing the spinner (during initial loading) when the first data fetch completes', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: false })).toBe( + false + ); + }); + + test('it should NOT show the spinner after initial loading has completed, even if the user requests more data (e.g. by clicking Refresh)', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: true })).toBe( + false + ); + }); + + test('it should NOT show the spinner after initial loading has completed', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: false })).toBe( + false + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/helpers.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/helpers.tsx new file mode 100644 index 0000000000000..9d124201f022e --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/helpers.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { showAllOthersBucket } from '../../../../common/constants'; +import { HistogramData, AlertsAggregation, AlertsBucket, AlertsGroupBucket } from './types'; +import { AlertSearchResponse } from '../../containers/detection_engine/alerts/types'; +import * as i18n from './translations'; + +export const formatAlertsData = (alertsData: AlertSearchResponse<{}, AlertsAggregation> | null) => { + const groupBuckets: AlertsGroupBucket[] = + alertsData?.aggregations?.alertsByGrouping?.buckets ?? []; + return groupBuckets.reduce((acc, { key: group, alerts }) => { + const alertsBucket: AlertsBucket[] = alerts.buckets ?? []; + + return [ + ...acc, + ...alertsBucket.map(({ key, doc_count }: AlertsBucket) => ({ + x: key, + y: doc_count, + g: group, + })), + ]; + }, []); +}; + +export const getAlertsHistogramQuery = ( + stackByField: string, + from: number, + to: number, + additionalFilters: Array<{ + bool: { filter: unknown[]; should: unknown[]; must_not: unknown[]; must: unknown[] }; + }> +) => { + const missing = showAllOthersBucket.includes(stackByField) + ? { + missing: stackByField.endsWith('.ip') ? '0.0.0.0' : i18n.ALL_OTHERS, + } + : {}; + + return { + aggs: { + alertsByGrouping: { + terms: { + field: stackByField, + ...missing, + order: { + _count: 'desc', + }, + size: 10, + }, + aggs: { + alerts: { + date_histogram: { + field: '@timestamp', + fixed_interval: `${Math.floor((to - from) / 32)}ms`, + min_doc_count: 0, + extended_bounds: { + min: from, + max: to, + }, + }, + }, + }, + }, + }, + query: { + bool: { + filter: [ + ...additionalFilters, + { + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, + }, + ], + }, + }, + }; +}; + +/** + * Returns `true` when the alerts histogram initial loading spinner should be shown + * + * @param isInitialLoading The loading spinner will only be displayed if this value is `true`, because after initial load, a different, non-spinner loading indicator is displayed + * @param isLoadingAlerts When `true`, IO is being performed to request alerts (for rendering in the histogram) + */ +export const showInitialLoadingSpinner = ({ + isInitialLoading, + isLoadingAlerts, +}: { + isInitialLoading: boolean; + isLoadingAlerts: boolean; +}): boolean => isInitialLoading && isLoadingAlerts; diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.test.tsx new file mode 100644 index 0000000000000..3376df76ac6ec --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AlertsHistogramPanel } from './index'; + +jest.mock('../../../common/lib/kibana'); +jest.mock('../../../common/components/navigation/use_get_url_search'); + +describe('AlertsHistogramPanel', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[id="detections-histogram"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx new file mode 100644 index 0000000000000..7451ea6ec0ca1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx @@ -0,0 +1,284 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Position } from '@elastic/charts'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSelect, EuiPanel } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { isEmpty } from 'lodash/fp'; +import uuid from 'uuid'; + +import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; +import { UpdateDateRange } from '../../../common/components/charts/common'; +import { LegendItem } from '../../../common/components/charts/draggable_legend_item'; +import { escapeDataProviderId } from '../../../common/components/drag_and_drop/helpers'; +import { HeaderSection } from '../../../common/components/header_section'; +import { Filter, esQuery, Query } from '../../../../../../../src/plugins/data/public'; +import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query'; +import { getDetectionEngineUrl } from '../../../common/components/link_to'; +import { defaultLegendColors } from '../../../common/components/matrix_histogram/utils'; +import { InspectButtonContainer } from '../../../common/components/inspect'; +import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; +import { MatrixLoader } from '../../../common/components/matrix_histogram/matrix_loader'; +import { MatrixHistogramOption } from '../../../common/components/matrix_histogram/types'; +import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; +import { navTabs } from '../../../app/home/home_navigations'; +import { alertsHistogramOptions } from './config'; +import { formatAlertsData, getAlertsHistogramQuery, showInitialLoadingSpinner } from './helpers'; +import { AlertsHistogram } from './alerts_histogram'; +import * as i18n from './translations'; +import { RegisterQuery, AlertsHistogramOption, AlertsAggregation, AlertsTotal } from './types'; + +const DEFAULT_PANEL_HEIGHT = 300; + +const StyledEuiPanel = styled(EuiPanel)<{ height?: number }>` + display: flex; + flex-direction: column; + ${({ height }) => (height != null ? `height: ${height}px;` : '')} + position: relative; +`; + +const defaultTotalAlertsObj: AlertsTotal = { + value: 0, + relation: 'eq', +}; + +export const DETECTIONS_HISTOGRAM_ID = 'detections-histogram'; + +const ViewAlertsFlexItem = styled(EuiFlexItem)` + margin-left: 24px; +`; + +interface AlertsHistogramPanelProps { + chartHeight?: number; + defaultStackByOption?: AlertsHistogramOption; + deleteQuery?: ({ id }: { id: string }) => void; + filters?: Filter[]; + from: number; + headerChildren?: React.ReactNode; + /** Override all defaults, and only display this field */ + onlyField?: string; + query?: Query; + legendPosition?: Position; + panelHeight?: number; + signalIndexName: string | null; + setQuery: (params: RegisterQuery) => void; + showLinkToAlerts?: boolean; + showTotalAlertsCount?: boolean; + stackByOptions?: AlertsHistogramOption[]; + title?: string; + to: number; + updateDateRange: UpdateDateRange; +} + +const getHistogramOption = (fieldName: string): MatrixHistogramOption => ({ + text: fieldName, + value: fieldName, +}); + +const NO_LEGEND_DATA: LegendItem[] = []; + +export const AlertsHistogramPanel = memo( + ({ + chartHeight, + defaultStackByOption = alertsHistogramOptions[0], + deleteQuery, + filters, + headerChildren, + onlyField, + query, + from, + legendPosition = 'right', + panelHeight = DEFAULT_PANEL_HEIGHT, + setQuery, + signalIndexName, + showLinkToAlerts = false, + showTotalAlertsCount = false, + stackByOptions, + to, + title = i18n.HISTOGRAM_HEADER, + updateDateRange, + }) => { + // create a unique, but stable (across re-renders) query id + const uniqueQueryId = useMemo(() => `${DETECTIONS_HISTOGRAM_ID}-${uuid.v4()}`, []); + const [isInitialLoading, setIsInitialLoading] = useState(true); + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const [totalAlertsObj, setTotalAlertsObj] = useState(defaultTotalAlertsObj); + const [selectedStackByOption, setSelectedStackByOption] = useState( + onlyField == null ? defaultStackByOption : getHistogramOption(onlyField) + ); + const { + loading: isLoadingAlerts, + data: alertsData, + setQuery: setAlertsQuery, + response, + request, + refetch, + } = useQueryAlerts<{}, AlertsAggregation>( + getAlertsHistogramQuery(selectedStackByOption.value, from, to, []), + signalIndexName + ); + const kibana = useKibana(); + const urlSearch = useGetUrlSearch(navTabs.detections); + + const totalAlerts = useMemo( + () => + i18n.SHOWING_ALERTS( + numeral(totalAlertsObj.value).format(defaultNumberFormat), + totalAlertsObj.value, + totalAlertsObj.relation === 'gte' ? '>' : totalAlertsObj.relation === 'lte' ? '<' : '' + ), + [totalAlertsObj] + ); + + const setSelectedOptionCallback = useCallback((event: React.ChangeEvent) => { + setSelectedStackByOption( + stackByOptions?.find((co) => co.value === event.target.value) ?? defaultStackByOption + ); + }, []); + + const formattedAlertsData = useMemo(() => formatAlertsData(alertsData), [alertsData]); + + const legendItems: LegendItem[] = useMemo( + () => + alertsData?.aggregations?.alertsByGrouping?.buckets != null + ? alertsData.aggregations.alertsByGrouping.buckets.map((bucket, i) => ({ + color: i < defaultLegendColors.length ? defaultLegendColors[i] : undefined, + dataProviderId: escapeDataProviderId( + `draggable-legend-item-${uuid.v4()}-${selectedStackByOption.value}-${bucket.key}` + ), + field: selectedStackByOption.value, + value: bucket.key, + })) + : NO_LEGEND_DATA, + [alertsData, selectedStackByOption.value] + ); + + useEffect(() => { + let canceled = false; + + if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingAlerts })) { + setIsInitialLoading(false); + } + + return () => { + canceled = true; // prevent long running data fetches from updating state after unmounting + }; + }, [isInitialLoading, isLoadingAlerts, setIsInitialLoading]); + + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: uniqueQueryId }); + } + }; + }, []); + + useEffect(() => { + if (refetch != null && setQuery != null) { + setQuery({ + id: uniqueQueryId, + inspect: { + dsl: [request], + response: [response], + }, + loading: isLoadingAlerts, + refetch, + }); + } + }, [setQuery, isLoadingAlerts, alertsData, response, request, refetch]); + + useEffect(() => { + setTotalAlertsObj( + alertsData?.hits.total ?? { + value: 0, + relation: 'eq', + } + ); + }, [alertsData]); + + useEffect(() => { + const converted = esQuery.buildEsQuery( + undefined, + query != null ? [query] : [], + filters?.filter((f) => f.meta.disabled === false) ?? [], + { + ...esQuery.getEsQueryConfig(kibana.services.uiSettings), + dateFormatTZ: undefined, + } + ); + + setAlertsQuery( + getAlertsHistogramQuery( + selectedStackByOption.value, + from, + to, + !isEmpty(converted) ? [converted] : [] + ) + ); + }, [selectedStackByOption.value, from, to, query, filters]); + + const linkButton = useMemo(() => { + if (showLinkToAlerts) { + return ( + + {i18n.VIEW_ALERTS} + + ); + } + }, [showLinkToAlerts, urlSearch]); + + const titleText = useMemo(() => (onlyField == null ? title : i18n.TOP(onlyField)), [ + onlyField, + title, + ]); + + return ( + + + + + + {stackByOptions && ( + + )} + {headerChildren != null && headerChildren} + + {linkButton} + + + + {isInitialLoading ? ( + + ) : ( + + )} + + + ); + } +); + +AlertsHistogramPanel.displayName = 'AlertsHistogramPanel'; diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/translations.ts new file mode 100644 index 0000000000000..6eaa0ba3fc4ec --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/translations.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const STACK_BY_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.stackByLabel', + { + defaultMessage: 'Stack by', + } +); + +export const STACK_BY_RISK_SCORES = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.riskScoresDropDown', + { + defaultMessage: 'Risk scores', + } +); + +export const STACK_BY_SEVERITIES = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.severitiesDropDown', + { + defaultMessage: 'Severities', + } +); + +export const STACK_BY_DESTINATION_IPS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.destinationIpsDropDown', + { + defaultMessage: 'Top destination IPs', + } +); + +export const STACK_BY_SOURCE_IPS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.sourceIpsDropDown', + { + defaultMessage: 'Top source IPs', + } +); + +export const STACK_BY_ACTIONS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.eventActionsDropDown', + { + defaultMessage: 'Top event actions', + } +); + +export const STACK_BY_CATEGORIES = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.eventCategoriesDropDown', + { + defaultMessage: 'Top event categories', + } +); + +export const STACK_BY_HOST_NAMES = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.hostNamesDropDown', + { + defaultMessage: 'Top host names', + } +); + +export const STACK_BY_RULE_TYPES = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.ruleTypesDropDown', + { + defaultMessage: 'Top rule types', + } +); + +export const STACK_BY_RULE_NAMES = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.rulesDropDown', + { + defaultMessage: 'Top rules', + } +); + +export const STACK_BY_USERS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.usersDropDown', + { + defaultMessage: 'Top users', + } +); + +export const TOP = (fieldName: string) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.histogram.topNLabel', { + values: { fieldName }, + defaultMessage: `Top {fieldName}`, + }); + +export const HISTOGRAM_HEADER = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.headerTitle', + { + defaultMessage: 'Alert count', + } +); + +export const ALL_OTHERS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.allOthersGroupingLabel', + { + defaultMessage: 'All others', + } +); + +export const VIEW_ALERTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.viewAlertsButtonLabel', + { + defaultMessage: 'View alerts', + } +); + +export const SHOWING_ALERTS = ( + totalAlertsFormatted: string, + totalAlerts: number, + modifier: string +) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.histogram.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts, modifier }, + defaultMessage: + 'Showing: {modifier}{totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/types.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/types.ts new file mode 100644 index 0000000000000..0bf483f7ec927 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/types.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { inputsModel } from '../../../common/store'; + +export interface AlertsHistogramOption { + text: string; + value: string; +} + +export interface HistogramData { + x: number; + y: number; + g: string; +} + +export interface AlertsAggregation { + alertsByGrouping: { + buckets: AlertsGroupBucket[]; + }; +} + +export interface AlertsBucket { + key_as_string: string; + key: number; + doc_count: number; +} + +export interface AlertsGroupBucket { + key: string; + alerts: { + buckets: AlertsBucket[]; + }; +} + +export interface AlertsTotal { + value: number; + relation: string; +} + +export interface RegisterQuery { + id: string; + inspect: inputsModel.InspectQuery | null; + loading: boolean; + refetch: inputsModel.Refetch; +} diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_info/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_info/index.tsx new file mode 100644 index 0000000000000..7d35e429bcf50 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_info/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import React, { useState, useEffect } from 'react'; + +import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query'; +import { buildLastAlertsQuery } from './query.dsl'; +import { Aggs } from './types'; + +interface AlertInfo { + ruleId?: string | null; +} + +type Return = [React.ReactNode, React.ReactNode]; + +export const useAlertInfo = ({ ruleId = null }: AlertInfo): Return => { + const [lastAlerts, setLastAlerts] = useState( + + ); + const [totalAlerts, setTotalAlerts] = useState( + + ); + + const { loading, data: alerts } = useQueryAlerts(buildLastAlertsQuery(ruleId)); + + useEffect(() => { + if (alerts != null) { + const myAlerts = alerts; + setLastAlerts( + myAlerts.aggregations?.lastSeen.value != null ? ( + + ) : null + ); + setTotalAlerts(<>{myAlerts.hits.total.value}); + } else { + setLastAlerts(null); + setTotalAlerts(null); + } + }, [loading, alerts]); + + return [lastAlerts, totalAlerts]; +}; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_info/query.dsl.ts similarity index 91% rename from x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts rename to x-pack/plugins/security_solution/public/alerts/components/alerts_info/query.dsl.ts index 8cb07a4f8e6b5..a3972fd35bf2d 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_info/query.dsl.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const buildLastSignalsQuery = (ruleId: string | undefined | null) => { +export const buildLastAlertsQuery = (ruleId: string | undefined | null) => { const queryFilter = [ { bool: { should: [{ match: { 'signal.status': 'open' } }], minimum_should_match: 1 }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/types.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_info/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals_info/types.ts rename to x-pack/plugins/security_solution/public/alerts/components/alerts_info/types.ts diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/actions.test.tsx new file mode 100644 index 0000000000000..2fa7cfeedcd15 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/actions.test.tsx @@ -0,0 +1,385 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import sinon from 'sinon'; +import moment from 'moment'; + +import { sendAlertToTimelineAction, determineToAndFrom } from './actions'; +import { + mockEcsDataWithAlert, + defaultTimelineProps, + apolloClient, + mockTimelineApolloResult, +} from '../../../common/mock/'; +import { CreateTimeline, UpdateTimelineLoading } from './types'; +import { Ecs } from '../../../graphql/types'; +import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; + +jest.mock('apollo-client'); + +describe('alert actions', () => { + const anchor = '2020-03-01T17:59:46.349Z'; + const unix = moment(anchor).valueOf(); + let createTimeline: CreateTimeline; + let updateTimelineIsLoading: UpdateTimelineLoading; + let clock: sinon.SinonFakeTimers; + + beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 + jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); + + createTimeline = jest.fn() as jest.Mocked; + updateTimelineIsLoading = jest.fn() as jest.Mocked; + + jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResult); + + clock = sinon.useFakeTimers(unix); + }); + + afterEach(() => { + clock.restore(); + }); + + describe('sendAlertToTimelineAction', () => { + describe('timeline id is NOT empty string and apollo client exists', () => { + test('it invokes updateTimelineIsLoading to set to true', async () => { + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: mockEcsDataWithAlert, + updateTimelineIsLoading, + }); + + expect(updateTimelineIsLoading).toHaveBeenCalledTimes(1); + expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true }); + }); + + test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => { + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: mockEcsDataWithAlert, + updateTimelineIsLoading, + }); + const expected = { + from: 1541444305937, + timeline: { + columns: [ + { + aggregatable: undefined, + category: undefined, + columnHeaderType: 'not-filtered', + description: undefined, + example: undefined, + id: '@timestamp', + placeholder: undefined, + type: undefined, + width: 190, + }, + { + aggregatable: undefined, + category: undefined, + columnHeaderType: 'not-filtered', + description: undefined, + example: undefined, + id: 'message', + placeholder: undefined, + type: undefined, + width: 180, + }, + { + aggregatable: undefined, + category: undefined, + columnHeaderType: 'not-filtered', + description: undefined, + example: undefined, + id: 'event.category', + placeholder: undefined, + type: undefined, + width: 180, + }, + { + aggregatable: undefined, + category: undefined, + columnHeaderType: 'not-filtered', + description: undefined, + example: undefined, + id: 'host.name', + placeholder: undefined, + type: undefined, + width: 180, + }, + { + aggregatable: undefined, + category: undefined, + columnHeaderType: 'not-filtered', + description: undefined, + example: undefined, + id: 'source.ip', + placeholder: undefined, + type: undefined, + width: 180, + }, + { + aggregatable: undefined, + category: undefined, + columnHeaderType: 'not-filtered', + description: undefined, + example: undefined, + id: 'destination.ip', + placeholder: undefined, + type: undefined, + width: 180, + }, + { + aggregatable: undefined, + category: undefined, + columnHeaderType: 'not-filtered', + description: undefined, + example: undefined, + id: 'user.name', + placeholder: undefined, + type: undefined, + width: 180, + }, + ], + dataProviders: [], + dateRange: { + end: 1541444605937, + start: 1541444305937, + }, + deletedEventIds: [], + description: 'This is a sample rule description', + eventIdToNoteIds: {}, + eventType: 'all', + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + key: 'host.name', + negate: false, + params: { + query: 'apache', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'host.name': 'apache', + }, + }, + }, + ], + highlightedDropAndProviderId: '', + historyIds: [], + id: '', + isFavorite: false, + isLive: false, + isLoading: false, + isSaving: false, + isSelectAllChecked: false, + itemsPerPage: 25, + itemsPerPageOptions: [10, 25, 50, 100], + kqlMode: 'filter', + kqlQuery: { + filterQuery: { + kuery: { + expression: '', + kind: 'kuery', + }, + serializedQuery: '', + }, + filterQueryDraft: { + expression: '', + kind: 'kuery', + }, + }, + loadingEventIds: [], + noteIds: [], + pinnedEventIds: {}, + pinnedEventsSaveObject: {}, + savedObjectId: null, + selectedEventIds: {}, + show: true, + showCheckboxes: false, + showRowRenderers: true, + sort: { + columnId: '@timestamp', + sortDirection: 'desc', + }, + status: TimelineStatus.draft, + title: '', + timelineType: TimelineType.default, + templateTimelineId: null, + templateTimelineVersion: null, + version: null, + width: 1100, + }, + to: 1541444605937, + ruleNote: '# this is some markdown documentation', + }; + + expect(createTimeline).toHaveBeenCalledWith(expected); + }); + + test('it invokes createTimeline with kqlQuery.filterQuery.kuery.kind as "kuery" if not specified in returned timeline template', async () => { + const mockTimelineApolloResultModified = { + ...mockTimelineApolloResult, + kqlQuery: { + filterQuery: { + kuery: { + expression: [''], + }, + }, + filterQueryDraft: { + expression: [''], + }, + }, + }; + jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); + + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: mockEcsDataWithAlert, + updateTimelineIsLoading, + }); + // @ts-ignore + const createTimelineArg = createTimeline.mock.calls[0][0]; + + expect(createTimeline).toHaveBeenCalledTimes(1); + expect(createTimelineArg.timeline.kqlQuery.filterQuery.kuery.kind).toEqual('kuery'); + }); + + test('it invokes createTimeline with kqlQuery.filterQueryDraft.kuery.kind as "kuery" if not specified in returned timeline template', async () => { + const mockTimelineApolloResultModified = { + ...mockTimelineApolloResult, + kqlQuery: { + filterQuery: { + kuery: { + expression: [''], + }, + }, + filterQueryDraft: { + expression: [''], + }, + }, + }; + jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); + + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: mockEcsDataWithAlert, + updateTimelineIsLoading, + }); + // @ts-ignore + const createTimelineArg = createTimeline.mock.calls[0][0]; + + expect(createTimeline).toHaveBeenCalledTimes(1); + expect(createTimelineArg.timeline.kqlQuery.filterQueryDraft.kind).toEqual('kuery'); + }); + + test('it invokes createTimeline with default timeline if apolloClient throws', async () => { + jest.spyOn(apolloClient, 'query').mockImplementation(() => { + throw new Error('Test error'); + }); + + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: mockEcsDataWithAlert, + updateTimelineIsLoading, + }); + + expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true }); + expect(updateTimelineIsLoading).toHaveBeenCalledWith({ + id: 'timeline-1', + isLoading: false, + }); + expect(createTimeline).toHaveBeenCalledTimes(1); + expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps); + }); + }); + + describe('timelineId is empty string', () => { + test('it invokes createTimeline with timelineDefaults', async () => { + const ecsDataMock: Ecs = { + ...mockEcsDataWithAlert, + signal: { + rule: { + ...mockEcsDataWithAlert.signal?.rule!, + timeline_id: null, + }, + }, + }; + + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: ecsDataMock, + updateTimelineIsLoading, + }); + + expect(updateTimelineIsLoading).not.toHaveBeenCalled(); + expect(createTimeline).toHaveBeenCalledTimes(1); + expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps); + }); + }); + + describe('apolloClient is not defined', () => { + test('it invokes createTimeline with timelineDefaults', async () => { + const ecsDataMock: Ecs = { + ...mockEcsDataWithAlert, + signal: { + rule: { + ...mockEcsDataWithAlert.signal?.rule!, + timeline_id: [''], + }, + }, + }; + + await sendAlertToTimelineAction({ + createTimeline, + ecsData: ecsDataMock, + updateTimelineIsLoading, + }); + + expect(updateTimelineIsLoading).not.toHaveBeenCalled(); + expect(createTimeline).toHaveBeenCalledTimes(1); + expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps); + }); + }); + }); + + describe('determineToAndFrom', () => { + test('it uses ecs.Data.timestamp if one is provided', () => { + const ecsDataMock: Ecs = { + ...mockEcsDataWithAlert, + timestamp: '2020-03-20T17:59:46.349Z', + }; + const result = determineToAndFrom({ ecsData: ecsDataMock }); + + expect(result.from).toEqual(1584726886349); + expect(result.to).toEqual(1584727186349); + }); + + test('it uses current time timestamp if ecsData.timestamp is not provided', () => { + const { timestamp, ...ecsDataMock } = { + ...mockEcsDataWithAlert, + }; + const result = determineToAndFrom({ ecsData: ecsDataMock }); + + expect(result.from).toEqual(1583085286349); + expect(result.to).toEqual(1583085586349); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/actions.tsx new file mode 100644 index 0000000000000..cde81d44bc5d6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/actions.tsx @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import dateMath from '@elastic/datemath'; +import { getOr, isEmpty } from 'lodash/fp'; +import moment from 'moment'; + +import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; +import { SendAlertToTimelineActionProps, UpdateAlertStatusActionProps } from './types'; +import { TimelineNonEcsData, GetOneTimeline, TimelineResult, Ecs } from '../../../graphql/types'; +import { oneTimelineQuery } from '../../../timelines/containers/one/index.gql_query'; +import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; +import { + omitTypenameInTimeline, + formatTimelineResultToModel, +} from '../../../timelines/components/open_timeline/helpers'; +import { convertKueryToElasticSearchQuery } from '../../../common/lib/keury'; +import { + replaceTemplateFieldFromQuery, + replaceTemplateFieldFromMatchFilters, + replaceTemplateFieldFromDataProviders, +} from './helpers'; + +export const getUpdateAlertsQuery = (eventIds: Readonly) => { + return { + query: { + bool: { + filter: { + terms: { + _id: [...eventIds], + }, + }, + }, + }, + }; +}; + +export const getFilterAndRuleBounds = ( + data: TimelineNonEcsData[][] +): [string[], number, number] => { + const stringFilter = data?.[0].filter((d) => d.field === 'signal.rule.filters')?.[0]?.value ?? []; + + const eventTimes = data + .flatMap((alert) => alert.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) + .map((d) => moment(d)); + + return [stringFilter, moment.min(eventTimes).valueOf(), moment.max(eventTimes).valueOf()]; +}; + +export const updateAlertStatusAction = async ({ + query, + alertIds, + status, + setEventsLoading, + setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, +}: UpdateAlertStatusActionProps) => { + try { + setEventsLoading({ eventIds: alertIds, isLoading: true }); + + const queryObject = query ? { query: JSON.parse(query) } : getUpdateAlertsQuery(alertIds); + + const response = await updateAlertStatus({ query: queryObject, status }); + // TODO: Only delete those that were successfully updated from updatedRules + setEventsDeleted({ eventIds: alertIds, isDeleted: true }); + + onAlertStatusUpdateSuccess(response.updated, status); + } catch (error) { + onAlertStatusUpdateFailure(status, error); + } finally { + setEventsLoading({ eventIds: alertIds, isLoading: false }); + } +}; + +export const determineToAndFrom = ({ ecsData }: { ecsData: Ecs }) => { + const ellapsedTimeRule = moment.duration( + moment().diff( + dateMath.parse(ecsData.signal?.rule?.from != null ? ecsData.signal?.rule?.from[0] : 'now-0s') + ) + ); + + const from = moment(ecsData.timestamp ?? new Date()) + .subtract(ellapsedTimeRule) + .valueOf(); + const to = moment(ecsData.timestamp ?? new Date()).valueOf(); + + return { to, from }; +}; + +export const sendAlertToTimelineAction = async ({ + apolloClient, + createTimeline, + ecsData, + updateTimelineIsLoading, +}: SendAlertToTimelineActionProps) => { + let openAlertInBasicTimeline = true; + const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; + const timelineId = + ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; + const { to, from } = determineToAndFrom({ ecsData }); + + if (timelineId !== '' && apolloClient != null) { + try { + updateTimelineIsLoading({ id: 'timeline-1', isLoading: true }); + const responseTimeline = await apolloClient.query< + GetOneTimeline.Query, + GetOneTimeline.Variables + >({ + query: oneTimelineQuery, + fetchPolicy: 'no-cache', + variables: { + id: timelineId, + }, + }); + const resultingTimeline: TimelineResult = getOr({}, 'data.getOneTimeline', responseTimeline); + + if (!isEmpty(resultingTimeline)) { + const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); + openAlertInBasicTimeline = false; + const { timeline } = formatTimelineResultToModel(timelineTemplate, true); + const query = replaceTemplateFieldFromQuery( + timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', + ecsData + ); + const filters = replaceTemplateFieldFromMatchFilters(timeline.filters ?? [], ecsData); + const dataProviders = replaceTemplateFieldFromDataProviders( + timeline.dataProviders ?? [], + ecsData + ); + createTimeline({ + from, + timeline: { + ...timeline, + dataProviders, + eventType: 'all', + filters, + dateRange: { + start: from, + end: to, + }, + kqlQuery: { + filterQuery: { + kuery: { + kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery', + expression: query, + }, + serializedQuery: convertKueryToElasticSearchQuery(query), + }, + filterQueryDraft: { + kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery', + expression: query, + }, + }, + show: true, + }, + to, + ruleNote: noteContent, + }); + } + } catch { + openAlertInBasicTimeline = true; + updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); + } + } + + if (openAlertInBasicTimeline) { + createTimeline({ + from, + timeline: { + ...timelineDefaults, + dataProviders: [ + { + and: [], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-${ecsData._id}`, + name: ecsData._id, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: '_id', + value: ecsData._id, + operator: ':', + }, + }, + ], + id: 'timeline-1', + dateRange: { + start: from, + end: to, + }, + eventType: 'all', + kqlQuery: { + filterQuery: { + kuery: { + kind: 'kuery', + expression: '', + }, + serializedQuery: '', + }, + filterQueryDraft: { + kind: 'kuery', + expression: '', + }, + }, + }, + to, + ruleNote: noteContent, + }); + } +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx new file mode 100644 index 0000000000000..d7fabdabf8225 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AlertsTableFilterGroup } from './index'; + +describe('AlertsTableFilterGroup', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiFilterButton')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_filter_group/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_filter_group/index.tsx new file mode 100644 index 0000000000000..8521170637d6f --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_filter_group/index.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import * as i18n from '../translations'; + +export const FILTER_OPEN = 'open'; +export const FILTER_CLOSED = 'closed'; +export type AlertFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; + +interface Props { + onFilterGroupChanged: (filterGroup: AlertFilterOption) => void; +} + +const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + + const onClickOpenFilterCallback = useCallback(() => { + setFilterGroup(FILTER_OPEN); + onFilterGroupChanged(FILTER_OPEN); + }, [setFilterGroup, onFilterGroupChanged]); + + const onClickCloseFilterCallback = useCallback(() => { + setFilterGroup(FILTER_CLOSED); + onFilterGroupChanged(FILTER_CLOSED); + }, [setFilterGroup, onFilterGroupChanged]); + + return ( + + + {i18n.OPEN_ALERTS} + + + + {i18n.CLOSED_ALERTS} + + + ); +}; + +export const AlertsTableFilterGroup = React.memo(AlertsTableFilterGroupComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx new file mode 100644 index 0000000000000..543e11c9b1e69 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AlertsUtilityBar } from './index'; + +jest.mock('../../../../common/lib/kibana'); + +describe('AlertsUtilityBar', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[dataTestSubj="openCloseAlert"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx new file mode 100644 index 0000000000000..68b7039690db4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash/fp'; +import React, { useCallback } from 'react'; +import numeral from '@elastic/numeral'; + +import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../../common/components/utility_bar'; +import * as i18n from './translations'; +import { useUiSetting$ } from '../../../../common/lib/kibana'; +import { TimelineNonEcsData } from '../../../../graphql/types'; +import { UpdateAlertsStatus } from '../types'; +import { FILTER_CLOSED, FILTER_OPEN } from '../alerts_filter_group'; + +interface AlertsUtilityBarProps { + canUserCRUD: boolean; + hasIndexWrite: boolean; + areEventsLoading: boolean; + clearSelection: () => void; + isFilteredToOpen: boolean; + selectAll: () => void; + selectedEventIds: Readonly>; + showClearSelection: boolean; + totalCount: number; + updateAlertsStatus: UpdateAlertsStatus; +} + +const AlertsUtilityBarComponent: React.FC = ({ + canUserCRUD, + hasIndexWrite, + areEventsLoading, + clearSelection, + totalCount, + selectedEventIds, + isFilteredToOpen, + selectAll, + showClearSelection, + updateAlertsStatus, +}) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + + const handleUpdateStatus = useCallback(async () => { + await updateAlertsStatus({ + alertIds: Object.keys(selectedEventIds), + status: isFilteredToOpen ? FILTER_CLOSED : FILTER_OPEN, + }); + }, [selectedEventIds, updateAlertsStatus, isFilteredToOpen]); + + const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat); + const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format( + defaultNumberFormat + ); + + return ( + <> + + + + + {i18n.SHOWING_ALERTS(formattedTotalCount, totalCount)} + + + + + {canUserCRUD && hasIndexWrite && ( + <> + + {i18n.SELECTED_ALERTS( + showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, + showClearSelection ? totalCount : Object.keys(selectedEventIds).length + )} + + + + {isFilteredToOpen + ? i18n.BATCH_ACTION_CLOSE_SELECTED + : i18n.BATCH_ACTION_OPEN_SELECTED} + + + { + if (!showClearSelection) { + selectAll(); + } else { + clearSelection(); + } + }} + > + {showClearSelection + ? i18n.CLEAR_SELECTION + : i18n.SELECT_ALL_ALERTS(formattedTotalCount, totalCount)} + + + )} + + + + + ); +}; + +export const AlertsUtilityBar = React.memo( + AlertsUtilityBarComponent, + (prevProps, nextProps) => + prevProps.areEventsLoading === nextProps.areEventsLoading && + prevProps.selectedEventIds === nextProps.selectedEventIds && + prevProps.totalCount === nextProps.totalCount && + prevProps.showClearSelection === nextProps.showClearSelection +); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts new file mode 100644 index 0000000000000..9427464e23726 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SHOWING_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.utilityBar.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Showing {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECTED_ALERTS = (selectedAlertsFormatted: string, selectedAlerts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.utilityBar.selectedAlertsTitle', { + values: { selectedAlertsFormatted, selectedAlerts }, + defaultMessage: + 'Selected {selectedAlertsFormatted} {selectedAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECT_ALL_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.utilityBar.selectAllAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Select all {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const CLEAR_SELECTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.clearSelectionTitle', + { + defaultMessage: 'Clear selection', + } +); + +export const BATCH_ACTIONS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.batchActionsTitle', + { + defaultMessage: 'Batch actions', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInHostsTitle', + { + defaultMessage: 'View selected in hosts', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInNetworkTitle', + { + defaultMessage: 'View selected in network', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInTimelineTitle', + { + defaultMessage: 'View selected in timeline', + } +); + +export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.batchActions.openSelectedTitle', + { + defaultMessage: 'Open selected', + } +); + +export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.batchActions.closeSelectedTitle', + { + defaultMessage: 'Close selected', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.test.tsx new file mode 100644 index 0000000000000..1b9070ff83ac7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.test.tsx @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; +import { buildAlertsRuleIdFilter } from './default_config'; + +jest.mock('./actions'); + +describe('alerts default_config', () => { + describe('buildAlertsRuleIdFilter', () => { + test('given a rule id this will return an array with a single filter', () => { + const filters: Filter[] = buildAlertsRuleIdFilter('rule-id-1'); + const expectedFilter: Filter = { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.rule.id', + params: { + query: 'rule-id-1', + }, + }, + query: { + match_phrase: { + 'signal.rule.id': 'rule-id-1', + }, + }, + }; + expect(filters).toHaveLength(1); + expect(filters[0]).toEqual(expectedFilter); + }); + }); + // TODO: move these tests to ../timelines/components/timeline/body/events/event_column_view.tsx + // describe.skip('getAlertActions', () => { + // let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; + // let setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + // let createTimeline: CreateTimeline; + // let updateTimelineIsLoading: UpdateTimelineLoading; + // + // let onAlertStatusUpdateSuccess: (count: number, status: string) => void; + // let onAlertStatusUpdateFailure: (status: string, error: Error) => void; + // + // beforeEach(() => { + // setEventsLoading = jest.fn(); + // setEventsDeleted = jest.fn(); + // createTimeline = jest.fn(); + // updateTimelineIsLoading = jest.fn(); + // onAlertStatusUpdateSuccess = jest.fn(); + // onAlertStatusUpdateFailure = jest.fn(); + // }); + // + // describe('timeline tooltip', () => { + // test('it invokes sendAlertToTimelineAction when button clicked', () => { + // const alertsActions = getAlertActions({ + // canUserCRUD: true, + // hasIndexWrite: true, + // setEventsLoading, + // setEventsDeleted, + // createTimeline, + // status: 'open', + // updateTimelineIsLoading, + // onAlertStatusUpdateSuccess, + // onAlertStatusUpdateFailure, + // }); + // const timelineAction = alertsActions[0].getAction({ + // eventId: 'even-id', + // ecsData: mockEcsDataWithAlert, + // }); + // const wrapper = mount(timelineAction as React.ReactElement); + // wrapper.find(EuiButtonIcon).simulate('click'); + // + // expect(sendAlertToTimelineAction).toHaveBeenCalled(); + // }); + // }); + // + // describe('alert open action', () => { + // let alertsActions: TimelineAction[]; + // let alertOpenAction: JSX.Element; + // let wrapper: ReactWrapper; + // + // beforeEach(() => { + // alertsActions = getAlertActions({ + // canUserCRUD: true, + // hasIndexWrite: true, + // setEventsLoading, + // setEventsDeleted, + // createTimeline, + // status: 'open', + // updateTimelineIsLoading, + // onAlertStatusUpdateSuccess, + // onAlertStatusUpdateFailure, + // }); + // + // alertOpenAction = alertsActions[1].getAction({ + // eventId: 'event-id', + // ecsData: mockEcsDataWithAlert, + // }); + // + // wrapper = mount(alertOpenAction as React.ReactElement); + // }); + // + // afterEach(() => { + // wrapper.unmount(); + // }); + // + // test('it invokes updateAlertStatusAction when button clicked', () => { + // wrapper.find(EuiButtonIcon).simulate('click'); + // + // expect(updateAlertStatusAction).toHaveBeenCalledWith({ + // alertIds: ['event-id'], + // status: 'open', + // setEventsLoading, + // setEventsDeleted, + // onAlertStatusUpdateSuccess, + // onAlertStatusUpdateFailure, + // }); + // }); + // + // test('it displays expected text on hover', () => { + // const openAlert = wrapper.find(EuiToolTip); + // openAlert.simulate('mouseOver'); + // const tooltip = wrapper.find('.euiToolTipPopover').text(); + // + // expect(tooltip).toEqual(i18n.ACTION_OPEN_ALERT); + // }); + // + // test('it displays expected icon', () => { + // const icon = wrapper.find(EuiButtonIcon).props().iconType; + // + // expect(icon).toEqual('securityAlertDetected'); + // }); + // }); + // + // describe('alert close action', () => { + // let alertsActions: TimelineAction[]; + // let alertCloseAction: JSX.Element; + // let wrapper: ReactWrapper; + // + // beforeEach(() => { + // alertsActions = getAlertActions({ + // canUserCRUD: true, + // hasIndexWrite: true, + // setEventsLoading, + // setEventsDeleted, + // createTimeline, + // status: 'closed', + // updateTimelineIsLoading, + // onAlertStatusUpdateSuccess, + // onAlertStatusUpdateFailure, + // }); + // + // alertCloseAction = alertsActions[1].getAction({ + // eventId: 'event-id', + // ecsData: mockEcsDataWithAlert, + // }); + // + // wrapper = mount(alertCloseAction as React.ReactElement); + // }); + // + // afterEach(() => { + // wrapper.unmount(); + // }); + // + // test('it invokes updateAlertStatusAction when status button clicked', () => { + // wrapper.find(EuiButtonIcon).simulate('click'); + // + // expect(updateAlertStatusAction).toHaveBeenCalledWith({ + // alertIds: ['event-id'], + // status: 'closed', + // setEventsLoading, + // setEventsDeleted, + // onAlertStatusUpdateSuccess, + // onAlertStatusUpdateFailure, + // }); + // }); + // + // test('it displays expected text on hover', () => { + // const closeAlert = wrapper.find(EuiToolTip); + // closeAlert.simulate('mouseOver'); + // const tooltip = wrapper.find('.euiToolTipPopover').text(); + // expect(tooltip).toEqual(i18n.ACTION_CLOSE_ALERT); + // }); + // + // test('it displays expected icon', () => { + // const icon = wrapper.find(EuiButtonIcon).props().iconType; + // + // expect(icon).toEqual('securityAlertResolved'); + // }); + // }); + // }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.tsx new file mode 100644 index 0000000000000..201c46068458b --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.tsx @@ -0,0 +1,248 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable react/display-name */ + +import ApolloClient from 'apollo-client'; + +import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; +import { + TimelineRowAction, + TimelineRowActionOnClick, +} from '../../../timelines/components/timeline/body/actions'; +import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers'; +import { + DEFAULT_COLUMN_MIN_WIDTH, + DEFAULT_DATE_COLUMN_MIN_WIDTH, +} from '../../../timelines/components/timeline/body/constants'; +import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model'; +import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; + +import { FILTER_OPEN } from './alerts_filter_group'; +import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; +import * as i18n from './translations'; +import { + CreateTimeline, + SetEventsDeletedProps, + SetEventsLoadingProps, + UpdateTimelineLoading, +} from './types'; + +export const alertsOpenFilters: Filter[] = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.status', + params: { + query: 'open', + }, + }, + query: { + match_phrase: { + 'signal.status': 'open', + }, + }, + }, +]; + +export const alertsClosedFilters: Filter[] = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.status', + params: { + query: 'closed', + }, + }, + query: { + match_phrase: { + 'signal.status': 'closed', + }, + }, + }, +]; + +export const buildAlertsRuleIdFilter = (ruleId: string): Filter[] => [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.rule.id', + params: { + query: ruleId, + }, + }, + query: { + match_phrase: { + 'signal.rule.id': ruleId, + }, + }, + }, +]; + +export const alertsHeaders: ColumnHeaderOptions[] = [ + { + columnHeaderType: defaultColumnHeaderType, + id: '@timestamp', + width: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.name', + label: i18n.ALERTS_HEADERS_RULE, + linkField: 'signal.rule.id', + width: DEFAULT_COLUMN_MIN_WIDTH, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.version', + label: i18n.ALERTS_HEADERS_VERSION, + width: 95, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.type', + label: i18n.ALERTS_HEADERS_METHOD, + width: 100, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.severity', + label: i18n.ALERTS_HEADERS_SEVERITY, + width: 105, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.risk_score', + label: i18n.ALERTS_HEADERS_RISK_SCORE, + width: 115, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'event.module', + linkField: 'rule.reference', + width: DEFAULT_COLUMN_MIN_WIDTH, + }, + { + category: 'event', + columnHeaderType: defaultColumnHeaderType, + id: 'event.action', + type: 'string', + aggregatable: true, + width: 140, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'event.category', + width: 150, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'host.name', + width: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'user.name', + width: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'source.ip', + width: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'destination.ip', + width: 140, + }, +]; + +export const alertsDefaultModel: SubsetTimelineModel = { + ...timelineDefaults, + columns: alertsHeaders, + showCheckboxes: true, + showRowRenderers: false, +}; + +export const requiredFieldsForActions = [ + '@timestamp', + 'signal.original_time', + 'signal.rule.filters', + 'signal.rule.from', + 'signal.rule.language', + 'signal.rule.query', + 'signal.rule.to', + 'signal.rule.id', +]; + +export const getAlertActions = ({ + apolloClient, + canUserCRUD, + createTimeline, + hasIndexWrite, + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + status, + updateTimelineIsLoading, +}: { + apolloClient?: ApolloClient<{}>; + canUserCRUD: boolean; + createTimeline: CreateTimeline; + hasIndexWrite: boolean; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; + status: 'open' | 'closed'; + updateTimelineIsLoading: UpdateTimelineLoading; +}): TimelineRowAction[] => [ + { + ariaLabel: 'Send alert to timeline', + content: i18n.ACTION_INVESTIGATE_IN_TIMELINE, + dataTestSubj: 'send-alert-to-timeline', + displayType: 'icon', + iconType: 'timeline', + id: 'sendAlertToTimeline', + onClick: ({ ecsData }: TimelineRowActionOnClick) => + sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData, + updateTimelineIsLoading, + }), + width: 26, + }, + { + ariaLabel: 'Update alert status', + content: status === FILTER_OPEN ? i18n.ACTION_OPEN_ALERT : i18n.ACTION_CLOSE_ALERT, + dataTestSubj: 'update-alert-status', + displayType: 'icon', + iconType: status === FILTER_OPEN ? 'securitySignalDetected' : 'securitySignalResolved', + id: 'updateAlertStatus', + isActionDisabled: !canUserCRUD || !hasIndexWrite, + onClick: ({ eventId }: TimelineRowActionOnClick) => + updateAlertStatusAction({ + alertIds: [eventId], + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + status, + }), + width: 26, + }, +]; diff --git a/x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/helpers.test.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts rename to x-pack/plugins/security_solution/public/alerts/components/alerts_table/helpers.test.ts diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/helpers.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/helpers.ts new file mode 100644 index 0000000000000..11a03b0426891 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/helpers.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get, isEmpty } from 'lodash/fp'; +import { Filter, esKuery, KueryNode } from '../../../../../../../src/plugins/data/public'; +import { + DataProvider, + DataProvidersAnd, +} from '../../../timelines/components/timeline/data_providers/data_provider'; +import { Ecs } from '../../../graphql/types'; + +interface FindValueToChangeInQuery { + field: string; + valueToChange: string; +} + +/** + * Fields that will be replaced with the template strings from a a saved timeline template. + * This is used for the alerts detection engine feature when you save a timeline template + * and are the fields you can replace when creating a template. + */ +const templateFields = [ + 'host.name', + 'host.hostname', + 'host.domain', + 'host.id', + 'host.ip', + 'client.ip', + 'destination.ip', + 'server.ip', + 'source.ip', + 'network.community_id', + 'user.name', + 'process.name', +]; + +/** + * This will return an unknown as a string array if it exists from an unknown data type and a string + * that represents the path within the data object the same as lodash's "get". If the value is non-existent + * we will return an empty array. If it is a non string value then this will log a trace to the console + * that it encountered an error and return an empty array. + * @param field string of the field to access + * @param data The unknown data that is typically a ECS value to get the value + * @param localConsole The local console which can be sent in to make this pure (for tests) or use the default console + */ +export const getStringArray = (field: string, data: unknown, localConsole = console): string[] => { + const value: unknown | undefined = get(field, data); + if (value == null) { + return []; + } else if (typeof value === 'string') { + return [value]; + } else if (Array.isArray(value) && value.every((element) => typeof element === 'string')) { + return value; + } else { + localConsole.trace( + 'Data type that is not a string or string array detected:', + value, + 'when trying to access field:', + field, + 'from data object of:', + data + ); + return []; + } +}; + +export const findValueToChangeInQuery = ( + kueryNode: KueryNode, + valueToChange: FindValueToChangeInQuery[] = [] +): FindValueToChangeInQuery[] => { + let localValueToChange = valueToChange; + if (kueryNode.function === 'is' && templateFields.includes(kueryNode.arguments[0].value)) { + localValueToChange = [ + ...localValueToChange, + { + field: kueryNode.arguments[0].value, + valueToChange: kueryNode.arguments[1].value, + }, + ]; + } + return kueryNode.arguments.reduce( + (addValueToChange: FindValueToChangeInQuery[], ast: KueryNode) => { + if (ast.function === 'is' && templateFields.includes(ast.arguments[0].value)) { + return [ + ...addValueToChange, + { + field: ast.arguments[0].value, + valueToChange: ast.arguments[1].value, + }, + ]; + } + if (ast.arguments) { + return findValueToChangeInQuery(ast, addValueToChange); + } + return addValueToChange; + }, + localValueToChange + ); +}; + +export const replaceTemplateFieldFromQuery = (query: string, ecsData: Ecs): string => { + if (query.trim() !== '') { + const valueToChange = findValueToChangeInQuery(esKuery.fromKueryExpression(query)); + return valueToChange.reduce((newQuery, vtc) => { + const newValue = getStringArray(vtc.field, ecsData); + if (newValue.length) { + return newQuery.replace(vtc.valueToChange, newValue[0]); + } else { + return newQuery; + } + }, query); + } else { + return ''; + } +}; + +export const replaceTemplateFieldFromMatchFilters = (filters: Filter[], ecsData: Ecs): Filter[] => + filters.map((filter) => { + if ( + filter.meta.type === 'phrase' && + filter.meta.key != null && + templateFields.includes(filter.meta.key) + ) { + const newValue = getStringArray(filter.meta.key, ecsData); + if (newValue.length) { + filter.meta.params = { query: newValue[0] }; + filter.query = { match_phrase: { [filter.meta.key]: newValue[0] } }; + } + } + return filter; + }); + +export const reformatDataProviderWithNewValue = ( + dataProvider: T, + ecsData: Ecs +): T => { + if (templateFields.includes(dataProvider.queryMatch.field)) { + const newValue = getStringArray(dataProvider.queryMatch.field, ecsData); + if (newValue.length) { + dataProvider.id = dataProvider.id.replace(dataProvider.name, newValue[0]); + dataProvider.name = newValue[0]; + dataProvider.queryMatch.value = newValue[0]; + dataProvider.queryMatch.displayField = undefined; + dataProvider.queryMatch.displayValue = undefined; + } + } + return dataProvider; +}; + +export const replaceTemplateFieldFromDataProviders = ( + dataProviders: DataProvider[], + ecsData: Ecs +): DataProvider[] => + dataProviders.map((dataProvider) => { + const newDataProvider = reformatDataProviderWithNewValue(dataProvider, ecsData); + if (newDataProvider.and != null && !isEmpty(newDataProvider.and)) { + newDataProvider.and = newDataProvider.and.map((andDataProvider) => + reformatDataProviderWithNewValue(andDataProvider, ecsData) + ); + } + return newDataProvider; + }); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx new file mode 100644 index 0000000000000..51fdd828bcddb --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AlertsTableComponent } from './index'; + +describe('AlertsTableComponent', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[title="Alerts"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.tsx new file mode 100644 index 0000000000000..685e66e73ced2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.tsx @@ -0,0 +1,408 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiPanel, EuiLoadingContent } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; +import { Dispatch } from 'redux'; + +import { Filter, esQuery } from '../../../../../../../src/plugins/data/public'; +import { useFetchIndexPatterns } from '../../../alerts/containers/detection_engine/rules/fetch_index_patterns'; +import { StatefulEventsViewer } from '../../../common/components/events_viewer'; +import { HeaderSection } from '../../../common/components/header_section'; +import { combineQueries } from '../../../timelines/components/timeline/helpers'; +import { useKibana } from '../../../common/lib/kibana'; +import { inputsSelectors, State, inputsModel } from '../../../common/store'; +import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline'; +import { TimelineModel } from '../../../timelines/store/timeline/model'; +import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; +import { useManageTimeline } from '../../../timelines/components/manage_timeline'; +import { useApolloClient } from '../../../common/utils/apollo_context'; + +import { updateAlertStatusAction } from './actions'; +import { + getAlertActions, + requiredFieldsForActions, + alertsClosedFilters, + alertsDefaultModel, + alertsOpenFilters, +} from './default_config'; +import { + FILTER_CLOSED, + FILTER_OPEN, + AlertFilterOption, + AlertsTableFilterGroup, +} from './alerts_filter_group'; +import { AlertsUtilityBar } from './alerts_utility_bar'; +import * as i18n from './translations'; +import { + CreateTimelineProps, + SetEventsDeletedProps, + SetEventsLoadingProps, + UpdateAlertsStatusCallback, + UpdateAlertsStatusProps, +} from './types'; +import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; +import { + useStateToaster, + displaySuccessToast, + displayErrorToast, +} from '../../../common/components/toasters'; + +export const ALERTS_TABLE_TIMELINE_ID = 'alerts-table'; + +interface OwnProps { + canUserCRUD: boolean; + defaultFilters?: Filter[]; + hasIndexWrite: boolean; + from: number; + loading: boolean; + signalsIndex: string; + to: number; +} + +type AlertsTableComponentProps = OwnProps & PropsFromRedux; + +export const AlertsTableComponent: React.FC = ({ + canUserCRUD, + clearEventsDeleted, + clearEventsLoading, + clearSelected, + defaultFilters, + from, + globalFilters, + globalQuery, + hasIndexWrite, + isSelectAllChecked, + loading, + loadingEventIds, + selectedEventIds, + setEventsDeleted, + setEventsLoading, + signalsIndex, + to, + updateTimeline, + updateTimelineIsLoading, +}) => { + const [selectAll, setSelectAll] = useState(false); + const apolloClient = useApolloClient(); + + const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( + signalsIndex !== '' ? [signalsIndex] : [] + ); + const kibana = useKibana(); + const [, dispatchToaster] = useStateToaster(); + + const getGlobalQuery = useCallback(() => { + if (browserFields != null && indexPatterns != null) { + return combineQueries({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + dataProviders: [], + indexPattern: indexPatterns, + browserFields, + filters: isEmpty(defaultFilters) + ? globalFilters + : [...(defaultFilters ?? []), ...globalFilters], + kqlQuery: globalQuery, + kqlMode: globalQuery.language, + start: from, + end: to, + isEventViewer: true, + }); + } + return null; + }, [browserFields, globalFilters, globalQuery, indexPatterns, kibana, to, from]); + + // Callback for creating a new timeline -- utilized by row/batch actions + const createTimelineCallback = useCallback( + ({ from: fromTimeline, timeline, to: toTimeline, ruleNote }: CreateTimelineProps) => { + updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); + updateTimeline({ + duplicate: true, + from: fromTimeline, + id: 'timeline-1', + notes: [], + timeline: { + ...timeline, + show: true, + }, + to: toTimeline, + ruleNote, + })(); + }, + [updateTimeline, updateTimelineIsLoading] + ); + + const setEventsLoadingCallback = useCallback( + ({ eventIds, isLoading }: SetEventsLoadingProps) => { + setEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isLoading }); + }, + [setEventsLoading, ALERTS_TABLE_TIMELINE_ID] + ); + + const setEventsDeletedCallback = useCallback( + ({ eventIds, isDeleted }: SetEventsDeletedProps) => { + setEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isDeleted }); + }, + [setEventsDeleted, ALERTS_TABLE_TIMELINE_ID] + ); + + const onAlertStatusUpdateSuccess = useCallback( + (count: number, status: string) => { + const title = + status === 'closed' + ? i18n.CLOSED_ALERT_SUCCESS_TOAST(count) + : i18n.OPENED_ALERT_SUCCESS_TOAST(count); + + displaySuccessToast(title, dispatchToaster); + }, + [dispatchToaster] + ); + + const onAlertStatusUpdateFailure = useCallback( + (status: string, error: Error) => { + const title = + status === 'closed' ? i18n.CLOSED_ALERT_FAILED_TOAST : i18n.OPENED_ALERT_FAILED_TOAST; + displayErrorToast(title, [error.message], dispatchToaster); + }, + [dispatchToaster] + ); + + // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar + useEffect(() => { + if (!isSelectAllChecked) { + setShowClearSelectionAction(false); + } else { + setSelectAll(false); + } + }, [isSelectAllChecked]); + + // Callback for when open/closed filter changes + const onFilterGroupChangedCallback = useCallback( + (newFilterGroup: AlertFilterOption) => { + clearEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); + setFilterGroup(newFilterGroup); + }, + [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] + ); + + // Callback for clearing entire selection from utility bar + const clearSelectionCallback = useCallback(() => { + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); + setSelectAll(false); + setShowClearSelectionAction(false); + }, [clearSelected, setSelectAll, setShowClearSelectionAction]); + + // Callback for selecting all events on all pages from utility bar + // Dispatches to stateful_body's selectAll via TimelineTypeContext props + // as scope of response data required to actually set selectedEvents + const selectAllCallback = useCallback(() => { + setSelectAll(true); + setShowClearSelectionAction(true); + }, [setSelectAll, setShowClearSelectionAction]); + + const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback( + async (refetchQuery: inputsModel.Refetch, { alertIds, status }: UpdateAlertsStatusProps) => { + await updateAlertStatusAction({ + query: showClearSelectionAction ? getGlobalQuery()?.filterQuery : undefined, + alertIds: Object.keys(selectedEventIds), + status, + setEventsDeleted: setEventsDeletedCallback, + setEventsLoading: setEventsLoadingCallback, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, + }); + refetchQuery(); + }, + [ + getGlobalQuery, + selectedEventIds, + setEventsDeletedCallback, + setEventsLoadingCallback, + showClearSelectionAction, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, + ] + ); + + // Callback for creating the AlertsUtilityBar which receives totalCount from EventsViewer component + const utilityBarCallback = useCallback( + (refetchQuery: inputsModel.Refetch, totalCount: number) => { + return ( + 0} + clearSelection={clearSelectionCallback} + hasIndexWrite={hasIndexWrite} + isFilteredToOpen={filterGroup === FILTER_OPEN} + selectAll={selectAllCallback} + selectedEventIds={selectedEventIds} + showClearSelection={showClearSelectionAction} + totalCount={totalCount} + updateAlertsStatus={updateAlertsStatusCallback.bind(null, refetchQuery)} + /> + ); + }, + [ + canUserCRUD, + hasIndexWrite, + clearSelectionCallback, + filterGroup, + loadingEventIds.length, + selectAllCallback, + selectedEventIds, + showClearSelectionAction, + updateAlertsStatusCallback, + ] + ); + + // Send to Timeline / Update Alert Status Actions for each table row + const additionalActions = useMemo( + () => + getAlertActions({ + apolloClient, + canUserCRUD, + hasIndexWrite, + createTimeline: createTimelineCallback, + setEventsLoading: setEventsLoadingCallback, + setEventsDeleted: setEventsDeletedCallback, + status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, + updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, + }), + [ + apolloClient, + canUserCRUD, + createTimelineCallback, + hasIndexWrite, + filterGroup, + setEventsLoadingCallback, + setEventsDeletedCallback, + updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, + ] + ); + const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); + const defaultFiltersMemo = useMemo(() => { + if (isEmpty(defaultFilters)) { + return filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters; + } else if (defaultFilters != null && !isEmpty(defaultFilters)) { + return [ + ...defaultFilters, + ...(filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters), + ]; + } + }, [defaultFilters, filterGroup]); + const { initializeTimeline, setTimelineRowActions } = useManageTimeline(); + + useEffect(() => { + initializeTimeline({ + id: ALERTS_TABLE_TIMELINE_ID, + documentType: i18n.ALERTS_DOCUMENT_TYPE, + footerText: i18n.TOTAL_COUNT_OF_ALERTS, + loadingText: i18n.LOADING_ALERTS, + title: i18n.ALERTS_TABLE_TITLE, + selectAll: canUserCRUD ? selectAll : false, + }); + }, []); + useEffect(() => { + setTimelineRowActions({ + id: ALERTS_TABLE_TIMELINE_ID, + queryFields: requiredFieldsForActions, + timelineRowActions: additionalActions, + }); + }, [additionalActions]); + const headerFilterGroup = useMemo( + () => , + [onFilterGroupChangedCallback] + ); + + if (loading || isEmpty(signalsIndex)) { + return ( + + + + + ); + } + + return ( + + ); +}; + +const makeMapStateToProps = () => { + const getTimeline = timelineSelectors.getTimelineByIdSelector(); + const getGlobalInputs = inputsSelectors.globalSelector(); + const mapStateToProps = (state: State) => { + const timeline: TimelineModel = + getTimeline(state, ALERTS_TABLE_TIMELINE_ID) ?? timelineDefaults; + const { deletedEventIds, isSelectAllChecked, loadingEventIds, selectedEventIds } = timeline; + + const globalInputs: inputsModel.InputsRange = getGlobalInputs(state); + const { query, filters } = globalInputs; + return { + globalQuery: query, + globalFilters: filters, + deletedEventIds, + isSelectAllChecked, + loadingEventIds, + selectedEventIds, + }; + }; + return mapStateToProps; +}; + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + clearSelected: ({ id }: { id: string }) => dispatch(timelineActions.clearSelected({ id })), + setEventsLoading: ({ + id, + eventIds, + isLoading, + }: { + id: string; + eventIds: string[]; + isLoading: boolean; + }) => dispatch(timelineActions.setEventsLoading({ id, eventIds, isLoading })), + clearEventsLoading: ({ id }: { id: string }) => + dispatch(timelineActions.clearEventsLoading({ id })), + setEventsDeleted: ({ + id, + eventIds, + isDeleted, + }: { + id: string; + eventIds: string[]; + isDeleted: boolean; + }) => dispatch(timelineActions.setEventsDeleted({ id, eventIds, isDeleted })), + clearEventsDeleted: ({ id }: { id: string }) => + dispatch(timelineActions.clearEventsDeleted({ id })), + updateTimelineIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => + dispatch(timelineActions.updateIsLoading({ id, isLoading })), + updateTimeline: dispatchUpdateTimeline(dispatch), +}); + +const connector = connect(makeMapStateToProps, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +export const AlertsTable = connector(React.memo(AlertsTableComponent)); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/translations.ts new file mode 100644 index 0000000000000..07cc28681387d --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/translations.ts @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.securitySolution.detectionEngine.pageTitle', { + defaultMessage: 'Detection engine', +}); + +export const ALERTS_TABLE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.tableTitle', + { + defaultMessage: 'Alert list', + } +); + +export const ALERTS_DOCUMENT_TYPE = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.documentTypeTitle', + { + defaultMessage: 'Alerts', + } +); + +export const OPEN_ALERTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.openAlertsTitle', + { + defaultMessage: 'Open alerts', + } +); + +export const CLOSED_ALERTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.closedAlertsTitle', + { + defaultMessage: 'Closed alerts', + } +); + +export const LOADING_ALERTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.loadingAlertsTitle', + { + defaultMessage: 'Loading Alerts', + } +); + +export const TOTAL_COUNT_OF_ALERTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.totalCountOfAlertsTitle', + { + defaultMessage: 'alerts match the search criteria', + } +); + +export const ALERTS_HEADERS_RULE = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.ruleTitle', + { + defaultMessage: 'Rule', + } +); + +export const ALERTS_HEADERS_VERSION = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.versionTitle', + { + defaultMessage: 'Version', + } +); + +export const ALERTS_HEADERS_METHOD = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.methodTitle', + { + defaultMessage: 'Method', + } +); + +export const ALERTS_HEADERS_SEVERITY = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.severityTitle', + { + defaultMessage: 'Severity', + } +); + +export const ALERTS_HEADERS_RISK_SCORE = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.riskScoreTitle', + { + defaultMessage: 'Risk Score', + } +); + +export const ACTION_OPEN_ALERT = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.actions.openAlertTitle', + { + defaultMessage: 'Open alert', + } +); + +export const ACTION_CLOSE_ALERT = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.actions.closeAlertTitle', + { + defaultMessage: 'Close alert', + } +); + +export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineTitle', + { + defaultMessage: 'Investigate in timeline', + } +); + +export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.closedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.openedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.closedAlertFailedToastMessage', + { + defaultMessage: 'Failed to close alert(s).', + } +); + +export const OPENED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.openedAlertFailedToastMessage', + { + defaultMessage: 'Failed to open alert(s)', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/types.ts new file mode 100644 index 0000000000000..ba342ae441857 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/types.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import ApolloClient from 'apollo-client'; + +import { Ecs } from '../../../graphql/types'; +import { TimelineModel } from '../../../timelines/store/timeline/model'; +import { inputsModel } from '../../../common/store'; + +export interface SetEventsLoadingProps { + eventIds: string[]; + isLoading: boolean; +} + +export interface SetEventsDeletedProps { + eventIds: string[]; + isDeleted: boolean; +} + +export interface UpdateAlertsStatusProps { + alertIds: string[]; + status: 'open' | 'closed'; +} + +export type UpdateAlertsStatusCallback = ( + refetchQuery: inputsModel.Refetch, + { alertIds, status }: UpdateAlertsStatusProps +) => void; +export type UpdateAlertsStatus = ({ alertIds, status }: UpdateAlertsStatusProps) => void; + +export interface UpdateAlertStatusActionProps { + query?: string; + alertIds: string[]; + status: 'open' | 'closed'; + setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; + setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; +} + +export interface SendAlertToTimelineActionProps { + apolloClient?: ApolloClient<{}>; + createTimeline: CreateTimeline; + ecsData: Ecs; + updateTimelineIsLoading: UpdateTimelineLoading; +} + +export type UpdateTimelineLoading = ({ id, isLoading }: { id: string; isLoading: boolean }) => void; + +export interface CreateTimelineProps { + from: number; + timeline: TimelineModel; + to: number; + ruleNote?: string; +} + +export type CreateTimeline = ({ from, timeline, to }: CreateTimelineProps) => void; diff --git a/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.tsx new file mode 100644 index 0000000000000..a3e76557a6ff5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { HeaderPage, HeaderPageProps } from '../../../common/components/header_page'; +import * as i18n from './translations'; + +const DetectionEngineHeaderPageComponent: React.FC = (props) => ( + +); + +DetectionEngineHeaderPageComponent.defaultProps = { + badgeOptions: { + beta: true, + text: i18n.PAGE_BADGE_LABEL, + tooltip: i18n.PAGE_BADGE_TOOLTIP, + }, +}; + +export const DetectionEngineHeaderPage = React.memo(DetectionEngineHeaderPageComponent); diff --git a/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/translations.ts new file mode 100644 index 0000000000000..f59be16923805 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_BADGE_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.headerPage.pageBadgeLabel', + { + defaultMessage: 'Beta', + } +); + +export const PAGE_BADGE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionEngine.headerPage.pageBadgeTooltip', + { + defaultMessage: + 'Alerts is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/no_api_integration_callout/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/no_api_integration_callout/index.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/no_api_integration_callout/index.test.tsx rename to x-pack/plugins/security_solution/public/alerts/components/no_api_integration_callout/index.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/components/no_api_integration_callout/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/no_api_integration_callout/index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/no_api_integration_callout/index.tsx rename to x-pack/plugins/security_solution/public/alerts/components/no_api_integration_callout/index.tsx diff --git a/x-pack/plugins/security_solution/public/alerts/components/no_api_integration_callout/translations.ts b/x-pack/plugins/security_solution/public/alerts/components/no_api_integration_callout/translations.ts new file mode 100644 index 0000000000000..bc08a13c8b5d1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/no_api_integration_callout/translations.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_API_INTEGRATION_KEY_CALLOUT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.noApiIntegrationKeyCallOutTitle', + { + defaultMessage: 'API integration key required', + } +); + +export const NO_API_INTEGRATION_KEY_CALLOUT_MSG = i18n.translate( + 'xpack.securitySolution.detectionEngine.noApiIntegrationKeyCallOutMsg', + { + defaultMessage: `A new encryption key is generated for saved objects each time you start Kibana. Without a persistent key, you cannot delete or modify rules after Kibana restarts. To set a persistent key, add the xpack.encryptedSavedObjects.encryptionKey setting with any text value of 32 or more characters to the kibana.yml file.`, + } +); + +export const DISMISS_CALLOUT = i18n.translate( + 'xpack.securitySolution.detectionEngine.dismissNoApiIntegrationKeyButton', + { + defaultMessage: 'Dismiss', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/index.test.tsx new file mode 100644 index 0000000000000..2b1173f8a4843 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { NoWriteAlertsCallOut } from './index'; + +describe('no_write_alerts_callout', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/index.tsx new file mode 100644 index 0000000000000..dcb50ef43a841 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/no_write_alerts_callout/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import React, { memo, useCallback, useState } from 'react'; + +import * as i18n from './translations'; + +const NoWriteAlertsCallOutComponent = () => { + const [showCallOut, setShowCallOut] = useState(true); + const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); + + return showCallOut ? ( + +